diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml
index a1e59a8..f6899f2 100644
--- a/.forgejo/workflows/build.yaml
+++ b/.forgejo/workflows/build.yaml
@@ -1,4 +1,9 @@
-on: [push]
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
jobs:
check-changes:
runs-on: docker
@@ -34,6 +39,7 @@ jobs:
submodules: recursive
- uses: actions/setup-node@v4
with:
+ token: ${{ secrets.GH_TOKEN }}
node-version: lts/*
cache: yarn
cache-dependency-path: '**/yarn.lock'
@@ -42,10 +48,8 @@ jobs:
path: |
~/.cache/pip
~/node_modules
- key: ${{ runner.os }}-pio
- - uses: actions/setup-python@v5
- with:
- python-version: '>=3.10'
+ ~/.cache/ms-playwright
+ key: ${{ runner.os }}-pio-playwright-${{ hashFiles('**/yarn.lock') }}
- name: Get current date
id: dateAndTime
run: echo "dateAndTime=$(date +'%Y-%m-%d-%H:%M')" >> $GITHUB_OUTPUT
@@ -62,14 +66,19 @@ jobs:
- name: Run vitest tests
run: yarn vitest run
- name: Install Playwright Browsers
+ if: steps.cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- name: Build WebUI
run: yarn build
+
+ # The following steps only run on push to main
- name: Get current block
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: getBlockHeight
run: echo "blockHeight=$(curl -s https://mempool.space/api/blocks/tip/height)" >> $GITHUB_OUTPUT
+
- name: Write block height to file
env:
BLOCK_HEIGHT: ${{ steps.getBlockHeight.outputs.blockHeight }}
@@ -94,12 +103,14 @@ jobs:
echo "Directory size is within the threshold $DIRECTORY_SIZE"
fi
- name: Create tarball
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: tar czf webui.tgz --strip-components=1 dist
- name: Build LittleFS
run: |
set -e
/tmp/mklittlefs/mklittlefs -c build_gz -s 410000 output/littlefs.bin
- name: Upload artifacts
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
path: |
@@ -107,7 +118,7 @@ jobs:
output/littlefs.bin
- name: Create release
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- uses: https://code.forgejo.org/actions/forgejo-release@v2.4.0
+ uses: https://code.forgejo.org/actions/forgejo-release@v2.6.0
with:
url: 'https://git.btclock.dev/'
repo: '${{ github.repository }}'
diff --git a/README.md b/README.md
index 805bfa1..7913f27 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,11 @@
# BTClock WebUI
-[](https://github.com/btclock/webui2/actions/workflows/workflow.yml)
+[](https://git.btclock.dev/btclock/webui/releases/latest)
+[](https://git.btclock.dev/btclock/webui/actions?workflow=build.yaml&actor=0&status=0)
The web user-interface for the BTClock, based on Svelte-kit. It uses Bootstrap for the lay-out.
-
+

## Developing
@@ -30,7 +31,11 @@ Make sure the postinstall script is ran, because otherwise the filenames are to
## Deploying
-To upload the firmware to the BTClock, you need to GZIP all the files. You can use the python script `gzip_build.py` for that.
+To upload the firmware to the BTClock, you need to GZIP all the files. You can use the python script `gzip_build.py` for that:
+
+```bash
+python3 gzip_build.py
+```
Then you can make a `LittleFS.bin` with mklittlefs:
diff --git a/doc/screenshot-dark.webp b/doc/screenshot-dark.webp
index a225afe..64f26d2 100644
Binary files a/doc/screenshot-dark.webp and b/doc/screenshot-dark.webp differ
diff --git a/doc/screenshot-light.webp b/doc/screenshot-light.webp
new file mode 100644
index 0000000..d5fad73
Binary files /dev/null and b/doc/screenshot-light.webp differ
diff --git a/doc/screenshot.webp b/doc/screenshot.webp
deleted file mode 100644
index 0a5d87e..0000000
Binary files a/doc/screenshot.webp and /dev/null differ
diff --git a/package.json b/package.json
index 930fc00..8596a5c 100644
--- a/package.json
+++ b/package.json
@@ -5,14 +5,17 @@
"scripts": {
"dev": "vite dev",
"build": "vite build",
+ "build:test": "vite build --config vite.config.test.ts",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"postinstall": "patch-package",
- "test": "npm run test:integration && npm run test:unit",
+ "test": "prettier --write . && eslint . && npm run test:integration && npm run test:unit",
"test:integration": "playwright test",
+ "test:screenshots": "playwright test -c playwright.screenshot.config.ts",
+ "doc:update-screenshots": "playwright test -c playwright.doc-screenshot.config.ts",
"test:unit": "vitest"
},
"devDependencies": {
@@ -32,7 +35,9 @@
"jsdom": "^25.0.0",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.6",
+ "rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.79.3",
+ "sharp": "^0.33.5",
"svelte": "^4.2.19",
"svelte-check": "^4.0.2",
"svelte-preprocess": "^6.0.2",
@@ -40,8 +45,7 @@
"typescript": "^5.5.4",
"typescript-eslint": "^8.7.0",
"vite": "^5.4.7",
- "vitest": "^2.1.1",
- "vitest-github-actions-reporter": "^0.11.0"
+ "vitest": "^2.1.1"
},
"type": "module",
"dependencies": {
@@ -63,10 +67,7 @@
},
"resolutions": {
"es5-ext": ">=0.10.64",
- "undici": ">=5.28.4",
"ws": ">=8.18.0",
- "axios": ">=1.7.7",
"micromatch": ">=4.0.8"
- },
- "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
+ }
}
diff --git a/patches/@sveltejs+kit+2.7.5.patch b/patches/@sveltejs+kit+2.16.0+001+initial.patch
similarity index 66%
rename from patches/@sveltejs+kit+2.7.5.patch
rename to patches/@sveltejs+kit+2.16.0+001+initial.patch
index e25e1e5..7fb98b1 100644
--- a/patches/@sveltejs+kit+2.7.5.patch
+++ b/patches/@sveltejs+kit+2.16.0+001+initial.patch
@@ -1,17 +1,17 @@
diff --git a/node_modules/@sveltejs/kit/src/exports/vite/index.js b/node_modules/@sveltejs/kit/src/exports/vite/index.js
-index 40fa4c6..738cabf 100644
+index ddbe746..1d926a4 100644
--- a/node_modules/@sveltejs/kit/src/exports/vite/index.js
+++ b/node_modules/@sveltejs/kit/src/exports/vite/index.js
-@@ -655,9 +655,9 @@ async function kit({ svelte_config }) {
- input,
+@@ -658,9 +658,9 @@ async function kit({ svelte_config }) {
output: {
- format: 'esm',
+ format: inline ? 'iife' : 'esm',
+ name: `__sveltekit_${version_hash}.app`,
- entryFileNames: ssr ? '[name].js' : `${prefix}/[name].[hash].${ext}`,
-- chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name].[hash].${ext}`,
+- chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[hash].${ext}`,
- assetFileNames: `${prefix}/assets/[name].[hash][extname]`,
+ entryFileNames: ssr ? '[name].js' : `${prefix}/[hash].${ext}`,
-+ chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[hash].${ext}`,
-+ assetFileNames: `${prefix}/assets/[hash][extname]`,
++ chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/c[hash].${ext}`,
++ assetFileNames: `${prefix}/a[hash][extname]`,
hoistTransitiveImports: false,
- sourcemapIgnoreList
- },
+ sourcemapIgnoreList,
+ manualChunks: split ? undefined : () => 'bundle',
diff --git a/patches/@sveltejs+kit+2.8.5.patch b/patches/@sveltejs+kit+2.8.5.patch
deleted file mode 100644
index 80f5dba..0000000
--- a/patches/@sveltejs+kit+2.8.5.patch
+++ /dev/null
@@ -1,17 +0,0 @@
-diff --git a/node_modules/@sveltejs/kit/src/exports/vite/index.js b/node_modules/@sveltejs/kit/src/exports/vite/index.js
-index e6521e9..f31c28b 100644
---- a/node_modules/@sveltejs/kit/src/exports/vite/index.js
-+++ b/node_modules/@sveltejs/kit/src/exports/vite/index.js
-@@ -639,9 +639,9 @@ async function kit({ svelte_config }) {
- input,
- output: {
- format: 'esm',
-- entryFileNames: ssr ? '[name].js' : `${prefix}/[name].[hash].${ext}`,
-- chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name].[hash].${ext}`,
-- assetFileNames: `${prefix}/assets/[name].[hash][extname]`,
-+ entryFileNames: ssr ? '[name].js' : `${prefix}/[hash].${ext}`,
-+ chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[hash].${ext}`,
-+ assetFileNames: `${prefix}/assets/[hash][extname]`,
- hoistTransitiveImports: false,
- sourcemapIgnoreList
- },
diff --git a/playwright.config.ts b/playwright.config.ts
index d60d61c..461f3c2 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -6,11 +6,11 @@ const config: PlaywrightTestConfig = {
timezoneId: 'Europe/Amsterdam'
},
webServer: {
- command: 'npm run build && npm run preview',
+ command: 'npm run build:test && npm run preview',
port: 4173
},
reporter: process.env.CI ? 'github' : 'list',
- testDir: 'tests',
+ testDir: 'tests/playwright',
testMatch: /(.+\.)?(test|spec)\.[jt]s/
};
diff --git a/playwright.doc-screenshot.config.ts b/playwright.doc-screenshot.config.ts
new file mode 100644
index 0000000..f052338
--- /dev/null
+++ b/playwright.doc-screenshot.config.ts
@@ -0,0 +1,27 @@
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+ use: {
+ locale: 'en-GB',
+ timezoneId: 'Europe/Amsterdam'
+ },
+ webServer: {
+ command: 'yarn build && yarn preview',
+ port: 4173
+ },
+ testDir: './tests/doc-screenshots',
+ outputDir: './test-results/screenshots',
+ projects: [
+ {
+ name: 'Light Mode',
+ use: {
+ viewport: { width: 1440, height: 900 },
+ colorScheme: 'light'
+ }
+ },
+ {
+ name: 'Dark Mode',
+ use: { viewport: { width: 1440, height: 900 }, colorScheme: 'dark' }
+ }
+ ]
+});
diff --git a/playwright.screenshot.config.ts b/playwright.screenshot.config.ts
new file mode 100644
index 0000000..f4272ff
--- /dev/null
+++ b/playwright.screenshot.config.ts
@@ -0,0 +1,67 @@
+import { defineConfig, devices } from '@playwright/test';
+
+export default defineConfig({
+ reporter: 'html',
+ use: {
+ locale: 'en-GB',
+ timezoneId: 'Europe/Amsterdam'
+ },
+ webServer: {
+ command: 'npm run build && npm run preview',
+ port: 4173
+ },
+ testDir: './tests/screenshots',
+ outputDir: './test-results/screenshots',
+ projects: [
+ {
+ name: 'MacBook Air 13 inch',
+ use: {
+ viewport: { width: 1440, height: 900 }
+ }
+ },
+ {
+ name: 'iPhone 14 Pro',
+ use: { ...devices['iPhone 14 Pro'] }
+ },
+ {
+ name: 'iPhone 15 Pro Landscape',
+ use: { ...devices['iPhone 15 Pro Landscape'] }
+ },
+ {
+ name: 'MacBook Pro 14 inch',
+ use: {
+ viewport: { width: 1512, height: 982 }
+ }
+ },
+ {
+ name: 'MacBook Pro 14 inch NL locale',
+ use: {
+ viewport: { width: 1512, height: 982 },
+ locale: 'nl'
+ }
+ },
+ {
+ name: 'MacBook Pro 14 inch nl-NL locale',
+ use: {
+ viewport: { width: 1512, height: 982 },
+ locale: 'nl-NL'
+ }
+ },
+ {
+ name: 'MacBook Pro 14 inch Firefox HiDPI',
+ use: { ...devices['Desktop Firefox HiDPI'], viewport: { width: 1512, height: 982 } }
+ },
+ {
+ name: 'MacBook Pro 14 inch Safari',
+ use: { ...devices['Desktop Safari'], viewport: { width: 1512, height: 982 } }
+ },
+ {
+ name: 'MacBook Pro 14 inch Safari Dark Mode',
+ use: {
+ ...devices['Desktop Safari'],
+ viewport: { width: 1512, height: 982 },
+ colorScheme: 'dark'
+ }
+ }
+ ]
+});
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..7dca1ca
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": ["config:recommended"],
+ "packageRules": [
+ {
+ "matchUpdateTypes": ["major"],
+ "enabled": false,
+ "matchPackageNames": ["*"]
+ }
+ ],
+ "npm": {
+ "rangeStrategy": "update-lockfile"
+ }
+}
diff --git a/src/components/ColorSchemeSwitcher.svelte b/src/lib/components/ColorSchemeSwitcher.svelte
similarity index 100%
rename from src/components/ColorSchemeSwitcher.svelte
rename to src/lib/components/ColorSchemeSwitcher.svelte
diff --git a/src/lib/components/Placeholder.svelte b/src/lib/components/Placeholder.svelte
new file mode 100644
index 0000000..3eecfdc
--- /dev/null
+++ b/src/lib/components/Placeholder.svelte
@@ -0,0 +1,11 @@
+
+
+
+ {valueToCheck ? value : ''}
+
diff --git a/src/lib/components/SettingsInput.svelte b/src/lib/components/SettingsInput.svelte
new file mode 100644
index 0000000..01738e6
--- /dev/null
+++ b/src/lib/components/SettingsInput.svelte
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+ {#if suffix}
+ {suffix}
+ {/if}
+
+
+ {#if helpText}
+ {helpText}
+ {/if}
+
+
diff --git a/src/lib/components/SettingsSelect.svelte b/src/lib/components/SettingsSelect.svelte
new file mode 100644
index 0000000..ef34fec
--- /dev/null
+++ b/src/lib/components/SettingsSelect.svelte
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ {#each options as [key, val]}
+
+ {/each}
+
+ {#if helpText}
+ {helpText}
+ {/if}
+
+
diff --git a/src/lib/components/SettingsSwitch.svelte b/src/lib/components/SettingsSwitch.svelte
new file mode 100644
index 0000000..27a3156
--- /dev/null
+++ b/src/lib/components/SettingsSwitch.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/src/components/ToggleHeader.svelte b/src/lib/components/ToggleHeader.svelte
similarity index 100%
rename from src/components/ToggleHeader.svelte
rename to src/lib/components/ToggleHeader.svelte
diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts
new file mode 100644
index 0000000..b026c1c
--- /dev/null
+++ b/src/lib/components/index.ts
@@ -0,0 +1,6 @@
+export { default as SettingsSwitch } from './SettingsSwitch.svelte';
+export { default as SettingsInput } from './SettingsInput.svelte';
+export { default as SettingsSelect } from './SettingsSelect.svelte';
+export { default as ToggleHeader } from './ToggleHeader.svelte';
+export { default as ColorSchemeSwitcher } from './ColorSchemeSwitcher.svelte';
+export { default as Placeholder } from './Placeholder.svelte';
diff --git a/src/lib/components/settings/DataSourceSettings.svelte b/src/lib/components/settings/DataSourceSettings.svelte
new file mode 100644
index 0000000..5891573
--- /dev/null
+++ b/src/lib/components/settings/DataSourceSettings.svelte
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+ Data Source
+
+
+
+
+
+
+
+
+ {#if $settings.nostrRelay}
+
+
+
+ {/if}
+
+
+
+
+
+
+
+
+ {#if $settings.dataSource === DataSourceType.THIRD_PARTY_SOURCE}
+
+
+
+ HTTPS
+
+
+ {/if}
+
+ {#if $settings.dataSource === DataSourceType.NOSTR_SOURCE}
+
+ checkValidNostrPubkey('nostrPubKey')}
+ onInput={() => checkValidNostrPubkey('nostrPubKey')}
+ />
+ {/if}
+
+ {#if $settings.dataSource === DataSourceType.CUSTOM_SOURCE}
+
+
+ {/if}
+
+
diff --git a/src/lib/components/settings/DisplaySettings.svelte b/src/lib/components/settings/DisplaySettings.svelte
new file mode 100644
index 0000000..0edaad0
--- /dev/null
+++ b/src/lib/components/settings/DisplaySettings.svelte
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+ ($settings.timePerScreen = Number(e.target.value))}
+ type="number"
+ min={1}
+ step={1}
+ required={true}
+ suffix={$_('time.minutes')}
+ size={$uiSettings.inputSize}
+ />
+
+
+
+
+
+
+ {#if $settings.hasFrontlight && !$settings.flDisable}
+
+
+
+ {/if}
+
+ {#if !$settings.flDisable && $settings.hasLightLevel}
+
+ {/if}
+
+
+
+
+
+
+
+
+ {#if $settings.hasFrontlight}
+
+ {/if}
+
+ {#if $settings.hasFrontlight && !$settings.flDisable}
+
+
+
+
+ {#if $settings.hasLightLevel}
+
+ {/if}
+ {/if}
+
+
+
diff --git a/src/lib/components/settings/ExtraFeaturesSettings.svelte b/src/lib/components/settings/ExtraFeaturesSettings.svelte
new file mode 100644
index 0000000..2287d5b
--- /dev/null
+++ b/src/lib/components/settings/ExtraFeaturesSettings.svelte
@@ -0,0 +1,307 @@
+
+
+
+
+
+
+ {#if $settings.dnd.timeBasedEnabled}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/if}
+
+
+ {#if 'bitaxeEnabled' in $settings}
+
+
+ BitAxe
+
+ {#if $settings.bitaxeEnabled}
+
+
+
+ {/if}
+
+
+ {/if}
+
+
+ {#if 'miningPoolStats' in $settings}
+
+
+ Mining Pool stats
+
+ {#if $settings.miningPoolStats}
+
+ {#if $settings.miningPoolName === 'local_public_pool'}
+
+
+
+ {/if}
+
+ {/if}
+
+
+ {/if}
+
+
+ {#if 'nostrZapNotify' in $settings}
+
+
+ Nostr
+
+
+ {#if $settings.nostrZapNotify}
+
+
+ {#if $settings.hasFrontlight && !$settings.flDisable}
+
+ {/if}
+
+ checkValidNostrPubkey('nostrZapPubkey')}
+ onInput={() => checkValidNostrPubkey('nostrZapPubkey')}
+ />
+ {/if}
+
+
+ {/if}
+
+
diff --git a/src/lib/components/settings/ScreenSpecificSettings.svelte b/src/lib/components/settings/ScreenSpecificSettings.svelte
new file mode 100644
index 0000000..bae67df
--- /dev/null
+++ b/src/lib/components/settings/ScreenSpecificSettings.svelte
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if !$settings.actCurrencies}
+
+ {/if}
+
+
+ {$_('section.settings.screens')}
+ {#if $settings.screens}
+ {#each $settings.screens as s}
+
+ {/each}
+ {/if}
+
+ {#if $settings.actCurrencies && $settings.dataSource == DataSourceType.BTCLOCK_SOURCE}
+
+ {$_('section.settings.currencies')}
+ {$_('restartRequired')}
+ {#if $settings.availableCurrencies}
+ {#each $settings.availableCurrencies as c}
+
+
+
+
+
+
+ {/each}
+ {/if}
+
+ {/if}
+
+
diff --git a/src/lib/components/settings/SystemSettings.svelte b/src/lib/components/settings/SystemSettings.svelte
new file mode 100644
index 0000000..f774c8f
--- /dev/null
+++ b/src/lib/components/settings/SystemSettings.svelte
@@ -0,0 +1,98 @@
+
+
+
+
+ ($settings.tzString = value)}
+ size={$uiSettings.inputSize}
+ />
+
+ {#if $settings.httpAuthEnabled}
+
+
+
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/components/settings/TimezoneSelector.svelte b/src/lib/components/settings/TimezoneSelector.svelte
new file mode 100644
index 0000000..f76ddd6
--- /dev/null
+++ b/src/lib/components/settings/TimezoneSelector.svelte
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+ {#each timezones as tz}
+
+ {/each}
+
+
+
+ {$_('section.settings.tzOffsetHelpText')}
+
+
diff --git a/src/lib/components/settings/index.ts b/src/lib/components/settings/index.ts
new file mode 100644
index 0000000..6ddfa7c
--- /dev/null
+++ b/src/lib/components/settings/index.ts
@@ -0,0 +1,5 @@
+export { default as ScreenSpecificSettings } from './ScreenSpecificSettings.svelte';
+export { default as DisplaySettings } from './DisplaySettings.svelte';
+export { default as DataSourceSettings } from './DataSourceSettings.svelte';
+export { default as ExtraFeaturesSettings } from './ExtraFeaturesSettings.svelte';
+export { default as SystemSettings } from './SystemSettings.svelte';
diff --git a/src/lib/i18n/en.json b/src/lib/i18n/en.json
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/i18n/index.ts b/src/lib/i18n/index.ts
index 2959787..fa817a7 100644
--- a/src/lib/i18n/index.ts
+++ b/src/lib/i18n/index.ts
@@ -8,11 +8,23 @@ register('nl', () => import('../locales/nl.json'));
register('es', () => import('../locales/es.json'));
register('de', () => import('../locales/de.json'));
+const getInitialLocale = () => {
+ if (!browser) return defaultLocale;
+
+ // Check localStorage first
+ const storedLocale = localStorage.getItem('locale');
+ if (storedLocale) return storedLocale;
+
+ // Get browser locale and normalize it
+ const browserLocale = window.navigator.language;
+ const normalizedLocale = browserLocale.split('-')[0].toLowerCase();
+
+ // Check if we support this locale
+ const supportedLocales = ['en', 'nl', 'es', 'de'];
+ return supportedLocales.includes(normalizedLocale) ? normalizedLocale : defaultLocale;
+};
+
init({
fallbackLocale: defaultLocale,
- initialLocale: browser
- ? browser && localStorage.getItem('locale')
- ? localStorage.getItem('locale')
- : window.navigator.language.slice(0, 2)
- : defaultLocale
+ initialLocale: getInitialLocale()
});
diff --git a/src/lib/locales/de.json b/src/lib/locales/de.json
index 50c6ffc..2044e49 100644
--- a/src/lib/locales/de.json
+++ b/src/lib/locales/de.json
@@ -30,7 +30,7 @@
"wifiTxPower": "WiFi-TX-Leistung",
"settingsSaved": "Einstellungen gespeichert",
"errorSavingSettings": "Fehler beim Speichern der Einstellungen",
- "ownDataSource": "BTClock-Datenquelle verwenden",
+ "ownDataSource": "BTClock-Datenquelle",
"flAlwaysOn": "Displaybeleuchtung immer an",
"flEffectDelay": "Displaybeleuchtungeffekt Geschwindigkeit",
"flFlashOnUpd": "Displaybeleuchting bei neuem Block",
@@ -55,7 +55,25 @@
"ledFlashOnZap": "LED blinkt bei Nostr Zap",
"flFlashOnZap": "Displaybeleuchting bei Nostr Zap",
"showAll": "Alle anzeigen",
- "hideAll": "Alles ausblenden"
+ "hideAll": "Alles ausblenden",
+ "flOffWhenDark": "Displaybeleuchtung aus, wenn es dunkel ist",
+ "luxLightToggleText": "Zum Deaktivieren auf 0 setzen",
+ "verticalDesc": "Vrtikale Bildschirmbeschreibung",
+ "enableDebugLog": "Debug-Protokoll aktivieren",
+ "bitaxeEnabled": "BitAxe-Integration aktivieren",
+ "miningPoolStats": "Mining-Pool-Statistiken Integration Aktivieren",
+ "nostrZapNotify": "Nostr Zap-Benachrichtigungen aktivieren",
+ "thirdPartySource": "mempool.space/coincap.io Verwenden",
+ "dataSource": {
+ "nostr": "Nostr-Verlag",
+ "custom": "Benutzerdefinierter dataquelle"
+ },
+ "fontName": "Schriftart",
+ "timeBasedDnd": "Aktivieren Sie den Zeitplan „Bitte nicht stören“.",
+ "dndStartHour": "Startstunde",
+ "dndStartMinute": "Startminute",
+ "dndEndHour": "Endstunde",
+ "dndEndMinute": "Schlussminute"
},
"control": {
"systemInfo": "Systeminfo",
@@ -83,7 +101,9 @@
"wifiSignalStrength": "WiFi-Signalstärke",
"wsDataConnection": "BTClock-Datenquelle verbindung",
"lightSensor": "Lichtsensor",
- "nostrConnection": "Nostr Relay-Verbindung"
+ "nostrConnection": "Nostr Relay-Verbindung",
+ "doNotDisturb": "Bitte nicht stören",
+ "timeBasedDnd": "Zeitbasierter Zeitplan"
},
"firmwareUpdater": {
"fileUploadSuccess": "Datei erfolgreich hochgeladen, Gerät neu gestartet. WebUI in {countdown} Sekunden neu geladen",
@@ -95,7 +115,8 @@
"latestVersion": "Letzte Version",
"releaseDate": "Veröffentlichungsdatum",
"viewRelease": "Veröffentlichung anzeigen",
- "autoUpdate": "Update installieren (experimentell)"
+ "autoUpdate": "Update installieren (experimentell)",
+ "autoUpdateInProgress": "Automatische Aktualisierung läuft, bitte warten..."
}
},
"colors": {
diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json
index b4420dd..c3f0930 100644
--- a/src/lib/locales/en.json
+++ b/src/lib/locales/en.json
@@ -29,7 +29,7 @@
"wifiTxPower": "WiFi TX power",
"settingsSaved": "Settings saved",
"errorSavingSettings": "Error saving settings",
- "ownDataSource": "Use BTClock data source",
+ "ownDataSource": "BTClock data source",
"flMaxBrightness": "Frontlight brightness",
"flAlwaysOn": "Frontlight always on",
"flEffectDelay": "Frontlight effect speed",
@@ -40,10 +40,13 @@
"nostrPubKey": "Nostr source pubkey",
"nostrZapKey": "Nostr zap pubkey",
"nostrRelay": "Nostr Relay",
- "nostrZapNotify": "Nostr Zap Notifications",
+ "nostrZapNotify": "Enable Nostr Zap Notifications",
"useNostr": "Use Nostr data source",
"bitaxeHostname": "BitAxe hostname or IP",
- "bitaxeEnabled": "Enable BitAxe",
+ "bitaxeEnabled": "Enable BitAxe-integration",
+ "miningPoolStats": "Enable Mining Pool Stats integration",
+ "miningPoolName": "Mining Pool",
+ "miningPoolUser": "Mining Pool username or api key",
"nostrZapPubkey": "Nostr Zap pubkey",
"invalidNostrPubkey": "Invalid Nostr pubkey, note that your pubkey does NOT start with npub.",
"convertingValidNpub": "Converting valid npub to pubkey",
@@ -53,7 +56,7 @@
"httpAuthPass": "WebUI Password",
"httpAuthText": "Only password-protects WebUI, not API-calls.",
"currencies": "Currencies",
- "stagingSource": "Use Staging data source (for development)",
+ "customSource": "Use custom data source endpoint",
"useNostrTooltip": "Very experimental and unstable. Nostr data source is not required for Nostr Zap notifications.",
"mowMode": "Mow Suffix Mode",
"suffixShareDot": "Suffix compact notation",
@@ -67,7 +70,27 @@
"ledFlashOnZap": "LED flash on Nostr Zap",
"flFlashOnZap": "Frontlight flash on Nostr Zap",
"showAll": "Show all",
- "hideAll": "Hide all"
+ "hideAll": "Hide all",
+ "flOffWhenDark": "Frontlight off when dark",
+ "luxLightToggleText": "Set to 0 to disable",
+ "verticalDesc": "Use vertical screen description",
+ "enableDebugLog": "Enable Debug-log",
+ "dataSource": {
+ "label": "Data Source",
+ "btclock": "BTClock Data Source",
+ "thirdParty": "mempool.space/Kraken",
+ "nostr": "Nostr publisher",
+ "custom": "Custom Endpoint"
+ },
+ "thirdPartySource": "Use mempool.space/coincap.io",
+ "ceDisableSSL": "Disable SSL",
+ "ceEndpoint": "Endpoint hostname",
+ "fontName": "Font",
+ "timeBasedDnd": "Enable Do Not Disturb time schedule",
+ "dndStartHour": "Start hour",
+ "dndStartMinute": "Start minute",
+ "dndEndHour": "End hour",
+ "dndEndMinute": "End minute"
},
"control": {
"systemInfo": "System info",
@@ -97,7 +120,9 @@
"wifiSignalStrength": "WiFi Signal strength",
"wsDataConnection": "BTClock data-source connection",
"lightSensor": "Light sensor",
- "nostrConnection": "Nostr Relay connection"
+ "nostrConnection": "Nostr Relay connection",
+ "doNotDisturb": "Do not disturb",
+ "timeBasedDnd": "Time-based schedule"
},
"firmwareUpdater": {
"fileUploadFailed": "File upload failed. Make sure you have selected the correct file and try again.",
@@ -109,7 +134,8 @@
"latestVersion": "Latest Version",
"releaseDate": "Release Date",
"viewRelease": "View Release",
- "autoUpdate": "Install update (experimental)"
+ "autoUpdate": "Install update (experimental)",
+ "autoUpdateInProgress": "Auto-update in progress, please wait..."
}
},
"colors": {
diff --git a/src/lib/locales/es.json b/src/lib/locales/es.json
index 121767f..4033cab 100644
--- a/src/lib/locales/es.json
+++ b/src/lib/locales/es.json
@@ -28,7 +28,7 @@
"wifiTxPowerText": "En la mayoría de los casos no es necesario configurar esto.",
"settingsSaved": "Configuración guardada",
"errorSavingSettings": "Error al guardar la configuración",
- "ownDataSource": "Utilice la fuente de datos BTClock",
+ "ownDataSource": "fuente de datos BTClock",
"flMaxBrightness": "Brillo de luz de la pantalla",
"flAlwaysOn": "Luz de la pantalla siempre encendida",
"flEffectDelay": "Velocidad del efecto de luz de la pantalla",
@@ -54,7 +54,25 @@
"ledFlashOnZap": "LED parpadeante con Nostr Zap",
"flFlashOnZap": "Flash de luz frontal con Nostr Zap",
"showAll": "Mostrar todo",
- "hideAll": "Ocultar todo"
+ "hideAll": "Ocultar todo",
+ "flOffWhenDark": "Luz de la pantalla cuando está oscuro",
+ "luxLightToggleText": "Establecer en 0 para desactivar",
+ "verticalDesc": "Descripción de pantalla vertical",
+ "enableDebugLog": "Habilitar registro de depuración",
+ "bitaxeEnabled": "Habilitar la integración de BitAxe",
+ "miningPoolStats": "Habilitar la integración de estadísticas del grupo minero",
+ "nostrZapNotify": "Habilitar notificaciones de Nostr Zap",
+ "thirdPartySource": "Utilice mempool.space/coincap.io",
+ "dataSource": {
+ "nostr": "editorial nostr",
+ "custom": "Punto final personalizado"
+ },
+ "fontName": "Fuente",
+ "timeBasedDnd": "Habilitar el horario de No molestar",
+ "dndStartHour": "Hora de inicio",
+ "dndStartMinute": "Minuto de inicio",
+ "dndEndHour": "Hora final",
+ "dndEndMinute": "Minuto final"
},
"control": {
"turnOff": "Apagar",
@@ -82,7 +100,9 @@
"wifiSignalStrength": "Fuerza de la señal WiFi",
"wsDataConnection": "Conexión de fuente de datos BTClock",
"lightSensor": "Sensor de luz",
- "nostrConnection": "Conexión de relé Nostr"
+ "nostrConnection": "Conexión de relé Nostr",
+ "doNotDisturb": "No molestar",
+ "timeBasedDnd": "Horario basado en el tiempo"
},
"firmwareUpdater": {
"fileUploadSuccess": "Archivo cargado exitosamente, reiniciando el dispositivo. Recargando WebUI en {countdown} segundos",
@@ -94,7 +114,8 @@
"latestVersion": "Ultima versión",
"releaseDate": "Fecha de lanzamiento",
"viewRelease": "Ver lanzamiento",
- "autoUpdate": "Instalar actualización (experimental)"
+ "autoUpdate": "Instalar actualización (experimental)",
+ "autoUpdateInProgress": "Actualización automática en progreso, espere..."
}
},
"button": {
diff --git a/src/lib/locales/nl.json b/src/lib/locales/nl.json
index 65c120e..b239905 100644
--- a/src/lib/locales/nl.json
+++ b/src/lib/locales/nl.json
@@ -55,7 +55,16 @@
"ledFlashOnZap": "Knipper LED bij Nostr Zap",
"flFlashOnZap": "Knipper displaylicht bij Nostr Zap",
"showAll": "Toon alles",
- "hideAll": "Alles verbergen"
+ "hideAll": "Alles verbergen",
+ "flOffWhenDark": "Displaylicht uit als het donker is",
+ "luxLightToggleText": "Stel in op 0 om uit te schakelen",
+ "verticalDesc": "Verticale schermbeschrijving",
+ "fontName": "Lettertype",
+ "timeBasedDnd": "Schakel het tijdschema Niet storen in",
+ "dndStartHour": "Begin uur",
+ "dndStartMinute": "Beginminuut",
+ "dndEndHour": "Eind uur",
+ "dndEndMinute": "Einde minuut"
},
"control": {
"systemInfo": "Systeeminformatie",
@@ -82,7 +91,9 @@
"wifiSignalStrength": "WiFi signaalsterkte",
"wsDataConnection": "BTClock-gegevensbron verbinding",
"lightSensor": "Licht sensor",
- "nostrConnection": "Nostr Relay-verbinding"
+ "nostrConnection": "Nostr Relay-verbinding",
+ "doNotDisturb": "Niet storen",
+ "timeBasedDnd": "Op tijd gebaseerd schema"
},
"firmwareUpdater": {
"fileUploadSuccess": "Bestand geüpload, apparaat herstart. WebUI opnieuw geladen over {countdown} seconden",
@@ -94,7 +105,8 @@
"latestVersion": "Laatste versie",
"releaseDate": "Datum van publicatie",
"viewRelease": "Bekijk publicatie",
- "autoUpdate": "Update installeren (experimenteel)"
+ "autoUpdate": "Update installeren (experimenteel)",
+ "autoUpdateInProgress": "Automatische update wordt uitgevoerd. Even geduld a.u.b...."
}
},
"colors": {
diff --git a/src/lib/style/app.scss b/src/lib/style/app.scss
index 0fd0702..dd3d50a 100644
--- a/src/lib/style/app.scss
+++ b/src/lib/style/app.scss
@@ -1,11 +1,23 @@
+@use '@fontsource/ubuntu/scss/mixins' as Ubuntu;
+@use '@fontsource/antonio/scss/mixins' as Antonio;
+
@import '../node_modules/bootstrap/scss/functions';
-@import '../node_modules/bootstrap/scss/variables';
-@import '../node_modules/bootstrap/scss/variables-dark';
//@import "@fontsource/antonio/latin-400.css";
-@import '@fontsource/ubuntu/latin-400.css';
-//@import '@fontsource/oswald/latin-400.css';
-@import '@fontsource/antonio/latin-400.css';
+
+@include Ubuntu.faces(
+ $subsets: latin,
+ $weights: 400,
+ $formats: 'woff2',
+ $directory: '@fontsource/ubuntu/files'
+);
+
+@include Antonio.faces(
+ $subsets: latin,
+ $weights: 400,
+ $formats: 'woff2',
+ $directory: '@fontsource/antonio/files'
+);
@import './satsymbol';
@@ -14,6 +26,8 @@ $font-family-base: 'Ubuntu';
$font-size-base: 0.9rem;
$input-font-size-sm: $font-size-base * 0.875;
+@import '../node_modules/bootstrap/scss/variables';
+@import '../node_modules/bootstrap/scss/variables-dark';
// $border-radius: .675rem;
@import '../node_modules/bootstrap/scss/mixins';
@@ -39,10 +53,46 @@ $input-font-size-sm: $font-size-base * 0.875;
@import '../node_modules/bootstrap/scss/tooltip';
@import '../node_modules/bootstrap/scss/toasts';
@import '../node_modules/bootstrap/scss/alert';
+@import '../node_modules/bootstrap/scss/placeholders';
+@import '../node_modules/bootstrap/scss/spinners';
@import '../node_modules/bootstrap/scss/helpers';
@import '../node_modules/bootstrap/scss/utilities/api';
+/* Default state (xs) - sticky */
+.sticky-xs-top {
+ position: sticky;
+ top: 0;
+ z-index: 1020;
+}
+
+@media (max-width: 576px) {
+ main {
+ margin-top: 25px;
+ }
+}
+
+/* Remove sticky behavior for larger screens */
+@media (min-width: 576px) {
+ .sticky-xs-top {
+ position: relative;
+ }
+}
+
+@include color-mode(dark) {
+ .navbar {
+ --bs-navbar-color: $light;
+ background-color: $dark;
+ }
+}
+
+@include color-mode(light) {
+ .navbar {
+ --bs-navbar-color: $dark;
+ background-color: $light;
+ }
+}
+
nav {
margin-bottom: 15px;
}
@@ -53,6 +103,23 @@ nav {
.btn-group-sm .btn {
font-size: 0.8rem;
+ // text-overflow: ellipsis;
+ // white-space: nowrap;
+ // overflow: hidden;
+ // width: 4rem;
+}
+
+.btn-group-sm {
+ display: flex !important;
+ flex-wrap: wrap !important;
+ gap: 0.25rem !important;
+}
+
+/* Remove the border radius override that Bootstrap applies */
+.btn-group-sm > .btn {
+ border-radius: 0.25rem !important;
+ margin: 0 !important;
+ position: relative !important;
}
#customText {
@@ -63,7 +130,7 @@ nav {
.btclock {
background: #000;
display: flex;
- font-size: calc(3vw + 3vh);
+ font-size: calc(2vw + 2vh);
font-family: 'Antonio', sans-serif;
font-weight: 400;
padding: 10px;
@@ -104,19 +171,25 @@ nav {
flex-direction: column; /* Stack the text and line vertically */
align-items: center;
justify-content: space-around; /* Distribute items with space between */
- padding: 10px;
+ padding: 5px;
}
- .splitText div:first-child::after {
+ &.verticalDesc > .splitText:first-child {
+ .textcontainer {
+ transform: rotate(-90deg);
+ }
+ }
+
+ .splitText .textcontainer :first-child::after {
display: block;
content: '';
margin-top: 0px;
border-bottom: 2px solid;
- margin-bottom: 3px;
+ // margin-bottom: 3px;
}
.splitText {
- font-size: calc(0.5vw + 1vh);
+ font-size: calc(0.3vw + 1vh);
.top-text,
.bottom-text {
@@ -242,3 +315,38 @@ nav {
input[type='number'] {
text-align: right;
}
+
+.lightMode .bitaxelogo {
+ filter: brightness(0) saturate(100%);
+}
+
+.connection-lost-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.75);
+ z-index: 1050;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ .overlay-content {
+ background-color: rgba(255, 255, 255, 0.75);
+
+ padding: 0.5rem;
+ border-radius: 0.5rem;
+ text-align: center;
+
+ i {
+ font-size: 1rem;
+ color: $danger;
+ margin-bottom: 1rem;
+ }
+
+ h4 {
+ margin-bottom: 0.5rem;
+ }
+ }
+}
diff --git a/src/lib/style/satsymbol.scss b/src/lib/style/satsymbol.scss
index f761701..9b7eefd 100644
--- a/src/lib/style/satsymbol.scss
+++ b/src/lib/style/satsymbol.scss
@@ -1,8 +1,6 @@
@font-face {
font-family: 'Satoshi Symbol';
- src:
- url('/fonts/Satoshi_Symbol.woff2') format('woff2'),
- url('/fonts/Satoshi_Symbol.woff') format('woff');
+ src: url('/fonts/Satoshi_Symbol.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
diff --git a/src/lib/types/dataSource.ts b/src/lib/types/dataSource.ts
new file mode 100644
index 0000000..ffadd46
--- /dev/null
+++ b/src/lib/types/dataSource.ts
@@ -0,0 +1,6 @@
+export enum DataSourceType {
+ BTCLOCK_SOURCE = 0,
+ THIRD_PARTY_SOURCE = 1,
+ NOSTR_SOURCE = 2,
+ CUSTOM_SOURCE = 3
+}
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 0faad53..0f1cc8a 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -12,10 +12,12 @@
NavbarBrand,
NavbarToggler
} from '@sveltestrap/sveltestrap';
+ import { _ } from 'svelte-i18n';
import { page } from '$app/stores';
import { locale, locales, isLoading } from 'svelte-i18n';
- import ColorSchemeSwitcher from '../components/ColorSchemeSwitcher.svelte';
+ import { ColorSchemeSwitcher } from '$lib/components';
+ import { derived } from 'svelte/store';
export const setLocale = (lang: string) => () => {
locale.set(lang);
@@ -38,19 +40,20 @@
return flagMap[lowercaseCode];
} else {
// Return null for unsupported language codes
- return null;
+ return flagMap['en'];
}
};
let languageNames = {};
- locale.subscribe(() => {
- if ($locale) {
- let newLanguageNames = new Intl.DisplayNames([$locale], { type: 'language' });
+ const currentLocale = derived(locale, ($locale) => $locale || 'en');
- for (let l of $locales) {
- languageNames[l] = newLanguageNames.of(l);
- }
+ locale.subscribe(() => {
+ const localeToUse = $locale || 'en';
+ let newLanguageNames = new Intl.DisplayNames([localeToUse], { type: 'language' });
+
+ for (let l of $locales) {
+ languageNames[l] = newLanguageNames.of(l) || l;
}
});
@@ -61,8 +64,23 @@
};
-
- ₿TClock
+
+ ₿TClock
+
+
@@ -79,7 +97,10 @@
{#if !$isLoading}
- {getFlagEmoji($locale)} {languageNames[$locale]}
+ {getFlagEmoji($currentLocale)}
+ {languageNames[$currentLocale] || 'English'}
{#each $locales as locale}
-
+
+
+
diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts
index 4497634..3c7d853 100644
--- a/src/routes/+layout.ts
+++ b/src/routes/+layout.ts
@@ -6,10 +6,15 @@ import { locale, waitLocale } from 'svelte-i18n';
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async () => {
- if (browser && localStorage.getItem('locale')) {
- locale.set(localStorage.getItem('locale'));
- } else if (browser) {
- locale.set(window.navigator.language);
+ if (browser) {
+ if (localStorage.getItem('locale')) {
+ locale.set(localStorage.getItem('locale'));
+ } else {
+ // Normalize the browser locale
+ const browserLocale = window.navigator.language.split('-')[0].toLowerCase();
+ const supportedLocales = ['en', 'nl', 'es', 'de'];
+ locale.set(supportedLocales.includes(browserLocale) ? browserLocale : 'en');
+ }
}
await waitLocale();
};
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index b37ab13..5971a12 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -3,6 +3,7 @@
import { screenSize, updateScreenSize } from '$lib/screen';
import { Container, Row, Toast, ToastBody } from '@sveltestrap/sveltestrap';
+ import { replaceState } from '$app/navigation';
import { onMount } from 'svelte';
import { writable } from 'svelte/store';
@@ -12,16 +13,10 @@
import { uiSettings } from '$lib/uiSettings';
let settings = writable({
- fgColor: '0',
- bgColor: '0'
+ isLoaded: false,
+ timePerScreen: 0
});
- // let uiSettings = writable({
- // inputSize: 'sm',
- // selectClass: '',
- // btnSize: 'lg'
- // });
-
let status = writable({
data: ['L', 'O', 'A', 'D', 'I', 'N', 'G'],
espFreeHeap: 0,
@@ -30,48 +25,126 @@
price: false,
blocks: false
},
- leds: []
+ leds: [],
+ isUpdating: false
});
- const fetchStatusData = () => {
- fetch(`${PUBLIC_BASE_URL}/api/status`, { credentials: 'same-origin' })
- .then((res) => res.json())
- .then((data) => {
- status.set(data);
- });
+ const fetchStatusData = async () => {
+ const res = await fetch(`${PUBLIC_BASE_URL}/api/status`, { credentials: 'same-origin' });
+
+ if (!res.ok) {
+ console.error('Error fetching status data:', res.statusText);
+ return false;
+ }
+
+ const data = await res.json();
+ status.set(data);
+ return true;
};
- const fetchSettingsData = () => {
- fetch(PUBLIC_BASE_URL + `/api/settings`, { credentials: 'same-origin' })
- .then((res) => res.json())
- .then((data) => {
- data.fgColor = String(data.fgColor);
- data.bgColor = String(data.bgColor);
- data.timePerScreen = data.timerSeconds / 60;
+ const fetchSettingsData = async () => {
+ const res = await fetch(PUBLIC_BASE_URL + `/api/settings`, { credentials: 'same-origin' });
- if (data.fgColor > 65535) {
- data.fgColor = '65535';
- }
+ if (!res.ok) {
+ console.error('Error fetching settings data:', res.statusText);
+ return;
+ }
- if (data.bgColor > 65535) {
- data.bgColor = '65535';
- }
- settings.set(data);
- });
+ const data = await res.json();
+
+ data.fgColor = String(data.fgColor);
+ data.bgColor = String(data.bgColor);
+ data.timePerScreen = data.timerSeconds / 60;
+
+ if (data.fgColor > 65535) {
+ data.fgColor = '65535';
+ }
+
+ if (data.bgColor > 65535) {
+ data.bgColor = '65535';
+ }
+ settings.set(data);
};
- onMount(() => {
- fetchSettingsData();
- fetchStatusData();
+ let sections: (HTMLElement | null)[];
+ let observer: IntersectionObserver;
+ const SM_BREAKPOINT = 576;
- const evtSource = new EventSource(`${PUBLIC_BASE_URL}/events`);
+ const setupObserver = () => {
+ if (window.innerWidth < SM_BREAKPOINT) {
+ observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ const id = entry.target.id;
+ replaceState(`#${id}`);
- evtSource.addEventListener('status', (e) => {
- let dataObj = JSON.parse(e.data);
- status.set(dataObj);
- });
+ // Update nav pills
+ document.querySelectorAll('.nav-link').forEach((link) => {
+ link.classList.remove('active');
+ if (link.getAttribute('href') === `#${id}`) {
+ link.classList.add('active');
+ }
+ });
+ }
+ });
+ },
+ {
+ threshold: 0.25 // Trigger when section is 50% visible
+ }
+ );
+
+ sections = ['control', 'status', 'settings'].map((id) => document.getElementById(id));
+
+ sections.forEach((section) => observer.observe(section!));
+ }
+ };
+
+ onMount(async () => {
+ setupObserver();
+
+ const connectEventSource = () => {
+ const evtSource = new EventSource(`${PUBLIC_BASE_URL}/events`);
+
+ evtSource.addEventListener('status', (e) => {
+ let dataObj = JSON.parse(e.data);
+ status.update((s) => ({ ...s, isUpdating: true }));
+ status.set(dataObj);
+ });
+
+ evtSource.addEventListener('message', (e) => {
+ if (e.data == 'closing') {
+ console.log('EventSource closing');
+ status.update((s) => ({ ...s, isUpdating: false }));
+ evtSource.close(); // Close the current connection
+ setTimeout(connectEventSource, 5000);
+ }
+ });
+
+ evtSource.addEventListener('error', (e) => {
+ console.error('EventSource failed:', e);
+ status.update((s) => ({ ...s, isUpdating: false }));
+ evtSource.close(); // Close the current connection
+ setTimeout(connectEventSource, 1000);
+ });
+ };
+
+ try {
+ await fetchSettingsData();
+ if (await fetchStatusData()) {
+ settings.update((s) => ({ ...s, isLoaded: true }));
+ connectEventSource();
+ }
+ } catch (error) {
+ console.log('Error fetching data:', error);
+ }
function handleResize() {
+ if (observer) {
+ observer.disconnect();
+ }
+ setupObserver();
+
updateScreenSize();
}
@@ -123,9 +196,11 @@
-
+
+
+
diff --git a/src/routes/Control.svelte b/src/routes/Control.svelte
index d89ee10..fc3d76e 100644
--- a/src/routes/Control.svelte
+++ b/src/routes/Control.svelte
@@ -17,6 +17,7 @@
} from '@sveltestrap/sveltestrap';
import FirmwareUpdater from './FirmwareUpdater.svelte';
import { uiSettings } from '$lib/uiSettings';
+ import { Placeholder } from '$lib/components';
export let settings = {};
@@ -105,8 +106,8 @@
export let xxl = xl;
-
-
+
+
{$_('section.control.title', { default: 'Control' })}
@@ -214,15 +215,16 @@
{/if}
- {$_('section.control.buildTime')}: {new Date(
- $settings.lastBuildTime * 1000
- ).toLocaleString()}
+ {$_('section.control.buildTime')}:
- IP: {$settings.ip}
- HW revision: {$settings.hwRev}
- {$_('section.control.fwCommit')}: {$settings.gitRev}
- WebUI commit: {$settings.fsRev}
- {$_('section.control.hostname')}: {$settings.hostname}
+ IP:
+ HW revision:
+ {$_('section.control.fwCommit')}:
+ WebUI commit:
+ {$_('section.control.hostname')}:
@@ -239,7 +241,7 @@
{#if $settings.otaEnabled}
{$_('section.control.firmwareUpdate')}
-
+
{/if}
diff --git a/src/routes/FirmwareUpdater.svelte b/src/routes/FirmwareUpdater.svelte
index 2f467c4..06f4d0e 100644
--- a/src/routes/FirmwareUpdater.svelte
+++ b/src/routes/FirmwareUpdater.svelte
@@ -4,11 +4,12 @@
import { _ } from 'svelte-i18n';
import { writable } from 'svelte/store';
import { Progress, Alert, Button } from '@sveltestrap/sveltestrap';
+ import HourglassSplitIcon from 'svelte-bootstrap-icons/lib/HourglassSplit.svelte';
const dispatch = createEventDispatcher();
export let settings = { hwRev: '' };
-
+ export let status = writable({ isOTAUpdating: false });
let currentVersion: string = $settings.gitTag; // Replace with your current version
let latestVersion: string = '';
@@ -112,6 +113,25 @@
return binaryFilename;
};
+ const getWebUiBinaryName = () => {
+ let webuiFilename = '';
+ switch ($settings.hwRev) {
+ case 'REV_V8_EPD_2_13':
+ webuiFilename = 'littlefs_16MB.bin';
+ break;
+ case 'REV_B_EPD_2_13':
+ webuiFilename = 'littlefs_8MB.bin';
+ break;
+ case 'REV_A_EPD_2_13':
+ webuiFilename = 'littlefs_4MB.bin';
+ break;
+ default:
+ webuiFilename = 'Unsupported hardware, unable to determine WebUI binary filename';
+ }
+
+ return webuiFilename;
+ };
+
const onAutoUpdate = async (e: Event) => {
e.preventDefault();
@@ -188,8 +208,12 @@
)}: {releaseDate} -
{$_('section.firmwareUpdater.viewRelease')}
{#if isNewerVersionAvailable}
- {$_('section.firmwareUpdater.swUpdateAvailable')} -
- {$_('section.firmwareUpdater.autoUpdate')}.
+ {#if !$status.isOTAUpdating}
+ {$_('section.firmwareUpdater.swUpdateAvailable')} -
+ {$_('section.firmwareUpdater.autoUpdate')}.
+ {:else}
+ {$_('section.firmwareUpdater.autoUpdateInProgress')}
+ {/if}
{:else}
{$_('section.firmwareUpdater.swUpToDate')}
{/if}
@@ -199,57 +223,59 @@
{:else}
Loading...
{/if}
-
-
-
- handleFileChange(e, (file) => (firmwareUploadFile = file))}
- name="update"
- class="form-control"
- accept=".bin"
- />
-
-
-
+
+
+ handleFileChange(e, (file) => (firmwareUploadFile = file))}
+ name="update"
+ class="form-control"
+ accept=".bin"
+ />
+
+
+
+
+
+
+ handleFileChange(e, (file) => (firmwareWebUiFile = file))}
+ accept=".bin"
+ />
+
+
+
+
+
+ {#if firmwareUploadProgress > 0}
+
-
-
-
- handleFileChange(e, (file) => (firmwareWebUiFile = file))}
- accept=".bin"
- />
-
-
-
-
-
-{#if firmwareUploadProgress > 0}
-
-{/if}
-{#if firmwareUploadSuccess}
- {$_('section.firmwareUpdater.fileUploadSuccess', { values: { countdown: $countdown } })}
-
-{/if}
+ {/if}
+ {#if firmwareUploadSuccess}
+ {$_('section.firmwareUpdater.fileUploadSuccess', { values: { countdown: $countdown } })}
+
+ {/if}
-{#if firmwareUploadError}
- {$_('section.firmwareUpdater.fileUploadFailed')}{$_('section.firmwareUpdater.fileUploadFailed')}
+ {/if}
+ ⚠️ {$_('warning')}: {$_('section.firmwareUpdater.firmwareUpdateText')}
{/if}
-⚠️ {$_('warning')}: {$_('section.firmwareUpdater.firmwareUpdateText')}
diff --git a/src/routes/Rendered.svelte b/src/routes/Rendered.svelte
index 4c4167b..e4a1783 100644
--- a/src/routes/Rendered.svelte
+++ b/src/routes/Rendered.svelte
@@ -9,7 +9,7 @@
};
export let className = 'btclock-wrapper';
-
+ export let verticalDesc = false;
// Define the currency symbols as constants
const CURRENCY_USD = '$';
const CURRENCY_EUR = '[';
@@ -44,21 +44,22 @@
-
+
{#each status.data as char}
{#if isSplitText(char)}
- {#if char.split('/').length}
-
{char.split('/')[0]}
-
-
{char.split('/')[1]}
- {/if}
+
+ {#if char.split('/').length}
+ {char.split('/')[0]}
+ {char.split('/')[1]}
+ {/if}
+
{:else if char.startsWith('mdi')}
-
+
{#if char.endsWith('rocket')}
{/if}
@@ -68,6 +69,12 @@
{#if char.endsWith('bolt')}
{/if}
+ {#if char.endsWith('bitaxe')}
+

+ {/if}
+ {#if char.endsWith('miningpool')}
+
Mining Pool Logo
+ {/if}
{:else if char === 'STS'}
S
@@ -82,8 +89,26 @@
-
diff --git a/src/routes/Settings.svelte b/src/routes/Settings.svelte
index 14c5fb7..16de960 100644
--- a/src/routes/Settings.svelte
+++ b/src/routes/Settings.svelte
@@ -1,9 +1,6 @@
-
-
+
+
-
+
|
-
+
+
- {$_('section.settings.title', { default: 'Settings' })}
+ {$_('section.settings.title')}
-
+
+
+
+
+
+
+
+ {/if}
diff --git a/src/routes/Status.svelte b/src/routes/Status.svelte
index 1e76da6..a659af0 100644
--- a/src/routes/Status.svelte
+++ b/src/routes/Status.svelte
@@ -17,6 +17,7 @@
Row
} from '@sveltestrap/sveltestrap';
import Rendered from './Rendered.svelte';
+ import { DataSourceType } from '$lib/types/dataSource';
export let settings;
export let status: writable