diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml new file mode 100644 index 0000000..a1e59a8 --- /dev/null +++ b/.forgejo/workflows/build.yaml @@ -0,0 +1,121 @@ +on: [push] +jobs: + check-changes: + runs-on: docker + outputs: + all_changed_and_modified_files_count: ${{ steps.changed-files.outputs.all_changed_and_modified_files_count }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get changed files count + id: changed-files + uses: tj-actions/changed-files@v45 + with: + files_ignore: 'doc/**,README.md,Dockerfile,.*' + files_ignore_separator: ',' + - name: Print changed files count + run: > + echo "Changed files count: ${{ + steps.changed-files.outputs.all_changed_and_modified_files_count }}" + + build: + needs: check-changes + runs-on: docker + container: + image: ghcr.io/catthehacker/ubuntu:js-22.04 + if: ${{ needs.check-changes.outputs.all_changed_and_modified_files_count >= 1 }} + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: yarn + cache-dependency-path: '**/yarn.lock' + - uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + ~/node_modules + key: ${{ runner.os }}-pio + - uses: actions/setup-python@v5 + with: + python-version: '>=3.10' + - name: Get current date + id: dateAndTime + run: echo "dateAndTime=$(date +'%Y-%m-%d-%H:%M')" >> $GITHUB_OUTPUT + - name: Install mklittlefs + run: > + git clone https://github.com/earlephilhower/mklittlefs.git /tmp/mklittlefs && + cd /tmp/mklittlefs && + git submodule update --init && + make dist + - name: Install yarn + run: yarn && yarn postinstall + - name: Run linter + run: yarn lint + - name: Run vitest tests + run: yarn vitest run + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - name: Build WebUI + run: yarn build + - name: Get current block + 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 }} + run: mkdir -p output && echo "$BLOCK_HEIGHT" > output/version.txt + - name: gzip build for LittleFS + run: find dist -type f ! -name ".*" -exec sh -c 'mkdir -p "build_gz/$(dirname "${1#dist/}")" && gzip -k "$1" -c > "build_gz/${1#dist/}".gz' _ {} \; + - name: Write git rev to file + run: echo "$GITHUB_SHA" > build_gz/fs_hash.txt && echo "$GITHUB_SHA" > output/commit.txt + - name: Check GZipped directory size + run: | + # Set the threshold size in bytes + THRESHOLD=410000 + + # Calculate the total size of files in the directory + DIRECTORY_SIZE=$(du -b -s build_gz | awk '{print $1}') + + # Fail the workflow if the size exceeds the threshold + if [ "$DIRECTORY_SIZE" -gt "$THRESHOLD" ]; then + echo "Directory size exceeds the threshold of $THRESHOLD bytes" + exit 1 + else + echo "Directory size is within the threshold $DIRECTORY_SIZE" + fi + - name: Create tarball + 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 + uses: https://code.forgejo.org/forgejo/upload-artifact@v4 + with: + path: | + webui.tgz + 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 + with: + url: 'https://git.btclock.dev/' + repo: '${{ github.repository }}' + direction: upload + tag: ${{ steps.getBlockHeight.outputs.blockHeight }} + sha: '${{ github.sha }}' + release-dir: output + token: ${{ secrets.TOKEN }} + override: false + verbose: false + release-notes-assistant: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a5ca2ac..c44914e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,3 +10,6 @@ updates: schedule: interval: 'daily' versioning-strategy: 'increase-if-necessary' + ignore: + - dependency-name: '*' + update-types: ['version-update:semver-major'] diff --git a/README.md b/README.md index 805bfa1..1e9dd3c 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,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/package.json b/package.json index b84537e..44cc323 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "postinstall": "patch-package", "test": "npm run test:integration && npm run test:unit", "test:integration": "playwright test", + "test:screenshots": "playwright test -c playwright.screenshot.config.ts", "test:unit": "vitest" }, "devDependencies": { @@ -23,41 +24,43 @@ "@sveltejs/vite-plugin-svelte": "^3.0.1", "@testing-library/svelte": "^5.2.1", "@types/swagger-ui": "^3.52.4", - "@typescript-eslint/eslint-plugin": "^8.4.0", - "@typescript-eslint/parser": "^8.4.0", - "@vitest/ui": "^0.34.6", - "eslint": "^9.0.0", + "@typescript-eslint/eslint-plugin": "^8.7.0", + "@typescript-eslint/parser": "^8.7.0", + "@vitest/ui": "^2.0.5", + "eslint": "^9.11.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.36.0", "jsdom": "^25.0.0", - "prettier": "^3.1.1", - "prettier-plugin-svelte": "^3.1.2", - "sass": "^1.69.5", - "svelte": "^4.2.7", - "svelte-check": "^3.6.0", - "svelte-preprocess": "^5.1.1", - "tslib": "^2.4.1", + "prettier": "^3.3.3", + "prettier-plugin-svelte": "^3.2.6", + "sass": "^1.79.3", + "svelte": "^4.2.19", + "svelte-check": "^4.0.2", + "svelte-preprocess": "^6.0.2", + "tslib": "^2.7.0", "typescript": "^5.5.4", - "typescript-eslint": "^8.0.0", - "vite": "^5.4.2", - "vitest": "^2.0.5", + "typescript-eslint": "^8.7.0", + "vite": "^5.4.7", + "vitest": "^2.1.1", "vitest-github-actions-reporter": "^0.11.0" }, "type": "module", "dependencies": { - "@fontsource/antonio": "^5.0.17", - "@fontsource/oswald": "^5.0.17", - "@fontsource/ubuntu": "^5.0.8", + "@fontsource/antonio": "^5.1.0", + "@fontsource/oswald": "^5.1.0", + "@fontsource/ubuntu": "^5.1.0", "@noble/secp256k1": "^2.1.0", "@playwright/test": "^1.46.0", + "@popperjs/core": "^2.11.8", "@sveltestrap/sveltestrap": "^6.2.7", "@testing-library/jest-dom": "^6.5.0", - "bootstrap": "^5.3.2", + "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", + "msgpack-es": "^0.0.5", "nostr-tools": "^2.7.1", "patch-package": "^8.0.0", - "svelte-i18n": "^4.0.0", - "swagger-ui": "^5.10.0" + "svelte-bootstrap-icons": "^3.1.1", + "svelte-i18n": "^4.0.0" }, "resolutions": { "es5-ext": ">=0.10.64", diff --git a/patches/@sveltejs+kit+2.5.25.patch b/patches/@sveltejs+kit+2.7.5.patch similarity index 90% rename from patches/@sveltejs+kit+2.5.25.patch rename to patches/@sveltejs+kit+2.7.5.patch index 190b51f..e25e1e5 100644 --- a/patches/@sveltejs+kit+2.5.25.patch +++ b/patches/@sveltejs+kit+2.7.5.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/@sveltejs/kit/src/exports/vite/index.js b/node_modules/@sveltejs/kit/src/exports/vite/index.js -index 2280025..cfbcfa9 100644 +index 40fa4c6..738cabf 100644 --- a/node_modules/@sveltejs/kit/src/exports/vite/index.js +++ b/node_modules/@sveltejs/kit/src/exports/vite/index.js -@@ -621,9 +621,9 @@ async function kit({ svelte_config }) { +@@ -655,9 +655,9 @@ async function kit({ svelte_config }) { input, output: { format: 'esm', diff --git a/patches/@sveltejs+kit+2.8.5.patch b/patches/@sveltejs+kit+2.8.5.patch new file mode 100644 index 0000000..80f5dba --- /dev/null +++ b/patches/@sveltejs+kit+2.8.5.patch @@ -0,0 +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 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..bf148ce 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -10,7 +10,7 @@ const config: PlaywrightTestConfig = { port: 4173 }, reporter: process.env.CI ? 'github' : 'list', - testDir: 'tests', + testDir: 'tests/playwright', testMatch: /(.+\.)?(test|spec)\.[jt]s/ }; diff --git a/playwright.screenshot.config.ts b/playwright.screenshot.config.ts new file mode 100644 index 0000000..81264a4 --- /dev/null +++ b/playwright.screenshot.config.ts @@ -0,0 +1,51 @@ +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', + use: { + viewport: { width: 1512, height: 982 } + } + }, + { + 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 } } + } + ] +}); diff --git a/src/components/ColorSchemeSwitcher.svelte b/src/components/ColorSchemeSwitcher.svelte new file mode 100644 index 0000000..9d7e32a --- /dev/null +++ b/src/components/ColorSchemeSwitcher.svelte @@ -0,0 +1,53 @@ + + + + + {theme === 'auto' ? '🌗' : theme === 'dark' ? '🌙' : '☀️'} + + + setTheme('light')} + >☀️ Light + setTheme('dark')}>🌙 Dark + setTheme('auto')}>🌗 Auto + + diff --git a/src/components/ToggleHeader.svelte b/src/components/ToggleHeader.svelte new file mode 100644 index 0000000..acc8d3b --- /dev/null +++ b/src/components/ToggleHeader.svelte @@ -0,0 +1,28 @@ + + +

+ (isOpen = !isOpen)} + tabindex="0" + on:keypress={() => (isOpen = !isOpen)} + > + {#if isOpen} + + {:else} + + {/if} + {header} + +

+ + + diff --git a/src/icons/EyeIcon.svelte b/src/icons/EyeIcon.svelte deleted file mode 100644 index dd7513a..0000000 --- a/src/icons/EyeIcon.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/src/icons/EyeSlashIcon.svelte b/src/icons/EyeSlashIcon.svelte deleted file mode 100644 index bf45888..0000000 --- a/src/icons/EyeSlashIcon.svelte +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/src/lib/locales/de.json b/src/lib/locales/de.json index 9748996..e9756ff 100644 --- a/src/lib/locales/de.json +++ b/src/lib/locales/de.json @@ -42,7 +42,23 @@ "httpAuthUser": "WebUI-Benutzername", "httpAuthPass": "WebUI-Passwort", "httpAuthText": "Schützt nur die WebUI mit einem Passwort, nicht API-Aufrufe.", - "currencies": "Währungen" + "currencies": "Währungen", + "mowMode": "Mow suffixmodus", + "suffixShareDot": "Kompakte Suffix-Notation", + "section": { + "displaysAndLed": "Anzeigen und LEDs", + "screenSettings": "Infospezifisch", + "dataSource": "Datenquelle", + "extraFeatures": "Zusätzliche Funktionen", + "system": "System" + }, + "ledFlashOnZap": "LED blinkt bei Nostr Zap", + "flFlashOnZap": "Displaybeleuchting bei Nostr Zap", + "showAll": "Alle anzeigen", + "hideAll": "Alles ausblenden", + "flOffWhenDark": "Displaybeleuchtung aus, wenn es dunkel ist", + "luxLightToggleText": "Zum Deaktivieren auf 0 setzen", + "verticalDesc": "Vrtikale Bildschirmbeschreibung" }, "control": { "systemInfo": "Systeminfo", @@ -81,7 +97,8 @@ "swUpdateAvailable": "Eine neuere Version ist verfügbar!", "latestVersion": "Letzte Version", "releaseDate": "Veröffentlichungsdatum", - "viewRelease": "Veröffentlichung anzeigen" + "viewRelease": "Veröffentlichung anzeigen", + "autoUpdate": "Update installieren (experimentell)" } }, "colors": { diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json index c518200..f08f40b 100644 --- a/src/lib/locales/en.json +++ b/src/lib/locales/en.json @@ -41,9 +41,11 @@ "nostrZapKey": "Nostr zap pubkey", "nostrRelay": "Nostr Relay", "nostrZapNotify": "Nostr Zap Notifications", - "useNostr": "Use Nostr datasource", + "useNostr": "Use Nostr data source", "bitaxeHostname": "BitAxe hostname or IP", "bitaxeEnabled": "Enable BitAxe", + "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", @@ -52,7 +54,25 @@ "httpAuthUser": "WebUI Username", "httpAuthPass": "WebUI Password", "httpAuthText": "Only password-protects WebUI, not API-calls.", - "currencies": "Currencies" + "currencies": "Currencies", + "stagingSource": "Use Staging data source (for development)", + "useNostrTooltip": "Very experimental and unstable. Nostr data source is not required for Nostr Zap notifications.", + "mowMode": "Mow Suffix Mode", + "suffixShareDot": "Suffix compact notation", + "section": { + "displaysAndLed": "Displays and LEDs", + "screenSettings": "Screen specific", + "dataSource": "Data source", + "extraFeatures": "Extra features", + "system": "System" + }, + "ledFlashOnZap": "LED flash on Nostr Zap", + "flFlashOnZap": "Frontlight flash on Nostr Zap", + "showAll": "Show all", + "hideAll": "Hide all", + "flOffWhenDark": "Frontlight off when dark", + "luxLightToggleText": "Set to 0 to disable", + "verticalDesc": "Use vertical screen description" }, "control": { "systemInfo": "System info", @@ -93,7 +113,8 @@ "swUpToDate": "You are up to date.", "latestVersion": "Latest Version", "releaseDate": "Release Date", - "viewRelease": "View Release" + "viewRelease": "View Release", + "autoUpdate": "Install update (experimental)" } }, "colors": { diff --git a/src/lib/locales/es.json b/src/lib/locales/es.json index 06bf3fa..79b6673 100644 --- a/src/lib/locales/es.json +++ b/src/lib/locales/es.json @@ -41,7 +41,23 @@ "httpAuthUser": "Nombre de usuario WebUI", "httpAuthPass": "Contraseña WebUI", "httpAuthText": "Solo la WebUI está protegida con contraseña, no las llamadas API.", - "currencies": "Monedas" + "currencies": "Monedas", + "mowMode": "Modo de sufijo Mow", + "suffixShareDot": "Notación compacta de sufijo", + "section": { + "displaysAndLed": "Pantallas y LED", + "screenSettings": "Específico de la pantalla", + "dataSource": "fuente de datos", + "extraFeatures": "Funciones adicionales", + "system": "Sistema" + }, + "ledFlashOnZap": "LED parpadeante con Nostr Zap", + "flFlashOnZap": "Flash de luz frontal con Nostr Zap", + "showAll": "Mostrar todo", + "hideAll": "Ocultar todo", + "flOffWhenDark": "Luz de la pantalla cuando está oscuro", + "luxLightToggleText": "Establecer en 0 para desactivar", + "verticalDesc": "Descripción de pantalla vertical" }, "control": { "turnOff": "Apagar", @@ -80,7 +96,8 @@ "swUpdateAvailable": "¡Una nueva versión está disponible!", "latestVersion": "Ultima versión", "releaseDate": "Fecha de lanzamiento", - "viewRelease": "Ver lanzamiento" + "viewRelease": "Ver lanzamiento", + "autoUpdate": "Instalar actualización (experimental)" } }, "button": { diff --git a/src/lib/locales/nl.json b/src/lib/locales/nl.json index 2bc7d80..eb5b5af 100644 --- a/src/lib/locales/nl.json +++ b/src/lib/locales/nl.json @@ -42,7 +42,23 @@ "httpAuthUser": "WebUI-gebruikersnaam", "httpAuthPass": "WebUI-wachtwoord", "httpAuthText": "Beveiligd enkel WebUI, niet de API.", - "currencies": "Valuta's" + "currencies": "Valuta's", + "mowMode": "Mow achtervoegsel", + "suffixShareDot": "Achtervoegsel compacte notatie", + "section": { + "displaysAndLed": "Displays en LED's", + "screenSettings": "Schermspecifiek", + "dataSource": "Gegevensbron", + "extraFeatures": "Extra functies", + "system": "Systeem" + }, + "ledFlashOnZap": "Knipper LED bij Nostr Zap", + "flFlashOnZap": "Knipper displaylicht bij Nostr Zap", + "showAll": "Toon alles", + "hideAll": "Alles verbergen", + "flOffWhenDark": "Displaylicht uit als het donker is", + "luxLightToggleText": "Stel in op 0 om uit te schakelen", + "verticalDesc": "Verticale schermbeschrijving" }, "control": { "systemInfo": "Systeeminformatie", @@ -80,7 +96,8 @@ "swUpdateAvailable": "Een nieuwere versie is beschikbaar!", "latestVersion": "Laatste versie", "releaseDate": "Datum van publicatie", - "viewRelease": "Bekijk publicatie" + "viewRelease": "Bekijk publicatie", + "autoUpdate": "Update installeren (experimenteel)" } }, "colors": { diff --git a/src/lib/style/app.scss b/src/lib/style/app.scss index ef598ac..53eeee8 100644 --- a/src/lib/style/app.scss +++ b/src/lib/style/app.scss @@ -1,6 +1,4 @@ @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'; @@ -9,11 +7,13 @@ @import './satsymbol'; -$color-mode-type: media-query; +$color-mode-type: data; $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'; @@ -28,7 +28,7 @@ $input-font-size-sm: $font-size-base * 0.875; @import '../node_modules/bootstrap/scss/forms'; @import '../node_modules/bootstrap/scss/buttons'; @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'; @@ -43,6 +43,40 @@ $input-font-size-sm: $font-size-base * 0.875; @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 +87,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 +114,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 +155,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 { @@ -228,3 +285,21 @@ nav { #firmwareUploadProgress { @extend .my-2; } + +.sats { + font-family: 'Satoshi Symbol'; +} + +.currencyCode { + width: 20%; + text-align: center; + display: inline-block; +} + +input[type='number'] { + text-align: right; +} + +.lightMode .bitaxelogo { + filter: brightness(0) saturate(100%); +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index ef187a5..e421f5a 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -9,11 +9,14 @@ NavItem, NavLink, Navbar, - NavbarBrand + 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'; export const setLocale = (lang: string) => () => { locale.set(lang); @@ -36,7 +39,7 @@ return flagMap[lowercaseCode]; } else { // Return null for unsupported language codes - return null; + return flagMap['en']; } }; @@ -51,21 +54,47 @@ } } }); + + let isOpen = false; + + const toggle = () => { + isOpen = !isOpen; + }; - - ₿TClock - + + ₿TClock + + + + + {#if !$isLoading} - + {getFlagEmoji($locale)} {languageNames[$locale]} {#each $locales as locale} @@ -76,8 +105,11 @@ {/if} + - +
+ +
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 4100d86..c2e4e3b 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,15 +13,10 @@ import { uiSettings } from '$lib/uiSettings'; let settings = writable({ - fgColor: '0' + fgColor: '0', + bgColor: '0' }); - // let uiSettings = writable({ - // inputSize: 'sm', - // selectClass: '', - // btnSize: 'lg' - // }); - let status = writable({ data: ['L', 'O', 'A', 'D', 'I', 'N', 'G'], espFreeHeap: 0, @@ -59,7 +55,43 @@ }); }; + let sections: (HTMLElement | null)[]; + let observer: IntersectionObserver; + const SM_BREAKPOINT = 576; + + const setupObserver = () => { + if (window.innerWidth < SM_BREAKPOINT) { + observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const id = entry.target.id; + replaceState(`#${id}`); + + // 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(() => { + setupObserver(); + fetchSettingsData(); fetchStatusData(); @@ -71,6 +103,11 @@ }); function handleResize() { + if (observer) { + observer.disconnect(); + } + setupObserver(); + updateScreenSize(); } @@ -123,8 +160,10 @@ - + + + diff --git a/src/routes/Control.svelte b/src/routes/Control.svelte index b62c7ed..25f5a44 100644 --- a/src/routes/Control.svelte +++ b/src/routes/Control.svelte @@ -105,8 +105,8 @@ export let xxl = xl; - - + + {$_('section.control.title', { default: 'Control' })} @@ -239,7 +239,7 @@ {#if $settings.otaEnabled}

{$_('section.control.firmwareUpdate')}

- + {/if}
diff --git a/src/routes/FirmwareUpdater.svelte b/src/routes/FirmwareUpdater.svelte index 6c64667..a9a3229 100644 --- a/src/routes/FirmwareUpdater.svelte +++ b/src/routes/FirmwareUpdater.svelte @@ -1,10 +1,12 @@
-
+
{#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,9 @@ {#if char.endsWith('bolt')} {/if} + {#if char.endsWith('bitaxe')} + + {/if}
{:else if char === 'STS'}
S
@@ -82,8 +86,22 @@
- diff --git a/src/routes/Settings.svelte b/src/routes/Settings.svelte index 78f1fe1..43964f3 100644 --- a/src/routes/Settings.svelte +++ b/src/routes/Settings.svelte @@ -18,10 +18,15 @@ InputGroup, InputGroupText, Label, + Tooltip, Row } from '@sveltestrap/sveltestrap'; - import EyeIcon from '../icons/EyeIcon.svelte'; - import EyeSlashIcon from '../icons/EyeSlashIcon.svelte'; + + import EyeIcon from 'svelte-bootstrap-icons/lib/Eye.svelte'; + import EyeSlashIcon from 'svelte-bootstrap-icons/lib/EyeSlash.svelte'; + + import { derived } from 'svelte/store'; + import ToggleHeader from '../components/ToggleHeader.svelte'; export let settings; @@ -160,637 +165,837 @@ let showPassword = false; + let textColor = '0'; + const colorStore = derived(settings, ($settings) => ({ + fgColor: $settings.fgColor, + bgColor: $settings.bgColor + })); + + // $: { + // if ($colorStore) { + // console.log('Settings model changed:', $colorStore); + // if ($colorStore.fgColor < $colorStore.bgColor) + // textColor = "0"; + // else + // textColor = "1"; // 65535 + // } + // } + + colorStore.subscribe(() => { + if ($colorStore) { + if ($colorStore.fgColor < $colorStore.bgColor) textColor = '0'; + else textColor = '1'; // 65535 + } + }); + + const setTextColor = () => { + console.log(textColor); + if (textColor == '1') { + $settings.fgColor = 65535; + $settings.bgColor = 0; + } else { + $settings.fgColor = 0; + $settings.bgColor = 65535; + } + }; + + const showAll = (show: boolean) => { + screenSettingsIsOpen = show; + displaysAndLedIsOpen = show; + dataSourceIsOpen = show; + extraFeaturesIsOpen = show; + systemIsOpen = show; + }; + export let xs = 12; export let sm = xs; export let md = sm; export let lg = md; export let xl = lg; export let xxl = xl; + + let screenSettingsIsOpen: boolean, + displaysAndLedIsOpen: boolean, + dataSourceIsOpen: boolean, + extraFeaturesIsOpen: boolean, + systemIsOpen: boolean; - - + + +
+ + | + +
{$_('section.settings.title', { default: 'Settings' })}
-
+ - - - - - - - - - - - - - - - - - - - - - - - {$_('time.minutes')} - - - - - - - - - {$_('time.minutes')} - - - - - - - - - {$_('time.seconds')} - - {$_('section.settings.shortAmountsWarning')} - - - - - - - - {$_('time.minutes')} - - - {$_('section.settings.tzOffsetHelpText')} - - - - - - - - - {#if $settings.hasFrontlight && !$settings.flDisable} - - - - - - - - - - - - - {/if} - {#if $settings.hasLightLevel} - - - - - - - {/if} - {#if $settings.bitaxeEnabled} - - - - - - - - - - {/if} - {#if 'nostrZapNotify' in $settings && $settings['nostrZapNotify']} - - - - checkValidNostrPubkey('nostrZapPubkey')} - invalid={!isValidHexPubKey($settings.nostrZapPubkey)} - bsSize={$uiSettings.inputSize} - required - minlength="64" - > - {#if !isValidHexPubKey($settings.nostrZapPubkey)} - {$_('section.settings.invalidNostrPubkey')} - {/if} - - - {/if} - {#if $settings.useNostr} - - - - checkValidNostrPubkey('nostrPubKey')} - invalid={!isValidHexPubKey($settings.nostrPubKey)} - bsSize={$uiSettings.inputSize} - > - {#if !isValidHexPubKey($settings.nostrPubKey)} - {$_('section.settings.invalidNostrPubkey')} - {/if} - - - {/if} - {#if 'nostrZapNotify' in $settings || $settings.useNostr} - - - - - - - - - - {/if} - - - - - - - - HTTPS - - - {$_('section.settings.mempoolInstanceHelpText')} - - - {#if $settings.httpAuthEnabled} - - - - - - - - - - - - - - {$_('section.settings.httpAuthText')} - - - {/if} - - - - - - - - - - - {#each wifiTxPowerMap as [key, value]} - - {/each} - - {$_('section.settings.wifiTxPowerText')} - - - - - - - - {$_('time.seconds')} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {#if $settings.hasFrontlight} - - - - {/if} - {#if $settings.hasFrontlight && !$settings.flDisable} - - - - - - - {/if} - - - - - - - {#if $settings.nostrRelay} - - - - {/if} - {#if 'nostrZapNotify' in $settings} - - - - {/if} - {#if 'bitaxeEnabled' in $settings} - - - - {/if} - - - - - - - - - - - - -

{$_('section.settings.screens')}

- {#if $settings.screens} - {#each $settings.screens as s} + - {/each} - {/if} - - {#if $settings.actCurrencies} - -

{$_('section.settings.currencies')}

- {$_('restartRequired')} - {#if $settings.availableCurrencies} - {#each $settings.availableCurrencies as c} + + + + + + + + + + + + + + + + + + + + + + + + {#if !$settings.actCurrencies} -
- - -
+ - {/each} + {/if} +
+ +
{$_('section.settings.screens')}
+ {#if $settings.screens} + {#each $settings.screens as s} + + + + {/each} + {/if} +
+ {#if $settings.actCurrencies && $settings.useNostr !== true} + +
{$_('section.settings.currencies')}
+ {$_('restartRequired')} + {#if $settings.availableCurrencies} + {#each $settings.availableCurrencies as c} + +
+ + +
+ + {/each} + {/if} +
{/if} -
- {/if} + + + + + + + + + + + + + + + + + + + {$_('time.minutes')} + + + + + + + + + {$_('time.minutes')} + + + + + + + + + {$_('time.seconds')} + + {$_('section.settings.shortAmountsWarning')} + + + + + + + + + {#if $settings.hasFrontlight && !$settings.flDisable} + + + + + + + + + + + + + {/if} + {#if !$settings.flDisable && $settings.hasLightLevel} + + + + + {$_('section.settings.luxLightToggleText')} + + + {/if} + + + + + + + + + + + {#if $settings.hasFrontlight} + + + + {/if} + {#if $settings.hasFrontlight && !$settings.flDisable} + + + + + + + + + + {/if} + + + + + + + + + + + + HTTPS + + + {$_('section.settings.mempoolInstanceHelpText')} + + + + + + + {#if $settings.nostrRelay} + + + + {$_('section.settings.useNostrTooltip')} + + + {/if} + {#if 'stagingSource' in $settings} + + + + {/if} + + + + + {#if $settings.bitaxeEnabled} + + + + + + + + + + {/if} + {#if $settings.miningPoolStatsEnabled} + + + + + + + + + + + + + {/if} + {#if 'nostrZapNotify' in $settings && $settings['nostrZapNotify']} + + + + checkValidNostrPubkey('nostrZapPubkey')} + invalid={!isValidHexPubKey($settings.nostrZapPubkey)} + bsSize={$uiSettings.inputSize} + required + minlength="64" + > + {#if !isValidHexPubKey($settings.nostrZapPubkey)} + {$_('section.settings.invalidNostrPubkey')} + {/if} + + + {/if} + {#if $settings.useNostr} + + + + checkValidNostrPubkey('nostrPubKey')} + invalid={!isValidHexPubKey($settings.nostrPubKey)} + bsSize={$uiSettings.inputSize} + > + {#if !isValidHexPubKey($settings.nostrPubKey)} + {$_('section.settings.invalidNostrPubkey')} + {/if} + + + {/if} + {#if 'nostrZapNotify' in $settings || $settings.useNostr} + + + + + + + + + + {/if} + + {#if 'bitaxeEnabled' in $settings} + + + + {/if} + {#if 'nostrZapNotify' in $settings} + + + + + + + {#if $settings.hasFrontlight && !$settings.flDisable} + + + + {/if} + {/if} + + + + + + + + + + {$_('time.minutes')} + + + {$_('section.settings.tzOffsetHelpText')} + + + {#if $settings.httpAuthEnabled} + + + + + + + + + + + + + + {$_('section.settings.httpAuthText')} + + + {/if} + + + + + + + + + + + {#each wifiTxPowerMap as [key, value]} + + {/each} + + {$_('section.settings.wifiTxPowerText')} + + + + + + + + {$_('time.seconds')} + + + + + + + + + + + + + + + + + diff --git a/src/routes/Status.svelte b/src/routes/Status.svelte index 1323d56..812ee68 100644 --- a/src/routes/Status.svelte +++ b/src/routes/Status.svelte @@ -104,8 +104,8 @@ export let xxl = xl; - - + + {$_('section.status.title', { default: 'Status' })} @@ -135,8 +135,8 @@ {/each}
- {#if $settings.actCurrencies} -
+ {#if $settings.actCurrencies && $settings.ownDataSource} +
{#each $settings.actCurrencies as c}