import { Component, ChangeDetectionStrategy, input, output, signal, computed, effect, } from '@angular/core'; import { Page } from '../../models'; import { ToggleComponent } from '../shared/toggle/toggle.component'; export interface UpdatePagePayload { name: string; hide_create_tower_button: boolean; keep_tasks_open: boolean; } const UUIDV4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; @Component({ selector: 'lt-settings', standalone: true, imports: [ToggleComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `

Settings

@if (page()) {

This page


}

Account

Copy this token to another device to permanently sync your progress

Paste a token to switch accounts

@if (tokenInputTouched() && tokenInput() && !isValidToken()) {

Not a valid token. Must be a UUIDv4.

}
`, styles: ` @import '../../../library/main'; :host { display: block; } .card { @include card(); width: 66vw; max-width: 480px; @media (max-width: $mobile-width) { width: 88vw; max-width: 88vw; padding: var(--medium-padding); } box-sizing: border-box; padding: var(--large-padding); position: relative; box-shadow: $shadow; text-align: left; .exit { position: absolute; top: var(--medium-padding); right: var(--medium-padding); @include exit(); } h2 { margin: 0 0 var(--large-padding) 0; padding: 0 36px; line-height: 1.3; text-align: center; } h3 { margin: 0 0 var(--medium-padding) 0; font-size: var(--medium-font-size); line-height: 1.35; } section { @include inner-spacing(var(--medium-padding)); margin-bottom: var(--large-padding); &:last-child { margin-bottom: 0; } } hr { border: 0; border-top: 1px solid rgba(0, 0, 0, 0.08); margin: var(--large-padding) 0; } input[type='text'], button:not(.exit) { font-size: var(--medium-font-size); line-height: 1.35; } .toggle-list { display: flex; flex-direction: column; gap: var(--small-padding); } lt-toggle.setting-toggle { --toggle-label-width: 145px; box-sizing: border-box; justify-content: center; width: 100%; min-height: 52px; padding: var(--small-padding); border-radius: var(--border-radius); background: rgba($text-color, 0.035); @media (max-width: $mobile-width) { min-height: 48px; } } .hint { font-size: var(--medium-font-size); line-height: 1.35; color: rgba($text-color, 0.7); margin: 0 0 4px 0; } .token-row { display: flex; gap: var(--small-padding); align-items: center; input { flex: 1; min-width: 0; text-align: left; } button { margin: 0; flex: 0 0 auto; max-width: 100%; } @media (max-width: $mobile-width) { flex-wrap: wrap; input { width: 100%; } button { margin-left: auto; } } } input.error { box-shadow: 0 1px #b53f3f; } .error-message { color: #b53f3f; font-size: var(--small-font-size); margin-top: 4px; } button.danger { color: #b53f3f; border-bottom-color: #b53f3f55; &:after { background-color: #b53f3f; } } } `, }) export class SettingsComponent { readonly token = input.required(); readonly page = input(null); readonly close = output(); readonly switchAccount = output(); readonly updatePage = output(); readonly deletePage = output(); // Page-settings state — seeded from page() input readonly pageName = signal(''); readonly hideCreateTowerButton = signal(false); readonly keepTasksOpen = signal(false); // Token-switch state readonly tokenInput = signal(''); readonly tokenInputTouched = signal(false); readonly isValidToken = computed(() => UUIDV4_RE.test(this.tokenInput())); constructor() { effect(() => { const p = this.page(); if (p) { this.pageName.set(p.name); this.hideCreateTowerButton.set(p.hide_create_tower_button); this.keepTasksOpen.set(p.keep_tasks_open); } }); } onRenamePage(value: string): void { const trimmed = value.trim(); if (!trimmed) return; this.pageName.set(trimmed); this.flushPageUpdate(); } onHideCreateTowerButtonChange(value: boolean): void { this.hideCreateTowerButton.set(value); this.flushPageUpdate(); } onKeepTasksOpenChange(value: boolean): void { this.keepTasksOpen.set(value); this.flushPageUpdate(); } private flushPageUpdate(): void { this.updatePage.emit({ name: this.pageName(), hide_create_tower_button: this.hideCreateTowerButton(), keep_tasks_open: this.keepTasksOpen(), }); } onCopy(): void { navigator.clipboard.writeText(this.token()).catch(() => {}); } onTokenInput(value: string): void { this.tokenInput.set(value.trim()); } onSwitch(): void { if (!this.isValidToken()) return; this.switchAccount.emit(this.tokenInput()); this.close.emit(); } }