Compare commits
24 commits
Author | SHA1 | Date | |
---|---|---|---|
|
924be8fc2e | ||
|
23529dbd4b | ||
|
20c81628f1 | ||
|
25258b43a7 | ||
|
8a9c013f24 | ||
|
cefa98148a | ||
|
551d714cce | ||
|
fd328d4f05 | ||
|
dfe703d676 | ||
|
a00eb54573 | ||
711c625648 | |||
f458417536 | |||
0c70c74a1a | |||
2bea761d3c | |||
|
85b9b17506 | ||
|
eff18ba0c3 | ||
|
266a99be96 | ||
|
653a39d0a3 | ||
|
68c247f3cc | ||
|
25e91b2086 | ||
|
f0fa58b5ea | ||
|
b8ed628bf5 | ||
|
00af5f6521 | ||
|
51cce2ee9f |
37 changed files with 1583 additions and 1184 deletions
|
@ -81,7 +81,7 @@ jobs:
|
||||||
- name: Check GZipped directory size
|
- name: Check GZipped directory size
|
||||||
run: |
|
run: |
|
||||||
# Set the threshold size in bytes
|
# Set the threshold size in bytes
|
||||||
THRESHOLD=419840
|
THRESHOLD=410000
|
||||||
|
|
||||||
# Calculate the total size of files in the directory
|
# Calculate the total size of files in the directory
|
||||||
DIRECTORY_SIZE=$(du -b -s build_gz | awk '{print $1}')
|
DIRECTORY_SIZE=$(du -b -s build_gz | awk '{print $1}')
|
||||||
|
@ -98,7 +98,7 @@ jobs:
|
||||||
- name: Build LittleFS
|
- name: Build LittleFS
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
/tmp/mklittlefs/mklittlefs -c build_gz -s 419840 output/littlefs.bin
|
/tmp/mklittlefs/mklittlefs -c build_gz -s 410000 output/littlefs.bin
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
|
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -30,7 +30,11 @@ Make sure the postinstall script is ran, because otherwise the filenames are to
|
||||||
|
|
||||||
## Deploying
|
## 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:
|
Then you can make a `LittleFS.bin` with mklittlefs:
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
"test": "npm run test:integration && npm run test:unit",
|
"test": "npm run test:integration && npm run test:unit",
|
||||||
"test:integration": "playwright test",
|
"test:integration": "playwright test",
|
||||||
|
"test:screenshots": "playwright test -c playwright.screenshot.config.ts",
|
||||||
"test:unit": "vitest"
|
"test:unit": "vitest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
"jsdom": "^25.0.0",
|
"jsdom": "^25.0.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prettier-plugin-svelte": "^3.2.6",
|
"prettier-plugin-svelte": "^3.2.6",
|
||||||
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
"sass": "^1.79.3",
|
"sass": "^1.79.3",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^4.2.19",
|
||||||
"svelte-check": "^4.0.2",
|
"svelte-check": "^4.0.2",
|
||||||
|
|
|
@ -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
|
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 ad519c9..bee1516 100644
|
||||||
--- a/node_modules/@sveltejs/kit/src/exports/vite/index.js
|
--- a/node_modules/@sveltejs/kit/src/exports/vite/index.js
|
||||||
+++ b/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 }) {
|
@@ -644,9 +644,9 @@ async function kit({ svelte_config }) {
|
||||||
input,
|
input,
|
||||||
output: {
|
output: {
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
|
@ -13,5 +13,18 @@ index 40fa4c6..738cabf 100644
|
||||||
+ chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[hash].${ext}`,
|
+ chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[hash].${ext}`,
|
||||||
+ assetFileNames: `${prefix}/assets/[hash][extname]`,
|
+ assetFileNames: `${prefix}/assets/[hash][extname]`,
|
||||||
hoistTransitiveImports: false,
|
hoistTransitiveImports: false,
|
||||||
sourcemapIgnoreList
|
sourcemapIgnoreList,
|
||||||
},
|
manualChunks:
|
||||||
|
@@ -661,9 +661,9 @@ async function kit({ svelte_config }) {
|
||||||
|
worker: {
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
- entryFileNames: `${prefix}/workers/[name]-[hash].js`,
|
||||||
|
- chunkFileNames: `${prefix}/workers/chunks/[name]-[hash].js`,
|
||||||
|
- assetFileNames: `${prefix}/workers/assets/[name]-[hash][extname]`,
|
||||||
|
+ entryFileNames: `${prefix}/workers/[hash].js`,
|
||||||
|
+ chunkFileNames: `${prefix}/workers/chunks/[hash].js`,
|
||||||
|
+ assetFileNames: `${prefix}/workers/assets/[hash][extname]`,
|
||||||
|
hoistTransitiveImports: false
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ const config: PlaywrightTestConfig = {
|
||||||
port: 4173
|
port: 4173
|
||||||
},
|
},
|
||||||
reporter: process.env.CI ? 'github' : 'list',
|
reporter: process.env.CI ? 'github' : 'list',
|
||||||
testDir: 'tests',
|
testDir: 'tests/playwright',
|
||||||
testMatch: /(.+\.)?(test|spec)\.[jt]s/
|
testMatch: /(.+\.)?(test|spec)\.[jt]s/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
59
playwright.screenshot.config.ts
Normal file
59
playwright.screenshot.config.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
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 } }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -1,13 +0,0 @@
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="bi bi-eye"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8M1.173 8a13 13 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5s3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5s-3.879-1.168-5.168-2.457A13 13 0 0 1 1.172 8z"
|
|
||||||
/>
|
|
||||||
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 530 B |
|
@ -1,18 +0,0 @@
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="bi bi-eye-slash"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M13.359 11.238C15.06 9.72 16 8 16 8s-3-5.5-8-5.5a7 7 0 0 0-2.79.588l.77.771A6 6 0 0 1 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755q-.247.248-.517.486z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M11.297 9.176a3.5 3.5 0 0 0-4.474-4.474l.823.823a2.5 2.5 0 0 1 2.829 2.829zm-2.943 1.299.822.822a3.5 3.5 0 0 1-4.474-4.474l.823.823a2.5 2.5 0 0 0 2.829 2.829"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M3.35 5.47q-.27.24-.518.487A13 13 0 0 0 1.172 8l.195.288c.335.48.83 1.12 1.465 1.755C4.121 11.332 5.881 12.5 8 12.5c.716 0 1.39-.133 2.02-.36l.77.772A7 7 0 0 1 8 13.5C3 13.5 0 8 0 8s.939-1.721 2.641-3.238l.708.709zm10.296 8.884-12-12 .708-.708 12 12z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 814 B |
53
src/lib/components/ColorSchemeSwitcher.svelte
Normal file
53
src/lib/components/ColorSchemeSwitcher.svelte
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from '@sveltestrap/sveltestrap';
|
||||||
|
|
||||||
|
type Theme = 'light' | 'dark' | 'auto';
|
||||||
|
|
||||||
|
let theme: Theme = 'auto';
|
||||||
|
|
||||||
|
// Set the theme based on user selection and store it in localStorage
|
||||||
|
function setTheme(newTheme: Theme) {
|
||||||
|
theme = newTheme;
|
||||||
|
localStorage.setItem('theme', newTheme);
|
||||||
|
applyTheme(newTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the selected theme to the document
|
||||||
|
function applyTheme(selectedTheme: Theme) {
|
||||||
|
if (selectedTheme === 'auto') {
|
||||||
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
document.documentElement.setAttribute('data-bs-theme', prefersDark ? 'dark' : 'light');
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute('data-bs-theme', selectedTheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On component mount, check localStorage and apply the saved theme
|
||||||
|
onMount(() => {
|
||||||
|
const savedTheme = (localStorage.getItem('theme') as Theme) || 'auto';
|
||||||
|
applyTheme(savedTheme);
|
||||||
|
theme = savedTheme;
|
||||||
|
|
||||||
|
// Listen for changes in the system color scheme preference
|
||||||
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
mediaQuery.addEventListener('change', () => {
|
||||||
|
if (theme === 'auto') {
|
||||||
|
applyTheme('auto');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dropdown inNavbar>
|
||||||
|
<DropdownToggle nav caret>
|
||||||
|
{theme === 'auto' ? '🌗' : theme === 'dark' ? '🌙' : '☀️'}
|
||||||
|
</DropdownToggle>
|
||||||
|
<DropdownMenu end>
|
||||||
|
<DropdownItem active={theme === 'light'} on:click={() => setTheme('light')}
|
||||||
|
>☀️ Light</DropdownItem
|
||||||
|
>
|
||||||
|
<DropdownItem active={theme === 'dark'} on:click={() => setTheme('dark')}>🌙 Dark</DropdownItem>
|
||||||
|
<DropdownItem active={theme === 'auto'} on:click={() => setTheme('auto')}>🌗 Auto</DropdownItem>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
58
src/lib/components/SettingsInput.svelte
Normal file
58
src/lib/components/SettingsInput.svelte
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputGroupText,
|
||||||
|
Label,
|
||||||
|
FormText,
|
||||||
|
Col,
|
||||||
|
Row
|
||||||
|
} from '@sveltestrap/sveltestrap';
|
||||||
|
|
||||||
|
export let id: string;
|
||||||
|
export let label: string;
|
||||||
|
export let value: string | number;
|
||||||
|
export let type: string = 'text';
|
||||||
|
export let size: string = 'sm';
|
||||||
|
export let required: boolean = false;
|
||||||
|
export let min: number | undefined = undefined;
|
||||||
|
export let max: number | undefined = undefined;
|
||||||
|
export let step: number | string | undefined = undefined;
|
||||||
|
export let suffix: string | undefined = undefined;
|
||||||
|
export let helpText: string | undefined = undefined;
|
||||||
|
export let disabled: boolean = false;
|
||||||
|
export let valid: boolean | undefined = undefined;
|
||||||
|
export let invalid: boolean | undefined = undefined;
|
||||||
|
export let minlength: string | undefined = undefined;
|
||||||
|
export let onChange: (() => void) | undefined = undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Label md={6} for={id} {size}>{label}</Label>
|
||||||
|
<Col md="6">
|
||||||
|
<InputGroup {size}>
|
||||||
|
<Input
|
||||||
|
{id}
|
||||||
|
{type}
|
||||||
|
bind:value
|
||||||
|
{required}
|
||||||
|
{min}
|
||||||
|
{max}
|
||||||
|
{step}
|
||||||
|
{disabled}
|
||||||
|
{valid}
|
||||||
|
{invalid}
|
||||||
|
{minlength}
|
||||||
|
bsSize={size}
|
||||||
|
on:change={onChange}
|
||||||
|
/>
|
||||||
|
{#if suffix}
|
||||||
|
<InputGroupText>{suffix}</InputGroupText>
|
||||||
|
{/if}
|
||||||
|
<slot />
|
||||||
|
</InputGroup>
|
||||||
|
{#if helpText}
|
||||||
|
<FormText>{helpText}</FormText>
|
||||||
|
{/if}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
34
src/lib/components/SettingsSelect.svelte
Normal file
34
src/lib/components/SettingsSelect.svelte
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Input, Label, FormText, Col, Row } from '@sveltestrap/sveltestrap';
|
||||||
|
|
||||||
|
export let id: string;
|
||||||
|
export let label: string;
|
||||||
|
export let value: string | number;
|
||||||
|
export let options: Array<[string, string | number]>;
|
||||||
|
export let size: string = 'sm';
|
||||||
|
export let helpText: string | undefined = undefined;
|
||||||
|
export let selectClass: string | undefined = undefined;
|
||||||
|
export let onChange: (() => void) | undefined = undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Label md={6} for={id} {size}>{label}</Label>
|
||||||
|
<Col md="6">
|
||||||
|
<Input
|
||||||
|
{id}
|
||||||
|
type="select"
|
||||||
|
bind:value
|
||||||
|
name="select"
|
||||||
|
bsSize={size}
|
||||||
|
class={selectClass}
|
||||||
|
on:change={onChange}
|
||||||
|
>
|
||||||
|
{#each options as [key, val]}
|
||||||
|
<option value={val}>{key}</option>
|
||||||
|
{/each}
|
||||||
|
</Input>
|
||||||
|
{#if helpText}
|
||||||
|
<FormText>{helpText}</FormText>
|
||||||
|
{/if}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
15
src/lib/components/SettingsSwitch.svelte
Normal file
15
src/lib/components/SettingsSwitch.svelte
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Input, Col } from '@sveltestrap/sveltestrap';
|
||||||
|
|
||||||
|
// Props
|
||||||
|
export let id: string;
|
||||||
|
export let checked: boolean;
|
||||||
|
export let label: string;
|
||||||
|
export let size: string = 'sm';
|
||||||
|
export let disabled: boolean = false;
|
||||||
|
export let col: { [key: string]: string } = { md: '6', xl: '12', xxl: '6' };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Col {...col}>
|
||||||
|
<Input {id} bind:checked type="switch" bsSize={size} {label} {disabled} />
|
||||||
|
</Col>
|
5
src/lib/components/index.ts
Normal file
5
src/lib/components/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
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';
|
|
@ -8,11 +8,23 @@ register('nl', () => import('../locales/nl.json'));
|
||||||
register('es', () => import('../locales/es.json'));
|
register('es', () => import('../locales/es.json'));
|
||||||
register('de', () => import('../locales/de.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({
|
init({
|
||||||
fallbackLocale: defaultLocale,
|
fallbackLocale: defaultLocale,
|
||||||
initialLocale: browser
|
initialLocale: getInitialLocale()
|
||||||
? browser && localStorage.getItem('locale')
|
|
||||||
? localStorage.getItem('locale')
|
|
||||||
: window.navigator.language.slice(0, 2)
|
|
||||||
: defaultLocale
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -55,7 +55,10 @@
|
||||||
"ledFlashOnZap": "LED blinkt bei Nostr Zap",
|
"ledFlashOnZap": "LED blinkt bei Nostr Zap",
|
||||||
"flFlashOnZap": "Displaybeleuchting bei Nostr Zap",
|
"flFlashOnZap": "Displaybeleuchting bei Nostr Zap",
|
||||||
"showAll": "Alle anzeigen",
|
"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"
|
||||||
},
|
},
|
||||||
"control": {
|
"control": {
|
||||||
"systemInfo": "Systeminfo",
|
"systemInfo": "Systeminfo",
|
||||||
|
|
|
@ -44,6 +44,9 @@
|
||||||
"useNostr": "Use Nostr data source",
|
"useNostr": "Use Nostr data source",
|
||||||
"bitaxeHostname": "BitAxe hostname or IP",
|
"bitaxeHostname": "BitAxe hostname or IP",
|
||||||
"bitaxeEnabled": "Enable BitAxe",
|
"bitaxeEnabled": "Enable BitAxe",
|
||||||
|
"miningPoolStats": "Enable Mining Pool Stats",
|
||||||
|
"miningPoolName": "Mining Pool",
|
||||||
|
"miningPoolUser": "Mining Pool username or api key",
|
||||||
"nostrZapPubkey": "Nostr Zap pubkey",
|
"nostrZapPubkey": "Nostr Zap pubkey",
|
||||||
"invalidNostrPubkey": "Invalid Nostr pubkey, note that your pubkey does NOT start with npub.",
|
"invalidNostrPubkey": "Invalid Nostr pubkey, note that your pubkey does NOT start with npub.",
|
||||||
"convertingValidNpub": "Converting valid npub to pubkey",
|
"convertingValidNpub": "Converting valid npub to pubkey",
|
||||||
|
@ -67,7 +70,10 @@
|
||||||
"ledFlashOnZap": "LED flash on Nostr Zap",
|
"ledFlashOnZap": "LED flash on Nostr Zap",
|
||||||
"flFlashOnZap": "Frontlight flash on Nostr Zap",
|
"flFlashOnZap": "Frontlight flash on Nostr Zap",
|
||||||
"showAll": "Show all",
|
"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"
|
||||||
},
|
},
|
||||||
"control": {
|
"control": {
|
||||||
"systemInfo": "System info",
|
"systemInfo": "System info",
|
||||||
|
|
|
@ -54,7 +54,10 @@
|
||||||
"ledFlashOnZap": "LED parpadeante con Nostr Zap",
|
"ledFlashOnZap": "LED parpadeante con Nostr Zap",
|
||||||
"flFlashOnZap": "Flash de luz frontal con Nostr Zap",
|
"flFlashOnZap": "Flash de luz frontal con Nostr Zap",
|
||||||
"showAll": "Mostrar todo",
|
"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"
|
||||||
},
|
},
|
||||||
"control": {
|
"control": {
|
||||||
"turnOff": "Apagar",
|
"turnOff": "Apagar",
|
||||||
|
|
|
@ -55,7 +55,10 @@
|
||||||
"ledFlashOnZap": "Knipper LED bij Nostr Zap",
|
"ledFlashOnZap": "Knipper LED bij Nostr Zap",
|
||||||
"flFlashOnZap": "Knipper displaylicht bij Nostr Zap",
|
"flFlashOnZap": "Knipper displaylicht bij Nostr Zap",
|
||||||
"showAll": "Toon alles",
|
"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"
|
||||||
},
|
},
|
||||||
"control": {
|
"control": {
|
||||||
"systemInfo": "Systeeminformatie",
|
"systemInfo": "Systeeminformatie",
|
||||||
|
|
|
@ -1,19 +1,33 @@
|
||||||
|
@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/functions';
|
||||||
@import '../node_modules/bootstrap/scss/variables';
|
|
||||||
@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/oswald/latin-400.css';
|
@include Ubuntu.faces(
|
||||||
@import '@fontsource/antonio/latin-400.css';
|
$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';
|
@import './satsymbol';
|
||||||
|
|
||||||
$color-mode-type: media-query;
|
$color-mode-type: data;
|
||||||
$font-family-base: 'Ubuntu';
|
$font-family-base: 'Ubuntu';
|
||||||
$font-size-base: 0.9rem;
|
$font-size-base: 0.9rem;
|
||||||
$input-font-size-sm: $font-size-base * 0.875;
|
$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;
|
// $border-radius: .675rem;
|
||||||
|
|
||||||
@import '../node_modules/bootstrap/scss/mixins';
|
@import '../node_modules/bootstrap/scss/mixins';
|
||||||
|
@ -43,6 +57,40 @@ $input-font-size-sm: $font-size-base * 0.875;
|
||||||
@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';
|
||||||
|
|
||||||
|
/* 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 {
|
nav {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
@ -53,6 +101,23 @@ nav {
|
||||||
|
|
||||||
.btn-group-sm .btn {
|
.btn-group-sm .btn {
|
||||||
font-size: 0.8rem;
|
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 {
|
#customText {
|
||||||
|
@ -63,7 +128,7 @@ nav {
|
||||||
.btclock {
|
.btclock {
|
||||||
background: #000;
|
background: #000;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: calc(3vw + 3vh);
|
font-size: calc(2vw + 2vh);
|
||||||
font-family: 'Antonio', sans-serif;
|
font-family: 'Antonio', sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
@ -104,19 +169,25 @@ nav {
|
||||||
flex-direction: column; /* Stack the text and line vertically */
|
flex-direction: column; /* Stack the text and line vertically */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around; /* Distribute items with space between */
|
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;
|
display: block;
|
||||||
content: '';
|
content: '';
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
border-bottom: 2px solid;
|
border-bottom: 2px solid;
|
||||||
margin-bottom: 3px;
|
// margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitText {
|
.splitText {
|
||||||
font-size: calc(0.5vw + 1vh);
|
font-size: calc(0.3vw + 1vh);
|
||||||
|
|
||||||
.top-text,
|
.top-text,
|
||||||
.bottom-text {
|
.bottom-text {
|
||||||
|
@ -242,3 +313,7 @@ nav {
|
||||||
input[type='number'] {
|
input[type='number'] {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lightMode .bitaxelogo {
|
||||||
|
filter: brightness(0) saturate(100%);
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Satoshi Symbol';
|
font-family: 'Satoshi Symbol';
|
||||||
src:
|
src: url('/fonts/Satoshi_Symbol.woff2') format('woff2');
|
||||||
url('/fonts/Satoshi_Symbol.woff2') format('woff2'),
|
|
||||||
url('/fonts/Satoshi_Symbol.woff') format('woff');
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
|
|
@ -12,9 +12,12 @@
|
||||||
NavbarBrand,
|
NavbarBrand,
|
||||||
NavbarToggler
|
NavbarToggler
|
||||||
} from '@sveltestrap/sveltestrap';
|
} from '@sveltestrap/sveltestrap';
|
||||||
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { locale, locales, isLoading } from 'svelte-i18n';
|
import { locale, locales, isLoading } from 'svelte-i18n';
|
||||||
|
import { ColorSchemeSwitcher } from '$lib/components';
|
||||||
|
import { derived } from 'svelte/store';
|
||||||
|
|
||||||
export const setLocale = (lang: string) => () => {
|
export const setLocale = (lang: string) => () => {
|
||||||
locale.set(lang);
|
locale.set(lang);
|
||||||
|
@ -37,19 +40,20 @@
|
||||||
return flagMap[lowercaseCode];
|
return flagMap[lowercaseCode];
|
||||||
} else {
|
} else {
|
||||||
// Return null for unsupported language codes
|
// Return null for unsupported language codes
|
||||||
return null;
|
return flagMap['en'];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let languageNames = {};
|
let languageNames = {};
|
||||||
|
|
||||||
|
const currentLocale = derived(locale, ($locale) => $locale || 'en');
|
||||||
|
|
||||||
locale.subscribe(() => {
|
locale.subscribe(() => {
|
||||||
if ($locale) {
|
const localeToUse = $locale || 'en';
|
||||||
let newLanguageNames = new Intl.DisplayNames([$locale], { type: 'language' });
|
let newLanguageNames = new Intl.DisplayNames([localeToUse], { type: 'language' });
|
||||||
|
|
||||||
for (let l of $locales) {
|
for (let l of $locales) {
|
||||||
languageNames[l] = newLanguageNames.of(l);
|
languageNames[l] = newLanguageNames.of(l) || l;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,8 +64,23 @@
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Navbar expand="md">
|
<Navbar expand="md" sticky="xs-top" theme="auto">
|
||||||
<NavbarBrand>₿TClock</NavbarBrand>
|
<NavbarBrand class="d-none d-sm-block">₿TClock</NavbarBrand>
|
||||||
|
<Nav class="d-md-none" pills>
|
||||||
|
<NavItem>
|
||||||
|
<NavLink href="#control" active>{$_('section.control.title', { default: 'Control' })}</NavLink
|
||||||
|
>
|
||||||
|
</NavItem>
|
||||||
|
<NavItem>
|
||||||
|
<NavLink href="#status">{$_('section.status.title', { default: 'Status' })}</NavLink>
|
||||||
|
</NavItem>
|
||||||
|
<NavItem>
|
||||||
|
<NavLink class="nav-link" href="#settings"
|
||||||
|
>{$_('section.settings.title', { default: 'Settings' })}</NavLink
|
||||||
|
>
|
||||||
|
</NavItem>
|
||||||
|
</Nav>
|
||||||
|
|
||||||
<NavbarToggler on:click={toggle} />
|
<NavbarToggler on:click={toggle} />
|
||||||
|
|
||||||
<Collapse {isOpen} navbar expand="sm">
|
<Collapse {isOpen} navbar expand="sm">
|
||||||
|
@ -77,8 +96,11 @@
|
||||||
</NavItem>
|
</NavItem>
|
||||||
</Nav>
|
</Nav>
|
||||||
{#if !$isLoading}
|
{#if !$isLoading}
|
||||||
<Dropdown id="nav-language-dropdown" inNavbar>
|
<Dropdown id="nav-language-dropdown" inNavbar class="me-3">
|
||||||
<DropdownToggle nav caret>{getFlagEmoji($locale)} {languageNames[$locale]}</DropdownToggle>
|
<DropdownToggle nav caret
|
||||||
|
>{getFlagEmoji($currentLocale)}
|
||||||
|
{languageNames[$currentLocale] || 'English'}</DropdownToggle
|
||||||
|
>
|
||||||
<DropdownMenu end>
|
<DropdownMenu end>
|
||||||
{#each $locales as locale}
|
{#each $locales as locale}
|
||||||
<DropdownItem on:click={setLocale(locale)}
|
<DropdownItem on:click={setLocale(locale)}
|
||||||
|
@ -88,8 +110,11 @@
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
{/if}
|
{/if}
|
||||||
|
<ColorSchemeSwitcher></ColorSchemeSwitcher>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<!-- +layout.svelte -->
|
<!-- +layout.svelte -->
|
||||||
|
<main>
|
||||||
<slot />
|
<slot />
|
||||||
|
</main>
|
||||||
|
|
|
@ -6,10 +6,15 @@ 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) {
|
||||||
|
if (localStorage.getItem('locale')) {
|
||||||
locale.set(localStorage.getItem('locale'));
|
locale.set(localStorage.getItem('locale'));
|
||||||
} else if (browser) {
|
} else {
|
||||||
locale.set(window.navigator.language);
|
// 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();
|
await waitLocale();
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { screenSize, updateScreenSize } from '$lib/screen';
|
import { screenSize, updateScreenSize } from '$lib/screen';
|
||||||
|
|
||||||
import { Container, Row, Toast, ToastBody } from '@sveltestrap/sveltestrap';
|
import { Container, Row, Toast, ToastBody } from '@sveltestrap/sveltestrap';
|
||||||
|
import { replaceState } from '$app/navigation';
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
@ -16,12 +17,6 @@
|
||||||
bgColor: '0'
|
bgColor: '0'
|
||||||
});
|
});
|
||||||
|
|
||||||
// let uiSettings = writable({
|
|
||||||
// inputSize: 'sm',
|
|
||||||
// selectClass: '',
|
|
||||||
// btnSize: 'lg'
|
|
||||||
// });
|
|
||||||
|
|
||||||
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,
|
||||||
|
@ -60,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(() => {
|
onMount(() => {
|
||||||
|
setupObserver();
|
||||||
|
|
||||||
fetchSettingsData();
|
fetchSettingsData();
|
||||||
fetchStatusData();
|
fetchStatusData();
|
||||||
|
|
||||||
|
@ -72,6 +103,11 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleResize() {
|
function handleResize() {
|
||||||
|
if (observer) {
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
setupObserver();
|
||||||
|
|
||||||
updateScreenSize();
|
updateScreenSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +161,9 @@
|
||||||
<Container fluid>
|
<Container fluid>
|
||||||
<Row>
|
<Row>
|
||||||
<Control bind:settings on:showToast={showToast} bind:status lg="3" xxl="4"></Control>
|
<Control bind:settings on:showToast={showToast} bind:status lg="3" xxl="4"></Control>
|
||||||
|
|
||||||
<Status bind:settings bind:status lg="6" xxl="4"></Status>
|
<Status bind:settings bind:status lg="6" xxl="4"></Status>
|
||||||
|
|
||||||
<Settings bind:settings on:showToast={showToast} on:formReset={fetchSettingsData} lg="3" xxl="4"
|
<Settings bind:settings on:showToast={showToast} on:formReset={fetchSettingsData} lg="3" xxl="4"
|
||||||
></Settings>
|
></Settings>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -105,8 +105,8 @@
|
||||||
export let xxl = xl;
|
export let xxl = xl;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Col {xs} {sm} {md} {lg} {xl} {xxl}>
|
<Col {xs} {sm} {md} {lg} {xl} {xxl} class="mb-4 mb-xl-0">
|
||||||
<Card>
|
<Card id="control">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{$_('section.control.title', { default: 'Control' })}</CardTitle>
|
<CardTitle>{$_('section.control.title', { default: 'Control' })}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
|
@ -200,7 +200,7 @@
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
{/if}
|
{/if}
|
||||||
<section class="row row-cols-lg-auto align-items-end">
|
<section class="row row-cols-lg-auto align-items-end">
|
||||||
<div class="col-12">
|
<div class="col flex-fill">
|
||||||
<label for="firmwareFile" class="form-label">Firmware file ({getFirmwareBinaryName()})</label>
|
<label for="firmwareFile" class="form-label">Firmware file ({getFirmwareBinaryName()})</label>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
|
@ -216,7 +216,7 @@
|
||||||
>Update firmware</Button
|
>Update firmware</Button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="col mt-2">
|
<div class="col flex-fill">
|
||||||
<label for="webuiFile" class="form-label">WebUI file (littlefs.bin)</label>
|
<label for="webuiFile" class="form-label">WebUI file (littlefs.bin)</label>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
export let className = 'btclock-wrapper';
|
export let className = 'btclock-wrapper';
|
||||||
|
export let verticalDesc = false;
|
||||||
// Define the currency symbols as constants
|
// Define the currency symbols as constants
|
||||||
const CURRENCY_USD = '$';
|
const CURRENCY_USD = '$';
|
||||||
const CURRENCY_EUR = '[';
|
const CURRENCY_EUR = '[';
|
||||||
|
@ -44,21 +44,22 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={className} id={className}>
|
<div class={className} id={className}>
|
||||||
<div class="btclock">
|
<div class={'btclock' + (verticalDesc ? ' verticalDesc' : '')}>
|
||||||
{#each status.data as char}
|
{#each status.data as char}
|
||||||
{#if isSplitText(char)}
|
{#if isSplitText(char)}
|
||||||
<div class="splitText">
|
<div class="splitText">
|
||||||
|
<div class="textcontainer">
|
||||||
{#if char.split('/').length}
|
{#if char.split('/').length}
|
||||||
<span class="top-text">{char.split('/')[0]}</span>
|
<span class="top-text">{char.split('/')[0]}</span>
|
||||||
<hr />
|
|
||||||
<span class="bottom-text">{char.split('/')[1]}</span>
|
<span class="bottom-text">{char.split('/')[1]}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
<!-- {#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.startsWith('mdi')}
|
{:else if char.startsWith('mdi')}
|
||||||
<div class="digit icon">
|
<div class={'digit icon' + (char.endsWith('bitaxe') ? ' icon-img' : '')}>
|
||||||
{#if char.endsWith('rocket')}
|
{#if char.endsWith('rocket')}
|
||||||
<RocketIcon></RocketIcon>
|
<RocketIcon></RocketIcon>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -68,6 +69,12 @@
|
||||||
{#if char.endsWith('bolt')}
|
{#if char.endsWith('bolt')}
|
||||||
<ZapIcon></ZapIcon>
|
<ZapIcon></ZapIcon>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if char.endsWith('bitaxe')}
|
||||||
|
<img src="/bitaxe.webp" class="bitaxelogo" alt="BitAxe logo" />
|
||||||
|
{/if}
|
||||||
|
{#if char.endsWith('miningpool')}
|
||||||
|
<span class="pool-logo">Mining Pool Logo</span>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if char === 'STS'}
|
{:else if char === 'STS'}
|
||||||
<div class="digit sats">S</div>
|
<div class="digit sats">S</div>
|
||||||
|
@ -82,8 +89,26 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
.icon {
|
.icon {
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btclock-wrapper .btclock .icon.icon-img {
|
||||||
|
// padding: 0 15px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
width: calc(100 / 7);
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bitaxelogo {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pool-logo {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import { PUBLIC_BASE_URL } from '$lib/config';
|
import { PUBLIC_BASE_URL } from '$lib/config';
|
||||||
import { uiSettings } from '$lib/uiSettings';
|
import { uiSettings } from '$lib/uiSettings';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -13,18 +12,12 @@
|
||||||
CardTitle,
|
CardTitle,
|
||||||
Col,
|
Col,
|
||||||
Form,
|
Form,
|
||||||
FormText,
|
|
||||||
Input,
|
|
||||||
InputGroup,
|
|
||||||
InputGroupText,
|
|
||||||
Label,
|
|
||||||
Tooltip,
|
|
||||||
Row
|
Row
|
||||||
} from '@sveltestrap/sveltestrap';
|
} from '@sveltestrap/sveltestrap';
|
||||||
import EyeIcon from '../icons/EyeIcon.svelte';
|
import EyeIcon from 'svelte-bootstrap-icons/lib/Eye.svelte';
|
||||||
import EyeSlashIcon from '../icons/EyeSlashIcon.svelte';
|
import EyeSlashIcon from 'svelte-bootstrap-icons/lib/EyeSlash.svelte';
|
||||||
import { derived } from 'svelte/store';
|
import { derived } from 'svelte/store';
|
||||||
import ToggleHeader from '../components/ToggleHeader.svelte';
|
import { SettingsSwitch, SettingsInput, SettingsSelect, ToggleHeader } from '$lib/components';
|
||||||
|
|
||||||
export let settings;
|
export let settings;
|
||||||
|
|
||||||
|
@ -42,6 +35,21 @@
|
||||||
['5dBm', 20] // 5dBm
|
['5dBm', 20] // 5dBm
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const miningPoolMap = new Map<string, string>([
|
||||||
|
['noderunners', 'Noderunners.network'],
|
||||||
|
['braiins', 'Braiins Pool'],
|
||||||
|
['ocean', 'ocean.xyz'],
|
||||||
|
['satoshi_radio', 'Satoshi Radio pool'],
|
||||||
|
['public_pool', 'public-pool.io'],
|
||||||
|
['gobrrr_pool', 'Go Brrr pool']
|
||||||
|
]);
|
||||||
|
|
||||||
|
const getMiningPoolName = (name: string) => {
|
||||||
|
if (miningPoolMap.has(name)) return miningPoolMap.get(name);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
const handleReset = (e: Event) => {
|
const handleReset = (e: Event) => {
|
||||||
|
@ -139,7 +147,7 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkValidNostrPubkey = (key) => {
|
const checkValidNostrPubkey = (key: string) => {
|
||||||
if (isValidNpub($settings[key])) {
|
if (isValidNpub($settings[key])) {
|
||||||
dispatch('showToast', {
|
dispatch('showToast', {
|
||||||
color: 'info',
|
color: 'info',
|
||||||
|
@ -219,8 +227,8 @@
|
||||||
systemIsOpen: boolean;
|
systemIsOpen: boolean;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Col {xs} {sm} {md} {lg} {xl} {xxl}>
|
<Col {xs} {sm} {md} {lg} {xl} {xxl} class="mb-4 mb-xl-0">
|
||||||
<Card>
|
<Card id="settings">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div class="float-end">
|
<div class="float-end">
|
||||||
<small
|
<small
|
||||||
|
@ -241,6 +249,7 @@
|
||||||
</div>
|
</div>
|
||||||
<CardTitle>{$_('section.settings.title', { default: 'Settings' })}</CardTitle>
|
<CardTitle>{$_('section.settings.title', { default: 'Settings' })}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Form on:submit={onSave} class="clearfix">
|
<Form on:submit={onSave} class="clearfix">
|
||||||
<Row>
|
<Row>
|
||||||
|
@ -250,98 +259,86 @@
|
||||||
isOpen={screenSettingsIsOpen}
|
isOpen={screenSettingsIsOpen}
|
||||||
>
|
>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="stealFocus"
|
id="stealFocus"
|
||||||
bind:checked={$settings.stealFocus}
|
bind:checked={$settings.stealFocus}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.StealFocusOnNewBlock')}
|
label={$_('section.settings.StealFocusOnNewBlock')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
col={{ md: '6', xl: '12', xxl: '6' }}
|
||||||
/>
|
/>
|
||||||
</Col>
|
<SettingsSwitch
|
||||||
<Col md="6" xl="12" xxl="6">
|
|
||||||
<Input
|
|
||||||
id="mcapBigChar"
|
id="mcapBigChar"
|
||||||
bind:checked={$settings.mcapBigChar}
|
bind:checked={$settings.mcapBigChar}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.useBigCharsMcap')}
|
label={$_('section.settings.useBigCharsMcap')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
col={{ md: '6', xl: '12', xxl: '6' }}
|
||||||
/>
|
/>
|
||||||
</Col>
|
<SettingsSwitch
|
||||||
|
|
||||||
<Col md="6" xl="12" xxl="6">
|
|
||||||
<Input
|
|
||||||
id="useBlkCountdown"
|
id="useBlkCountdown"
|
||||||
bind:checked={$settings.useBlkCountdown}
|
bind:checked={$settings.useBlkCountdown}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.useBlkCountdown')}
|
label={$_('section.settings.useBlkCountdown')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
col={{ md: '6', xl: '12', xxl: '6' }}
|
||||||
/>
|
/>
|
||||||
</Col>
|
<SettingsSwitch
|
||||||
<Col md="6" xl="12" xxl="6">
|
|
||||||
<Input
|
|
||||||
id="useSatsSymbol"
|
id="useSatsSymbol"
|
||||||
bind:checked={$settings.useSatsSymbol}
|
bind:checked={$settings.useSatsSymbol}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.useSatsSymbol')}
|
label={$_('section.settings.useSatsSymbol')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
col={{ md: '6', xl: '12', xxl: '6' }}
|
||||||
/>
|
/>
|
||||||
</Col>
|
<SettingsSwitch
|
||||||
<Col md="6" xl="12" xxl="6">
|
|
||||||
<Input
|
|
||||||
id="suffixPrice"
|
id="suffixPrice"
|
||||||
bind:checked={$settings.suffixPrice}
|
bind:checked={$settings.suffixPrice}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.suffixPrice')}
|
label={$_('section.settings.suffixPrice')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
col={{ md: '6', xl: '12', xxl: '6' }}
|
||||||
/>
|
/>
|
||||||
</Col>
|
<SettingsSwitch
|
||||||
<Col md="6" xl="12" xxl="6">
|
|
||||||
<Input
|
|
||||||
disabled={!$settings.suffixPrice}
|
|
||||||
id="mowMode"
|
id="mowMode"
|
||||||
bind:checked={$settings.mowMode}
|
bind:checked={$settings.mowMode}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.mowMode')}
|
label={$_('section.settings.mowMode')}
|
||||||
/>
|
size={$uiSettings.inputSize}
|
||||||
</Col>
|
col={{ md: '6', xl: '12', xxl: '6' }}
|
||||||
<Col md="6" xl="12" xxl="6">
|
|
||||||
<Input
|
|
||||||
disabled={!$settings.suffixPrice}
|
disabled={!$settings.suffixPrice}
|
||||||
|
/>
|
||||||
|
<SettingsSwitch
|
||||||
id="suffixShareDot"
|
id="suffixShareDot"
|
||||||
bind:checked={$settings.suffixShareDot}
|
bind:checked={$settings.suffixShareDot}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.suffixShareDot')}
|
label={$_('section.settings.suffixShareDot')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
col={{ md: '6', xl: '12', xxl: '6' }}
|
||||||
|
disabled={!$settings.suffixPrice}
|
||||||
|
/>
|
||||||
|
<SettingsSwitch
|
||||||
|
id="verticalDesc"
|
||||||
|
bind:checked={$settings.verticalDesc}
|
||||||
|
label={$_('section.settings.verticalDesc')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
col={{ md: '6', xl: '12', xxl: '6' }}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
|
|
||||||
{#if !$settings.actCurrencies}
|
{#if !$settings.actCurrencies}
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="fetchEurPrice"
|
id="fetchEurPrice"
|
||||||
bind:checked={$settings.fetchEurPrice}
|
bind:checked={$settings.fetchEurPrice}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label="{$_('section.settings.fetchEuroPrice')} ({$_('restartRequired')})"
|
label="{$_('section.settings.fetchEuroPrice')} ({$_('restartRequired')})"
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
col={{ md: '6', xl: '12', xxl: '6' }}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
{/if}
|
{/if}
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<h5>{$_('section.settings.screens')}</h5>
|
<h5>{$_('section.settings.screens')}</h5>
|
||||||
{#if $settings.screens}
|
{#if $settings.screens}
|
||||||
{#each $settings.screens as s}
|
{#each $settings.screens as s}
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="screens_{s.id}"
|
id="screens_{s.id}"
|
||||||
bind:checked={s.enabled}
|
bind:checked={s.enabled}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={s.name}
|
label={s.name}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
col={{ md: '6', xl: '12', xxl: '6' }}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -375,207 +372,157 @@
|
||||||
header={$_('section.settings.section.displaysAndLed')}
|
header={$_('section.settings.section.displaysAndLed')}
|
||||||
isOpen={displaysAndLedIsOpen}
|
isOpen={displaysAndLedIsOpen}
|
||||||
>
|
>
|
||||||
<Row>
|
<SettingsSelect
|
||||||
<Label md={6} for="textColor" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.textColor', { default: 'Text color' })}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<Input
|
|
||||||
type="select"
|
|
||||||
bind:value={textColor}
|
|
||||||
name="select"
|
|
||||||
id="textColor"
|
id="textColor"
|
||||||
on:change={setTextColor}
|
label={$_('section.settings.textColor')}
|
||||||
bsSize={$uiSettings.inputSize}
|
bind:value={textColor}
|
||||||
class={$uiSettings.selectClass}
|
options={[
|
||||||
>
|
[$_('colors.black') + ' on ' + $_('colors.white'), '0'],
|
||||||
<option value="0">{$_('colors.black')} on {$_('colors.white')}</option>
|
[$_('colors.white') + ' on ' + $_('colors.black'), '1']
|
||||||
<option value="1">{$_('colors.white')} on {$_('colors.black')}</option>
|
]}
|
||||||
</Input>
|
size={$uiSettings.inputSize}
|
||||||
</Col>
|
selectClass={$uiSettings.selectClass}
|
||||||
</Row>
|
onChange={setTextColor}
|
||||||
|
/>
|
||||||
|
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="timePerScreen" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.timePerScreen')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<InputGroup size={$uiSettings.inputSize}>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
id="timePerScreen"
|
id="timePerScreen"
|
||||||
min={1}
|
label={$_('section.settings.timePerScreen')}
|
||||||
step="1"
|
|
||||||
required
|
|
||||||
bind:value={$settings.timePerScreen}
|
bind:value={$settings.timePerScreen}
|
||||||
/>
|
|
||||||
<InputGroupText>{$_('time.minutes')}</InputGroupText>
|
|
||||||
</InputGroup>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Label md={6} for="fullRefreshMin" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.fullRefreshEvery')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<InputGroup size={$uiSettings.inputSize}>
|
|
||||||
<Input
|
|
||||||
type="number"
|
type="number"
|
||||||
|
min={1}
|
||||||
|
step="1"
|
||||||
|
required={true}
|
||||||
|
suffix={$_('time.minutes')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingsInput
|
||||||
id="fullRefreshMin"
|
id="fullRefreshMin"
|
||||||
min={1}
|
label={$_('section.settings.fullRefreshEvery')}
|
||||||
step="1"
|
|
||||||
required
|
|
||||||
bind:value={$settings.fullRefreshMin}
|
bind:value={$settings.fullRefreshMin}
|
||||||
/>
|
|
||||||
<InputGroupText>{$_('time.minutes')}</InputGroupText>
|
|
||||||
</InputGroup>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Label md={6} for="minSecPriceUpd" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.timeBetweenPriceUpdates')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<InputGroup size={$uiSettings.inputSize}>
|
|
||||||
<Input
|
|
||||||
type="number"
|
type="number"
|
||||||
id="minSecPriceUpd"
|
|
||||||
min={1}
|
min={1}
|
||||||
step="1"
|
step="1"
|
||||||
bind:value={$settings.minSecPriceUpd}
|
required={true}
|
||||||
|
suffix={$_('time.minutes')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
<InputGroupText>{$_('time.seconds')}</InputGroupText>
|
|
||||||
</InputGroup>
|
<SettingsInput
|
||||||
<FormText>{$_('section.settings.shortAmountsWarning')}</FormText>
|
id="minSecPriceUpd"
|
||||||
</Col>
|
label={$_('section.settings.timeBetweenPriceUpdates')}
|
||||||
</Row>
|
bind:value={$settings.minSecPriceUpd}
|
||||||
<Row>
|
type="number"
|
||||||
<Label md={6} for="ledBrightness" size={$uiSettings.inputSize}
|
min={1}
|
||||||
>{$_('section.settings.ledBrightness')}</Label
|
step="1"
|
||||||
>
|
suffix={$_('time.seconds')}
|
||||||
<Col md="6">
|
helpText={$_('section.settings.shortAmountsWarning')}
|
||||||
<Input
|
size={$uiSettings.inputSize}
|
||||||
type="range"
|
/>
|
||||||
name="ledBrightness"
|
|
||||||
|
<SettingsInput
|
||||||
id="ledBrightness"
|
id="ledBrightness"
|
||||||
|
label={$_('section.settings.ledBrightness')}
|
||||||
bind:value={$settings.ledBrightness}
|
bind:value={$settings.ledBrightness}
|
||||||
|
type="range"
|
||||||
min={0}
|
min={0}
|
||||||
max={255}
|
max={255}
|
||||||
step={1}
|
step={1}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{#if $settings.hasFrontlight && !$settings.flDisable}
|
{#if $settings.hasFrontlight && !$settings.flDisable}
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="flMaxBrightness" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.flMaxBrightness')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<Input
|
|
||||||
type="range"
|
|
||||||
name="flMaxBrightness"
|
|
||||||
id="flMaxBrightness"
|
id="flMaxBrightness"
|
||||||
|
label={$_('section.settings.flMaxBrightness')}
|
||||||
bind:value={$settings.flMaxBrightness}
|
bind:value={$settings.flMaxBrightness}
|
||||||
on:change={onFlBrightnessChange}
|
type="range"
|
||||||
min={0}
|
min={0}
|
||||||
max={4095}
|
max={4095}
|
||||||
step={1}
|
step={1}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
onChange={onFlBrightnessChange}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
</Row>
|
<SettingsInput
|
||||||
<Row>
|
|
||||||
<Label md={6} for="flEffectDelay" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.flEffectDelay')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<Input
|
|
||||||
type="range"
|
|
||||||
name="flEffectDelay"
|
|
||||||
id="flEffectDelay"
|
id="flEffectDelay"
|
||||||
|
label={$_('section.settings.flEffectDelay')}
|
||||||
bind:value={$settings.flEffectDelay}
|
bind:value={$settings.flEffectDelay}
|
||||||
|
type="range"
|
||||||
min={5}
|
min={5}
|
||||||
max={300}
|
max={300}
|
||||||
step={1}
|
step={1}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !$settings.flDisable && $settings.hasLightLevel}
|
{#if !$settings.flDisable && $settings.hasLightLevel}
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="luxLightToggle" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.luxLightToggle')} ({$settings.luxLightToggle})</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<Input
|
|
||||||
type="range"
|
|
||||||
name="luxLightToggle"
|
|
||||||
id="luxLightToggle"
|
id="luxLightToggle"
|
||||||
|
label={`${$_('section.settings.luxLightToggle')} (${$settings.luxLightToggle})`}
|
||||||
bind:value={$settings.luxLightToggle}
|
bind:value={$settings.luxLightToggle}
|
||||||
|
type="range"
|
||||||
min={0}
|
min={0}
|
||||||
max={1000}
|
max={1000}
|
||||||
step={1}
|
step={1}
|
||||||
|
helpText={$_('section.settings.luxLightToggleText')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="ledTestOnPower"
|
id="ledTestOnPower"
|
||||||
bind:checked={$settings.ledTestOnPower}
|
bind:checked={$settings.ledTestOnPower}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.ledPowerOnTest')}
|
label={$_('section.settings.ledPowerOnTest')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="ledFlashOnUpd"
|
id="ledFlashOnUpd"
|
||||||
bind:checked={$settings.ledFlashOnUpd}
|
bind:checked={$settings.ledFlashOnUpd}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.ledFlashOnBlock')}
|
label={$_('section.settings.ledFlashOnBlock')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="disableLeds"
|
id="disableLeds"
|
||||||
bind:checked={$settings.disableLeds}
|
bind:checked={$settings.disableLeds}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.disableLeds')}
|
label={$_('section.settings.disableLeds')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
{#if $settings.hasFrontlight}
|
{#if $settings.hasFrontlight}
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="flDisable"
|
id="flDisable"
|
||||||
bind:checked={$settings.flDisable}
|
bind:checked={$settings.flDisable}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.flDisable')}
|
label={$_('section.settings.flDisable')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $settings.hasFrontlight && !$settings.flDisable}
|
{#if $settings.hasFrontlight && !$settings.flDisable}
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="flAlwaysOn"
|
id="flAlwaysOn"
|
||||||
bind:checked={$settings.flAlwaysOn}
|
bind:checked={$settings.flAlwaysOn}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.flAlwaysOn')}
|
label={$_('section.settings.flAlwaysOn')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="flFlashOnUpd"
|
id="flFlashOnUpd"
|
||||||
bind:checked={$settings.flFlashOnUpd}
|
bind:checked={$settings.flFlashOnUpd}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.flFlashOnUpd')}
|
label={$_('section.settings.flFlashOnUpd')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingsSwitch
|
||||||
|
id="flOffWhenDark"
|
||||||
|
bind:checked={$settings.flOffWhenDark}
|
||||||
|
label={$_('section.settings.flOffWhenDark')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
{/if}
|
{/if}
|
||||||
</Row>
|
</Row>
|
||||||
</ToggleHeader>
|
</ToggleHeader>
|
||||||
|
@ -584,69 +531,40 @@
|
||||||
header={$_('section.settings.section.dataSource')}
|
header={$_('section.settings.section.dataSource')}
|
||||||
isOpen={dataSourceIsOpen}
|
isOpen={dataSourceIsOpen}
|
||||||
>
|
>
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="mempoolInstance" size="sm"
|
|
||||||
>{$_('section.settings.mempoolnstance')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<InputGroup size={$uiSettings.inputSize}>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
bind:value={$settings.mempoolInstance}
|
|
||||||
name="mempoolInstance"
|
|
||||||
id="mempoolInstance"
|
id="mempoolInstance"
|
||||||
|
label={$_('section.settings.mempoolnstance')}
|
||||||
|
bind:value={$settings.mempoolInstance}
|
||||||
disabled={$settings.ownDataSource}
|
disabled={$settings.ownDataSource}
|
||||||
bsSize="sm"
|
required={true}
|
||||||
required
|
helpText={$_('section.settings.mempoolInstanceHelpText')}
|
||||||
></Input>
|
size={$uiSettings.inputSize}
|
||||||
<InputGroupText>
|
|
||||||
<Input
|
|
||||||
addon
|
|
||||||
type="checkbox"
|
|
||||||
bind:checked={$settings.mempoolSecure}
|
|
||||||
disabled={$settings.ownDataSource}
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
/>
|
/>
|
||||||
HTTPS
|
|
||||||
</InputGroupText>
|
|
||||||
</InputGroup>
|
|
||||||
<FormText>{$_('section.settings.mempoolInstanceHelpText')}</FormText>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="ownDataSource"
|
id="ownDataSource"
|
||||||
bind:checked={$settings.ownDataSource}
|
bind:checked={$settings.ownDataSource}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label="{$_('section.settings.ownDataSource')} ({$_('restartRequired')})"
|
label="{$_('section.settings.ownDataSource')} ({$_('restartRequired')})"
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
{#if $settings.nostrRelay}
|
{#if $settings.nostrRelay}
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="useNostr"
|
id="useNostr"
|
||||||
bind:checked={$settings.useNostr}
|
bind:checked={$settings.useNostr}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label="{$_('section.settings.useNostr')} ({$_('restartRequired')})"
|
label="{$_('section.settings.useNostr')} ({$_('restartRequired')})"
|
||||||
></Input>
|
size={$uiSettings.inputSize}
|
||||||
<Tooltip target="useNostr" placement="left">
|
/>
|
||||||
{$_('section.settings.useNostrTooltip')}
|
|
||||||
</Tooltip>
|
|
||||||
</Col>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if 'stagingSource' in $settings}
|
{#if 'stagingSource' in $settings}
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="stagingSource"
|
id="stagingSource"
|
||||||
bind:checked={$settings.stagingSource}
|
bind:checked={$settings.stagingSource}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label="{$_('section.settings.stagingSource')} ({$_('restartRequired')})"
|
label="{$_('section.settings.stagingSource')} ({$_('restartRequired')})"
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
{/if}
|
{/if}
|
||||||
</Row>
|
</Row>
|
||||||
</ToggleHeader>
|
</ToggleHeader>
|
||||||
|
@ -656,287 +574,213 @@
|
||||||
isOpen={extraFeaturesIsOpen}
|
isOpen={extraFeaturesIsOpen}
|
||||||
>
|
>
|
||||||
{#if $settings.bitaxeEnabled}
|
{#if $settings.bitaxeEnabled}
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="bitaxeHostname" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.bitaxeHostname')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<InputGroup size={$uiSettings.inputSize}>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
bind:value={$settings.bitaxeHostname}
|
|
||||||
name="bitaxeHostname"
|
|
||||||
valid={validBitaxe}
|
|
||||||
id="bitaxeHostname"
|
id="bitaxeHostname"
|
||||||
required
|
label={$_('section.settings.bitaxeHostname')}
|
||||||
></Input>
|
bind:value={$settings.bitaxeHostname}
|
||||||
|
required={true}
|
||||||
|
valid={validBitaxe}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
>
|
||||||
<Button type="button" color="success" on:click={testBitaxe}
|
<Button type="button" color="success" on:click={testBitaxe}
|
||||||
>{$_('test', { default: 'Test' })}</Button
|
>{$_('test', { default: 'Test' })}</Button
|
||||||
>
|
>
|
||||||
</InputGroup>
|
</SettingsInput>
|
||||||
</Col>
|
{/if}
|
||||||
</Row>
|
{#if $settings.miningPoolStats}
|
||||||
|
<SettingsSelect
|
||||||
|
id="miningPoolName"
|
||||||
|
label={$_('section.settings.miningPoolName')}
|
||||||
|
bind:value={$settings.miningPoolName}
|
||||||
|
options={$settings.availablePools.map((pool) => [getMiningPoolName(pool), pool])}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
selectClass={$uiSettings.selectClass}
|
||||||
|
/>
|
||||||
|
<SettingsInput
|
||||||
|
id="miningPoolUser"
|
||||||
|
label={$_('section.settings.miningPoolUser')}
|
||||||
|
bind:value={$settings.miningPoolUser}
|
||||||
|
required={true}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if 'nostrZapNotify' in $settings && $settings['nostrZapNotify']}
|
{#if 'nostrZapNotify' in $settings && $settings['nostrZapNotify']}
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="nostrZapPubkey" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.nostrZapPubkey')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
bind:value={$settings.nostrZapPubkey}
|
|
||||||
name="nostrZapPubkey"
|
|
||||||
id="nostrZapPubkey"
|
id="nostrZapPubkey"
|
||||||
on:change={() => checkValidNostrPubkey('nostrZapPubkey')}
|
label={$_('section.settings.nostrZapPubkey')}
|
||||||
invalid={!isValidHexPubKey($settings.nostrZapPubkey)}
|
bind:value={$settings.nostrZapPubkey}
|
||||||
bsSize={$uiSettings.inputSize}
|
required={true}
|
||||||
required
|
|
||||||
minlength="64"
|
minlength="64"
|
||||||
></Input>
|
invalid={!isValidHexPubKey($settings.nostrZapPubkey)}
|
||||||
{#if !isValidHexPubKey($settings.nostrZapPubkey)}
|
helpText={!isValidHexPubKey($settings.nostrZapPubkey)
|
||||||
<FormText>{$_('section.settings.invalidNostrPubkey')}</FormText>
|
? $_('section.settings.invalidNostrPubkey')
|
||||||
{/if}
|
: undefined}
|
||||||
</Col>
|
size={$uiSettings.inputSize}
|
||||||
</Row>
|
onChange={() => checkValidNostrPubkey('nostrZapPubkey')}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $settings.useNostr}
|
{#if $settings.useNostr}
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="nostrPubKey" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.nostrPubKey')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
bind:value={$settings.nostrPubKey}
|
|
||||||
name="nostrPubKey"
|
|
||||||
id="nostrPubKey"
|
id="nostrPubKey"
|
||||||
on:change={() => checkValidNostrPubkey('nostrPubKey')}
|
label={$_('section.settings.nostrPubKey')}
|
||||||
|
bind:value={$settings.nostrPubKey}
|
||||||
invalid={!isValidHexPubKey($settings.nostrPubKey)}
|
invalid={!isValidHexPubKey($settings.nostrPubKey)}
|
||||||
bsSize={$uiSettings.inputSize}
|
helpText={!isValidHexPubKey($settings.nostrPubKey)
|
||||||
></Input>
|
? $_('section.settings.invalidNostrPubkey')
|
||||||
{#if !isValidHexPubKey($settings.nostrPubKey)}
|
: undefined}
|
||||||
<FormText>{$_('section.settings.invalidNostrPubkey')}</FormText>
|
size={$uiSettings.inputSize}
|
||||||
{/if}
|
onChange={() => checkValidNostrPubkey('nostrPubKey')}
|
||||||
</Col>
|
/>
|
||||||
</Row>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if 'nostrZapNotify' in $settings || $settings.useNostr}
|
{#if 'nostrZapNotify' in $settings || $settings.useNostr}
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="nostrRelay" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.nostrRelay')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<InputGroup size={$uiSettings.inputSize}>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
bind:value={$settings.nostrRelay}
|
|
||||||
name="nostrRelay"
|
|
||||||
id="nostrRelay"
|
id="nostrRelay"
|
||||||
|
label={$_('section.settings.nostrRelay')}
|
||||||
|
bind:value={$settings.nostrRelay}
|
||||||
|
required={true}
|
||||||
valid={validNostrRelay}
|
valid={validNostrRelay}
|
||||||
bsSize={$uiSettings.inputSize}
|
size={$uiSettings.inputSize}
|
||||||
required
|
>
|
||||||
></Input>
|
|
||||||
<Button type="button" color="success" on:click={testNostrRelay}
|
<Button type="button" color="success" on:click={testNostrRelay}
|
||||||
>{$_('test', { default: 'Test' })}</Button
|
>{$_('test', { default: 'Test' })}</Button
|
||||||
>
|
>
|
||||||
</InputGroup>
|
</SettingsInput>
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{/if}
|
{/if}
|
||||||
<Row>
|
<Row>
|
||||||
{#if 'bitaxeEnabled' in $settings}
|
{#if 'bitaxeEnabled' in $settings}
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="bitaxeEnabled"
|
id="bitaxeEnabled"
|
||||||
bind:checked={$settings.bitaxeEnabled}
|
bind:checked={$settings.bitaxeEnabled}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label="{$_('section.settings.bitaxeEnabled')} ({$_('restartRequired')})"
|
label="{$_('section.settings.bitaxeEnabled')} ({$_('restartRequired')})"
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if 'miningPoolStats' in $settings}
|
||||||
|
<SettingsSwitch
|
||||||
|
id="miningPoolStats"
|
||||||
|
bind:checked={$settings.miningPoolStats}
|
||||||
|
label="{$_('section.settings.miningPoolStats')} ({$_('restartRequired')})"
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if 'nostrZapNotify' in $settings}
|
{#if 'nostrZapNotify' in $settings}
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="nostrZapNotify"
|
id="nostrZapNotify"
|
||||||
bind:checked={$settings.nostrZapNotify}
|
bind:checked={$settings.nostrZapNotify}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label="{$_('section.settings.nostrZapNotify')} ({$_('restartRequired')})"
|
label="{$_('section.settings.nostrZapNotify')} ({$_('restartRequired')})"
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
<SettingsSwitch
|
||||||
<Col md="6" xl="12" xxl="6">
|
|
||||||
<Input
|
|
||||||
id="ledFlashOnZap"
|
id="ledFlashOnZap"
|
||||||
bind:checked={$settings.ledFlashOnZap}
|
bind:checked={$settings.ledFlashOnZap}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.ledFlashOnZap')}
|
label={$_('section.settings.ledFlashOnZap')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
{#if $settings.hasFrontlight && !$settings.flDisable}
|
{#if $settings.hasFrontlight && !$settings.flDisable}
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="flFlashOnZap"
|
id="flFlashOnZap"
|
||||||
bind:checked={$settings.flFlashOnZap}
|
bind:checked={$settings.flFlashOnZap}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label={$_('section.settings.flFlashOnZap')}
|
label={$_('section.settings.flFlashOnZap')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</Row>
|
</Row>
|
||||||
</ToggleHeader>
|
</ToggleHeader>
|
||||||
</Row><Row>
|
</Row><Row>
|
||||||
<ToggleHeader header={$_('section.settings.section.system')} isOpen={systemIsOpen}>
|
<ToggleHeader header={$_('section.settings.section.system')} isOpen={systemIsOpen}>
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="tzOffset" size={$uiSettings.inputSize}
|
id="tzOffset"
|
||||||
>{$_('section.settings.timezoneOffset')}</Label
|
label={$_('section.settings.timezoneOffset')}
|
||||||
>
|
bind:value={$settings.tzOffset}
|
||||||
<Col md="6">
|
|
||||||
<InputGroup size={$uiSettings.inputSize}>
|
|
||||||
<Input
|
|
||||||
type="number"
|
type="number"
|
||||||
step="1"
|
step="1"
|
||||||
name="tzOffset"
|
required={true}
|
||||||
id="tzOffset"
|
suffix={$_('time.minutes')}
|
||||||
required
|
helpText={$_('section.settings.tzOffsetHelpText')}
|
||||||
bind:value={$settings.tzOffset}
|
size={$uiSettings.inputSize}
|
||||||
/>
|
>
|
||||||
<InputGroupText>{$_('time.minutes')}</InputGroupText>
|
|
||||||
<Button type="button" color="info" on:click={getTzOffsetFromSystem}
|
<Button type="button" color="info" on:click={getTzOffsetFromSystem}
|
||||||
>{$_('auto-detect')}</Button
|
>{$_('auto-detect')}</Button
|
||||||
>
|
>
|
||||||
</InputGroup>
|
</SettingsInput>
|
||||||
<FormText>{$_('section.settings.tzOffsetHelpText')}</FormText>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{#if $settings.httpAuthEnabled}
|
{#if $settings.httpAuthEnabled}
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="httpAuthUser" size="sm"
|
|
||||||
>{$_('section.settings.httpAuthUser')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
bind:value={$settings.httpAuthUser}
|
|
||||||
name="httpAuthUser"
|
|
||||||
id="httpAuthUser"
|
id="httpAuthUser"
|
||||||
bsSize="sm"
|
label={$_('section.settings.httpAuthUser')}
|
||||||
required
|
bind:value={$settings.httpAuthUser}
|
||||||
></Input>
|
required={true}
|
||||||
</Col>
|
size={$uiSettings.inputSize}
|
||||||
</Row>
|
/>
|
||||||
<Row>
|
<SettingsInput
|
||||||
<Label md={6} for="httpAuthPass" size="sm"
|
|
||||||
>{$_('section.settings.httpAuthPass')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<InputGroup size={$uiSettings.inputSize}>
|
|
||||||
<Input
|
|
||||||
type={showPassword ? 'text' : 'password'}
|
|
||||||
bind:value={$settings.httpAuthPass}
|
|
||||||
name="httpAuthPass"
|
|
||||||
id="httpAuthPass"
|
id="httpAuthPass"
|
||||||
bsSize="sm"
|
label={$_('section.settings.httpAuthPass')}
|
||||||
required
|
bind:value={$settings.httpAuthPass}
|
||||||
></Input>
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
required={true}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => (showPassword = !showPassword)}
|
on:click={() => (showPassword = !showPassword)}
|
||||||
color={showPassword ? 'success' : 'danger'}
|
color={showPassword ? 'success' : 'danger'}
|
||||||
>{#if !showPassword}<EyeIcon></EyeIcon>{:else}<EyeSlashIcon
|
|
||||||
></EyeSlashIcon>{/if}</Button
|
|
||||||
>
|
>
|
||||||
</InputGroup>
|
{#if !showPassword}<EyeIcon />{:else}<EyeSlashIcon />{/if}
|
||||||
<FormText>{$_('section.settings.httpAuthText')}</FormText>
|
</Button>
|
||||||
</Col>
|
</SettingsInput>
|
||||||
</Row>
|
|
||||||
{/if}
|
{/if}
|
||||||
<Row>
|
|
||||||
<Label md={6} for="hostnamePrefix" size={$uiSettings.inputSize}
|
<SettingsInput
|
||||||
>{$_('section.settings.hostnamePrefix')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
bind:value={$settings.hostnamePrefix}
|
|
||||||
name="hostnamePrefix"
|
|
||||||
id="hostnamePrefix"
|
id="hostnamePrefix"
|
||||||
bsSize={$uiSettings.inputSize}
|
label={$_('section.settings.hostnamePrefix')}
|
||||||
required
|
bind:value={$settings.hostnamePrefix}
|
||||||
|
required={true}
|
||||||
minlength="1"
|
minlength="1"
|
||||||
></Input>
|
size={$uiSettings.inputSize}
|
||||||
</Col>
|
/>
|
||||||
</Row>
|
|
||||||
<Row>
|
<SettingsSelect
|
||||||
<Label md={6} for="wifiTxPower" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.wifiTxPower', { default: 'WiFi Tx Power' })}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<Input
|
|
||||||
type="select"
|
|
||||||
bind:value={$settings.txPower}
|
|
||||||
name="select"
|
|
||||||
id="wifiTxPower"
|
id="wifiTxPower"
|
||||||
bsSize={$uiSettings.inputSize}
|
label={$_('section.settings.wifiTxPower', { default: 'WiFi Tx Power' })}
|
||||||
class={$uiSettings.selectClass}
|
bind:value={$settings.txPower}
|
||||||
>
|
options={Array.from(wifiTxPowerMap.entries())}
|
||||||
{#each wifiTxPowerMap as [key, value]}
|
size={$uiSettings.inputSize}
|
||||||
<option {value}>{key}</option>
|
selectClass={$uiSettings.selectClass}
|
||||||
{/each}
|
helpText={$_('section.settings.wifiTxPowerText')}
|
||||||
</Input>
|
/>
|
||||||
<FormText>{$_('section.settings.wifiTxPowerText')}</FormText>
|
|
||||||
</Col>
|
<SettingsInput
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Label md={6} for="wpTimeout" size={$uiSettings.inputSize}
|
|
||||||
>{$_('section.settings.wpTimeout')}</Label
|
|
||||||
>
|
|
||||||
<Col md="6">
|
|
||||||
<InputGroup size={$uiSettings.inputSize}>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
id="wpTimeout"
|
id="wpTimeout"
|
||||||
|
label={$_('section.settings.wpTimeout')}
|
||||||
|
bind:value={$settings.wpTimeout}
|
||||||
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
step="1"
|
step="1"
|
||||||
bind:value={$settings.wpTimeout}
|
required={true}
|
||||||
required
|
suffix={$_('time.seconds')}
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
<InputGroupText>{$_('time.seconds')}</InputGroupText>
|
|
||||||
</InputGroup>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col md="6" xl="12" xxl="6">
|
<SettingsSwitch
|
||||||
<Input
|
|
||||||
id="otaEnabled"
|
id="otaEnabled"
|
||||||
bind:checked={$settings.otaEnabled}
|
bind:checked={$settings.otaEnabled}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label="{$_('section.settings.otaUpdates')} ({$_('restartRequired')})"
|
label="{$_('section.settings.otaUpdates')} ({$_('restartRequired')})"
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
<SettingsSwitch
|
||||||
<Col md="6" xl="12" xxl="6">
|
|
||||||
<Input
|
|
||||||
id="mdnsEnabled"
|
id="mdnsEnabled"
|
||||||
bind:checked={$settings.mdnsEnabled}
|
bind:checked={$settings.mdnsEnabled}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label="{$_('section.settings.enableMdns')} ({$_('restartRequired')})"
|
label="{$_('section.settings.enableMdns')} ({$_('restartRequired')})"
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
<SettingsSwitch
|
||||||
<Col md="6" xl="12" xxl="6">
|
|
||||||
<Input
|
|
||||||
id="httpAuthEnabled"
|
id="httpAuthEnabled"
|
||||||
bind:checked={$settings.httpAuthEnabled}
|
bind:checked={$settings.httpAuthEnabled}
|
||||||
type="switch"
|
|
||||||
bsSize={$uiSettings.inputSize}
|
|
||||||
label="{$_('section.settings.httpAuthEnabled')} ({$_('restartRequired')})"
|
label="{$_('section.settings.httpAuthEnabled')} ({$_('restartRequired')})"
|
||||||
|
size={$uiSettings.inputSize}
|
||||||
/>
|
/>
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
</ToggleHeader>
|
</ToggleHeader>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -104,8 +104,8 @@
|
||||||
export let xxl = xl;
|
export let xxl = xl;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Col {xs} {sm} {md} {lg} {xl} {xxl}>
|
<Col {xs} {sm} {md} {lg} {xl} {xxl} class="mb-4 mb-xl-0">
|
||||||
<Card>
|
<Card id="status">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{$_('section.status.title', { default: 'Status' })}</CardTitle>
|
<CardTitle>{$_('section.status.title', { default: 'Status' })}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
@ -151,7 +151,11 @@
|
||||||
<hr />
|
<hr />
|
||||||
{#if $status.data}
|
{#if $status.data}
|
||||||
<section class={lightMode ? 'lightMode' : 'darkMode'}>
|
<section class={lightMode ? 'lightMode' : 'darkMode'}>
|
||||||
<Rendered status={$status} className="btclock-wrapper"></Rendered>
|
<Rendered
|
||||||
|
status={$status}
|
||||||
|
className="btclock-wrapper"
|
||||||
|
verticalDesc={$settings.verticalDesc}
|
||||||
|
></Rendered>
|
||||||
</section>
|
</section>
|
||||||
{$_('section.status.screenCycle')}:
|
{$_('section.status.screenCycle')}:
|
||||||
<a
|
<a
|
||||||
|
|
BIN
static/bitaxe.webp
Normal file
BIN
static/bitaxe.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
@ -1,11 +1,11 @@
|
||||||
import adapter from '@sveltejs/adapter-static';
|
import adapter from '@sveltejs/adapter-static';
|
||||||
import preprocess from 'svelte-preprocess';
|
import { sveltePreprocess } from 'svelte-preprocess';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
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: preprocess({}),
|
preprocess: sveltePreprocess({}),
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
|
|
|
@ -1,114 +1,7 @@
|
||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
import { initMock, settingsJson, statusJson } from '../shared';
|
||||||
|
|
||||||
const statusJson = {
|
test.beforeEach(initMock);
|
||||||
currentScreen: 0,
|
|
||||||
numScreens: 7,
|
|
||||||
timerRunning: true,
|
|
||||||
espUptime: 4479,
|
|
||||||
espFreeHeap: 58508,
|
|
||||||
espHeapSize: 342108,
|
|
||||||
connectionStatus: { price: true, blocks: true },
|
|
||||||
rssi: -66,
|
|
||||||
data: ['BLOCK/HEIGHT', '8', '1', '8', '0', '2', '6'],
|
|
||||||
rendered: ['BLOCK/HEIGHT', '8', '1', '8', '0', '2', '6'],
|
|
||||||
leds: [
|
|
||||||
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
|
||||||
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
|
||||||
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
|
||||||
{ red: 0, green: 0, blue: 0, hex: '#000000' }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const settingsJson = {
|
|
||||||
numScreens: 7,
|
|
||||||
fgColor: 415029,
|
|
||||||
bgColor: 0,
|
|
||||||
timerSeconds: 1800,
|
|
||||||
timerRunning: true,
|
|
||||||
minSecPriceUpd: 30,
|
|
||||||
fullRefreshMin: 60,
|
|
||||||
wpTimeout: 600,
|
|
||||||
tzOffset: 0,
|
|
||||||
useBitcoinNode: false,
|
|
||||||
mempoolInstance: 'mempool.space',
|
|
||||||
ledTestOnPower: true,
|
|
||||||
ledFlashOnUpd: true,
|
|
||||||
ledBrightness: 128,
|
|
||||||
stealFocus: true,
|
|
||||||
mcapBigChar: true,
|
|
||||||
mdnsEnabled: true,
|
|
||||||
otaEnabled: true,
|
|
||||||
fetchEurPrice: false,
|
|
||||||
hostnamePrefix: 'btclock',
|
|
||||||
hostname: 'btclock-d60b14',
|
|
||||||
ip: '192.168.20.231',
|
|
||||||
txPower: 78,
|
|
||||||
gitRev: '25d8b92bcbc8938417c140355ea3ba99ff9eb4b7',
|
|
||||||
gitTag: '3.1.9',
|
|
||||||
bitaxeEnabled: false,
|
|
||||||
bitaxeHostname: 'bitaxe1',
|
|
||||||
nostrZapNotify: true,
|
|
||||||
hwRev: 'REV_A_EPD_2_13',
|
|
||||||
fsRev: '4c5d9616212b27e3f05c35370f0befcf2c5a04b2',
|
|
||||||
nostrZapPubkey: 'b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422',
|
|
||||||
lastBuildTime: '1700666677',
|
|
||||||
screens: [
|
|
||||||
{ id: 0, name: 'Block Height', enabled: true },
|
|
||||||
{ id: 1, name: 'Sats per dollar', enabled: true },
|
|
||||||
{ id: 2, name: 'Ticker', enabled: true },
|
|
||||||
{ id: 3, name: 'Time', enabled: true },
|
|
||||||
{ id: 4, name: 'Halving countdown', enabled: true },
|
|
||||||
{ id: 5, name: 'Market Cap', enabled: true }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await page.route('*/**/api/status', async (route) => {
|
|
||||||
await route.fulfill({ json: statusJson });
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.route('*/**/api/show/screen/1', async (route) => {
|
|
||||||
//if (route.request().url().includes('*/**/api/show/screen/1')) {
|
|
||||||
statusJson.currentScreen = 1;
|
|
||||||
statusJson.data = ['MSCW/TIME', ' ', ' ', '2', '6', '4', '4'];
|
|
||||||
statusJson.rendered = statusJson.data;
|
|
||||||
//}
|
|
||||||
|
|
||||||
await route.fulfill({ json: statusJson });
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.route('*/**/api/show/screen/2', async (route) => {
|
|
||||||
statusJson.currentScreen = 2;
|
|
||||||
statusJson.data = ['BTC/USD', '$', '3', '7', '8', '2', '4'];
|
|
||||||
statusJson.rendered = statusJson.data;
|
|
||||||
|
|
||||||
await route.fulfill({ json: statusJson });
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.route('*/**/api/show/screen/4', async (route) => {
|
|
||||||
statusJson.currentScreen = 4;
|
|
||||||
statusJson.data = ['BIT/COIN', 'HALV/ING', '0/YRS', '149/DAYS', '8/HRS', '30/MINS', 'TO/GO'];
|
|
||||||
statusJson.rendered = statusJson.data;
|
|
||||||
|
|
||||||
await route.fulfill({ json: statusJson });
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.route('*/**/api/settings', async (route) => {
|
|
||||||
await route.fulfill({ json: settingsJson });
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.route('**/events', (route) => {
|
|
||||||
const newStatus = statusJson;
|
|
||||||
newStatus.data = ['BLOCK/HEIGHT', '8', '0', '0', '8', '1', '5'];
|
|
||||||
|
|
||||||
// Respond with a custom SSE message
|
|
||||||
route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
contentType: 'text/event-stream',
|
|
||||||
json: `${JSON.stringify(newStatus)}\n\n`
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('index page has expected columns control, status, settings', async ({ page }) => {
|
test('index page has expected columns control, status, settings', async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
|
@ -181,6 +74,8 @@ test('time values can not be zero or negative', async ({ page }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('info message when fetch eur price is enabled', async ({ page }) => {
|
test('info message when fetch eur price is enabled', async ({ page }) => {
|
||||||
|
delete (settingsJson as { actCurrencies?: string[] }).actCurrencies;
|
||||||
|
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await page.getByRole('button', { name: 'Show all' }).click();
|
await page.getByRole('button', { name: 'Show all' }).click();
|
||||||
|
|
132
tests/screenshots/viewport-screenshots.spec.ts
Normal file
132
tests/screenshots/viewport-screenshots.spec.ts
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { initMock, settingsJson, statusJson } from '../shared';
|
||||||
|
|
||||||
|
test.beforeEach(initMock);
|
||||||
|
|
||||||
|
// Define the translations for the headings
|
||||||
|
const headings = {
|
||||||
|
en: {
|
||||||
|
control: 'Control',
|
||||||
|
status: 'Status',
|
||||||
|
settings: 'Settings',
|
||||||
|
language: 'English'
|
||||||
|
},
|
||||||
|
de: {
|
||||||
|
control: 'Steuerung',
|
||||||
|
status: 'Status',
|
||||||
|
settings: 'Einstellungen',
|
||||||
|
language: 'Deutsch'
|
||||||
|
},
|
||||||
|
nl: {
|
||||||
|
control: 'Besturing',
|
||||||
|
status: 'Status',
|
||||||
|
settings: 'Instellingen',
|
||||||
|
language: 'Nederlands'
|
||||||
|
},
|
||||||
|
es: {
|
||||||
|
control: 'Control',
|
||||||
|
status: 'Estado',
|
||||||
|
settings: 'Ajustes',
|
||||||
|
language: 'Español'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test('capture screenshots across devices', async ({ page }, testInfo) => {
|
||||||
|
// Get the locale from the browser or default to 'en'
|
||||||
|
const locale = testInfo.project.use?.locale?.split('-')[0].toLowerCase() || 'en';
|
||||||
|
const translations = headings[locale] || headings.en;
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByRole('heading', { name: translations.control })).toBeVisible();
|
||||||
|
await expect(page.getByRole('heading', { name: translations.status })).toBeVisible();
|
||||||
|
await expect(page.getByRole('heading', { name: translations.settings })).toBeVisible();
|
||||||
|
|
||||||
|
if (await page.locator('#nav-language-dropdown').isVisible()) {
|
||||||
|
await expect(page.getByRole('link', { name: translations.language })).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
const screenshot = await page.screenshot({
|
||||||
|
path: `./test-results/screenshots/default-${test.info().project.name.toLowerCase().replace(' ', '_')}.png`
|
||||||
|
});
|
||||||
|
|
||||||
|
await testInfo.attach(`default`, {
|
||||||
|
body: screenshot,
|
||||||
|
contentType: 'image/png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('capture screenshots across devices with bitaxe screens', async ({ page }, testInfo) => {
|
||||||
|
const locale = testInfo.project.use?.locale?.split('-')[0].toLowerCase() || 'en';
|
||||||
|
const translations = headings[locale] || headings.en;
|
||||||
|
|
||||||
|
settingsJson.screens = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: 'Block Height',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Time',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Halving countdown',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'Block Fee Rate',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: 'Sats per dollar',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 20,
|
||||||
|
name: 'Ticker',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 30,
|
||||||
|
name: 'Market Cap',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 80,
|
||||||
|
name: 'BitAxe Hashrate',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 81,
|
||||||
|
name: 'BitAxe Best Difficulty',
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
statusJson.data = ['mdi:bitaxe', '', 'mdi:pickaxe', '6', '3', '7', 'GH/S'];
|
||||||
|
statusJson.rendered = ['mdi:bitaxe', '', 'mdi:pickaxe', '6', '3', '7', 'GH/S'];
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
await expect(page.getByRole('heading', { name: translations.control })).toBeVisible();
|
||||||
|
await expect(page.getByRole('heading', { name: translations.status })).toBeVisible();
|
||||||
|
await expect(page.getByRole('heading', { name: translations.settings })).toBeVisible();
|
||||||
|
|
||||||
|
if (await page.locator('#nav-language-dropdown').isVisible()) {
|
||||||
|
await expect(page.getByRole('link', { name: translations.language })).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.screenshot({
|
||||||
|
path: `./test-results/screenshots/bitaxe-${test.info().project.name.toLowerCase().replace(' ', '_')}.png`
|
||||||
|
});
|
||||||
|
|
||||||
|
await testInfo.attach(`bitaxe`, {
|
||||||
|
path: `./test-results/screenshots/bitaxe-${test.info().project.name.toLowerCase().replace(' ', '_')}.png`,
|
||||||
|
contentType: 'image/png'
|
||||||
|
});
|
||||||
|
});
|
143
tests/shared.ts
Normal file
143
tests/shared.ts
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
export const statusJson = {
|
||||||
|
currentScreen: 0,
|
||||||
|
numScreens: 7,
|
||||||
|
timerRunning: true,
|
||||||
|
espUptime: 4479,
|
||||||
|
espFreeHeap: 58508,
|
||||||
|
espHeapSize: 342108,
|
||||||
|
connectionStatus: { price: true, blocks: true },
|
||||||
|
rssi: -66,
|
||||||
|
data: ['BLOCK/HEIGHT', '8', '1', '8', '0', '2', '6'],
|
||||||
|
rendered: ['BLOCK/HEIGHT', '8', '1', '8', '0', '2', '6'],
|
||||||
|
leds: [
|
||||||
|
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
||||||
|
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
||||||
|
{ red: 0, green: 0, blue: 0, hex: '#000000' },
|
||||||
|
{ red: 0, green: 0, blue: 0, hex: '#000000' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const settingsJson = {
|
||||||
|
numScreens: 7,
|
||||||
|
fgColor: 415029,
|
||||||
|
bgColor: 0,
|
||||||
|
timerSeconds: 1800,
|
||||||
|
timerRunning: true,
|
||||||
|
minSecPriceUpd: 30,
|
||||||
|
fullRefreshMin: 60,
|
||||||
|
wpTimeout: 600,
|
||||||
|
tzOffset: 0,
|
||||||
|
useBitcoinNode: false,
|
||||||
|
mempoolInstance: 'mempool.space',
|
||||||
|
ledTestOnPower: true,
|
||||||
|
ledFlashOnUpd: true,
|
||||||
|
ledBrightness: 128,
|
||||||
|
stealFocus: true,
|
||||||
|
mcapBigChar: true,
|
||||||
|
mdnsEnabled: true,
|
||||||
|
otaEnabled: true,
|
||||||
|
fetchEurPrice: false,
|
||||||
|
hostnamePrefix: 'btclock',
|
||||||
|
hostname: 'btclock-d60b14',
|
||||||
|
ip: '192.168.20.231',
|
||||||
|
txPower: 78,
|
||||||
|
gitRev: '25d8b92bcbc8938417c140355ea3ba99ff9eb4b7',
|
||||||
|
gitTag: '3.1.9',
|
||||||
|
bitaxeEnabled: false,
|
||||||
|
bitaxeHostname: 'bitaxe1',
|
||||||
|
miningPoolStats: false,
|
||||||
|
miningPoolName: 'ocean',
|
||||||
|
miningPoolUser: '38Qkkei3SuF1Eo45BaYmRHUneRD54yyTFy',
|
||||||
|
nostrZapNotify: true,
|
||||||
|
hwRev: 'REV_A_EPD_2_13',
|
||||||
|
fsRev: '4c5d9616212b27e3f05c35370f0befcf2c5a04b2',
|
||||||
|
nostrZapPubkey: 'b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422',
|
||||||
|
lastBuildTime: '1700666677',
|
||||||
|
screens: [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: 'Block Height',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Time',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Halving countdown',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'Block Fee Rate',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: 'Sats per dollar',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 20,
|
||||||
|
name: 'Ticker',
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 30,
|
||||||
|
name: 'Market Cap',
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
actCurrencies: ['USD', 'EUR'],
|
||||||
|
availableCurrencies: ['USD', 'EUR', 'GBP', 'JPY', 'AUD', 'CAD']
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initMock = async ({ page }) => {
|
||||||
|
await page.route('*/**/api/status', async (route) => {
|
||||||
|
await route.fulfill({ json: statusJson });
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route('*/**/api/show/screen/1', async (route) => {
|
||||||
|
//if (route.request().url().includes('*/**/api/show/screen/1')) {
|
||||||
|
statusJson.currentScreen = 1;
|
||||||
|
statusJson.data = ['MSCW/TIME', ' ', ' ', '2', '6', '4', '4'];
|
||||||
|
statusJson.rendered = statusJson.data;
|
||||||
|
//}
|
||||||
|
|
||||||
|
await route.fulfill({ json: statusJson });
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route('*/**/api/show/screen/2', async (route) => {
|
||||||
|
statusJson.currentScreen = 2;
|
||||||
|
statusJson.data = ['BTC/USD', '$', '3', '7', '8', '2', '4'];
|
||||||
|
statusJson.rendered = statusJson.data;
|
||||||
|
|
||||||
|
await route.fulfill({ json: statusJson });
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route('*/**/api/show/screen/4', async (route) => {
|
||||||
|
statusJson.currentScreen = 4;
|
||||||
|
statusJson.data = ['BIT/COIN', 'HALV/ING', '0/YRS', '149/DAYS', '8/HRS', '30/MINS', 'TO/GO'];
|
||||||
|
statusJson.rendered = statusJson.data;
|
||||||
|
|
||||||
|
await route.fulfill({ json: statusJson });
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route('*/**/api/settings', async (route) => {
|
||||||
|
await route.fulfill({ json: settingsJson });
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route('**/events', (route) => {
|
||||||
|
const newStatus = statusJson;
|
||||||
|
newStatus.data = ['BLOCK/HEIGHT', '8', '0', '0', '8', '1', '5'];
|
||||||
|
|
||||||
|
// Respond with a custom SSE message
|
||||||
|
route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'text/event-stream',
|
||||||
|
json: `${JSON.stringify(newStatus)}\n\n`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vite';
|
||||||
import GithubActionsReporter from 'vitest-github-actions-reporter';
|
import GithubActionsReporter from 'vitest-github-actions-reporter';
|
||||||
|
// import { visualizer } from 'rollup-plugin-visualizer';
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
@ -65,14 +66,34 @@ export default defineConfig({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// visualizer({
|
||||||
|
// emitFile: true,
|
||||||
|
// filename: "stats.html",
|
||||||
|
// })
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
minify: true,
|
minify: 'esbuild',
|
||||||
cssCodeSplit: false,
|
cssCodeSplit: false,
|
||||||
|
chunkSizeWarningLimit: 550,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
manualChunks: () => 'app',
|
// assetFileNames: '[hash][extname]',
|
||||||
assetFileNames: '[name][extname]'
|
entryFileNames: `[hash][extname]`,
|
||||||
|
chunkFileNames: `[hash][extname]`,
|
||||||
|
assetFileNames: `[hash][extname]`,
|
||||||
|
preserveModules: false,
|
||||||
|
|
||||||
|
manualChunks: () => {
|
||||||
|
return 'app';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
quietDeps: true,
|
||||||
|
silenceDeprecations: ['import']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -81,5 +102,8 @@ export default defineConfig({
|
||||||
globals: true,
|
globals: true,
|
||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
reporters: process.env.GITHUB_ACTIONS ? ['default', new GithubActionsReporter()] : 'default'
|
reporters: process.env.GITHUB_ACTIONS ? ['default', new GithubActionsReporter()] : 'default'
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue