Eve/src/routes/Settings.ts
Danny Morabito 6434102635
- Convert ANSI-formatted relay logs to HTML for cleaner visualization in initial setup screen
- Add relay logs section to settings interface for improved accessibility
2025-04-03 13:47:43 +02:00

201 lines
6.2 KiB
TypeScript

import defaultAvatar from '@/default-avatar.png';
import { getSigner, getUserProfile, ndk } from '@/ndk';
import type { ArxInputChangeEvent } from '@components/General/Input';
import { NDKEvent, type NDKUserProfile } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';
import '@components/Breadcrumbs';
import '@components/DateTimeSettings';
import '@components/General/Button';
import '@components/General/Card';
import '@components/General/Fieldset';
import '@components/General/Input';
import '@components/RelayLogs';
@customElement('arx-settings')
export class EveSettings extends LitElement {
static override styles = css`
:host {
display: block;
font-family: var(--font-family, "Inter", system-ui, sans-serif);
margin: 0 auto;
line-height: 1.6;
color: var(--color-base-content);
background-color: var(--color-base-100);
}
.profile-image {
width: 140px;
height: 140px;
border-radius: 50%;
object-fit: cover;
border: calc(var(--border) * 2) solid var(--color-base-100);
box-shadow: calc(var(--depth) * 3px) calc(var(--depth) * 3px)
calc(var(--depth) * 6px)
oklch(from var(--color-base-content) l c h / 0.15),
calc(var(--depth) * -2px) calc(var(--depth) * -2px)
calc(var(--depth) * 4px) oklch(from var(--color-base-100) l c h / 0.6);
transition: transform 0.3s, box-shadow 0.3s;
margin: 0 auto;
}
.profile-image:hover {
transform: scale(1.05);
box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px)
calc(var(--depth) * 8px) oklch(from var(--color-accent) l c h / 0.3),
calc(var(--depth) * -2px) calc(var(--depth) * -2px)
calc(var(--depth) * 6px) oklch(from var(--color-base-100) l c h / 0.6);
}
.profile-header {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
`;
@state() private loading = true;
@state() private saving = false;
@state() private profile: NDKUserProfile | undefined;
@state() private error: string | undefined;
@state() private darkMode = false;
@state() private relayStatus: { running: boolean; pid: number | null; logs: string[] } = {
running: false,
pid: null,
logs: [],
};
private async updateRelayStatus() {
this.relayStatus = await window.relay.getStatus();
if (this.relayStatus.running) setTimeout(() => this.updateRelayStatus(), 2000);
}
protected override async firstUpdated() {
try {
this.profile = await getUserProfile();
this.darkMode = localStorage.getItem('darkMode') === 'true';
this.updateRelayStatus();
this.loading = false;
} catch (err) {
this.error = 'Failed to load profile';
console.error(err);
this.loading = false;
}
}
private handleInputChange(field: string, e: ArxInputChangeEvent) {
this.profile = {
...this.profile,
[field]: e.detail.value,
};
}
private async saveProfile() {
if (this.saving) return;
this.saving = true;
try {
await getSigner();
const event = new NDKEvent(ndk);
event.kind = 0;
event.content = JSON.stringify(this.profile);
await event.sign();
await event.publish();
} catch (err) {
alert(err);
console.error(err);
} finally {
this.saving = false;
}
}
private toggleDarkMode() {
this.darkMode = !this.darkMode;
localStorage.setItem('darkMode', this.darkMode.toString());
document.body.classList.toggle('dark', this.darkMode);
}
override render() {
if (this.error) return html`<arx-error-view .error=${this.error}></arx-error-view>`;
if (this.loading) return html`<arx-loading-view></arx-loading-view>`;
const breadcrumbItems = [{ text: 'Home', href: '/' }, { text: 'Settings' }];
return html`
<arx-breadcrumbs .items=${breadcrumbItems}></arx-breadcrumbs>
<arx-card>
<arx-fieldset legend="Dark Mode">
<arx-toggle
label="Dark Mode"
.checked=${this.darkMode}
@change=${() => this.toggleDarkMode()}
></arx-toggle>
</arx-fieldset>
<arx-fieldset legend="Profile">
${when(
this.profile!.picture,
() => html`
<div class="profile-header">
<img
class="profile-image"
src=${this.profile!.picture}
alt="Profile"
@error=${(e: Event) => {
(e.target as HTMLImageElement).src = defaultAvatar;
}}
/>
</div>
`,
)}
<arx-input
label="Name"
type="text"
name="name"
.value=${this.profile!.name}
@change=${(e: ArxInputChangeEvent) => this.handleInputChange('name', e)}
placeholder="Your display name"
></arx-input>
<arx-input
label="Profile Image URL"
type="text"
name="image"
.value=${this.profile!.picture}
@change=${(e: ArxInputChangeEvent) => this.handleInputChange('picture', e)}
placeholder="https://example.com/your-image.jpg"
></arx-input>
<arx-input
label="Banner URL"
type="text"
name="banner"
.value=${this.profile!.banner}
@change=${(e: ArxInputChangeEvent) => this.handleInputChange('banner', e)}
placeholder="https://example.com/your-image.jpg"
></arx-input>
</arx-fieldset>
<arx-date-time-settings></arx-date-time-settings>
<arx-button
.label=${this.saving ? 'Saving...' : 'Save Changes'}
@click=${this.saveProfile}
?disabled=${this.saving}
>
</arx-button>
</arx-card>
<arx-card>
<arx-fieldset legend="Relay">
<p>
${this.relayStatus.running ? `Relay is running. PID: ${this.relayStatus.pid}` : 'Relay is not running'}
</p>
<arx-relay-logs .logs=${this.relayStatus.logs}></arx-relay-logs>
</arx-fieldset>
</arx-card>
`;
}
}