💄 Dashboard: update settings tabs layout, integrate account tab

This commit is contained in:
itskovacs 2025-10-28 18:20:12 +01:00
parent 040f58fafe
commit 6861860b9d
2 changed files with 174 additions and 91 deletions

View File

@ -167,10 +167,10 @@
<i class="pi pi-info-circle"></i><span class="font-bold whitespace-nowrap">About</span> <i class="pi pi-info-circle"></i><span class="font-bold whitespace-nowrap">About</span>
</p-tab> </p-tab>
<p-tab [value]="1" class="flex items-center gap-2"> <p-tab [value]="1" class="flex items-center gap-2">
<i class="pi pi-sliders-v"></i><span class="font-bold whitespace-nowrap">Tweaks</span> <i class="pi pi-shield"></i><span class="font-bold whitespace-nowrap">Account</span>
</p-tab> </p-tab>
<p-tab [value]="2" class="flex items-center gap-2"> <p-tab [value]="2" class="flex items-center gap-2">
<i class="pi pi-map"></i><span class="font-bold whitespace-nowrap">Map</span> <i class="pi pi-sliders-v"></i><span class="font-bold whitespace-nowrap">Preferences</span>
</p-tab> </p-tab>
<p-tab [value]="3" class="flex items-center gap-2"> <p-tab [value]="3" class="flex items-center gap-2">
<i class="pi pi-th-large"></i><span class="font-bold whitespace-nowrap">Categories</span> <i class="pi pi-th-large"></i><span class="font-bold whitespace-nowrap">Categories</span>
@ -181,11 +181,12 @@
</p-tablist> </p-tablist>
<p-tabpanels> <p-tabpanels>
<p-tabpanel [value]="0"> <p-tabpanel [value]="0">
<div class="mt-2 flex justify-between align-items"> <div class="mt-2 flex justify-between items-center">
<h1 class="font-semibold tracking-tight text-xl">About</h1> <h1 class="font-semibold tracking-tight text-xl">About</h1>
<div class="flex"> <div class="flex">
<p-button (click)="logout()" text severity="primary" icon="pi pi-sign-out" label="Log out" /> <p-button (click)="toggleDarkMode()" text [icon]="settings?.mode_dark ? 'pi pi-sun' : 'pi pi-moon'"
size="large" />
<p-button (click)="toGithub()" text severity="primary" icon="pi pi-github" size="large" /> <p-button (click)="toGithub()" text severity="primary" icon="pi pi-github" size="large" />
</div> </div>
</div> </div>
@ -221,35 +222,80 @@
<div class="mt-4 text-center text-sm text-gray-500 dark:text-gray-400">Made with ❤️ in BZH</div> <div class="mt-4 text-center text-sm text-gray-500 dark:text-gray-400">Made with ❤️ in BZH</div>
</p-tabpanel> </p-tabpanel>
<p-tabpanel [value]="1"> <p-tabpanel [value]="1">
<div class="mt-4"> <div class="mt-2 flex justify-between items-center">
<h1 class="font-semibold tracking-tight text-xl">Low Network Mode</h1> <h1 class="font-semibold tracking-tight text-xl">Account</h1>
<span class="text-xs text-gray-500 dark:text-gray-400">You can disable Low Network Mode. Default is true.
Display Category <div class="flex">
image instead of Place image.</span> <p-button (click)="logout()" text severity="primary" icon="pi pi-sign-out" label="Log out" />
</div> </div>
<div class="mt-2 flex justify-between"> </div>
<div>Low Network Mode</div>
<div class="mt-6 flex justify-between items-center">
<div class="min-w-0 truncate">
<h1 class="font-semibold tracking-tight text-xl">TOTP</h1>
<span class="text-xs text-gray-500 dark:text-gray-400">Add a second layer of security to your
account</span>
</div>
<p-button [icon]="settings?.totp_enabled ? 'pi pi-lock' : 'pi pi-lock-open'" (click)="toggleTOTP()"
[severity]="settings?.totp_enabled ? 'success' : 'danger'"
[pTooltip]="settings?.totp_enabled ? 'Click to disable' : 'Click to enable'"
[label]="settings?.totp_enabled ? 'Enabled' : 'Disabled'" text />
</div>
</p-tabpanel>
<p-tabpanel [value]="2">
<section [formGroup]="settingsForm">
<div class="mt-4 flex justify-between items-center">
<div>
<h1 class="font-semibold tracking-tight text-xl">Map parameters</h1>
<span class="text-xs text-gray-500 dark:text-gray-400">You can customize the default view on map
loading</span>
</div>
<p-button icon="pi pi-ethereum" pTooltip="Set current map center as default"
(click)="setMapCenterToCurrent()" text />
</div>
<div class="grid grid-cols-2 gap-4 mt-4">
<p-floatlabel variant="in">
<input id="map_lat" formControlName="map_lat" pInputText fluid />
<label for="map_lat">Default Lat.</label>
</p-floatlabel>
<p-floatlabel variant="in">
<input id="map_lng" formControlName="map_lng" pInputText fluid />
<label for="map_lng">Default Long.</label>
</p-floatlabel>
<p-floatlabel variant="in" class="col-span-full">
<input id="tile_layer" formControlName="tile_layer" pInputText fluid />
<label for="tile_layer">Tile Layer</label>
</p-floatlabel>
</div>
</section>
<div class="mt-4 flex items-center justify-between">
<div class="truncate min-w-0">
<div class="font-semibold tracking-tight text-xl">Low Network Mode</div>
<span class="text-xs text-gray-500 dark:text-gray-400">You can disable Low Network Mode.
Default is true. Display Category image instead of Place image.</span>
</div>
<div>
<p-toggleswitch [(ngModel)]="isLowNet" (onChange)="toggleLowNet()" /> <p-toggleswitch [(ngModel)]="isLowNet" (onChange)="toggleLowNet()" />
</div> </div>
<div class="mt-4">
<h1 class="font-semibold tracking-tight text-xl">Dark Mode</h1>
</div>
<div class="mt-2 flex justify-between">
<div>Enable Dark mode</div>
<p-toggleswitch [(ngModel)]="isDarkMode" (onChange)="toggleDarkMode()" />
</div> </div>
<div class="mt-4"> <div class="mt-4 flex items-center justify-between">
<h1 class="font-semibold tracking-tight text-xl">GPX Indication</h1> <div class="truncate min-w-0">
<span class="text-xs text-gray-500 dark:text-gray-400">If enabled, display a compass in Place bubble when <div class="font-semibold tracking-tight text-xl">GPX Indication</div>
a <span class="text-xs text-gray-500 dark:text-gray-400 truncate">Display a compass in Place bubble when a
GPX is present.</span> GPX is present.</span>
</div> </div>
<div class="mt-2 flex justify-between"> <div>
<div>Enable GPX indication</div>
<p-toggleswitch [(ngModel)]="isGpxInPlaceMode" (onChange)="toggleGpxInPlace()" /> <p-toggleswitch [(ngModel)]="isGpxInPlaceMode" (onChange)="toggleGpxInPlace()" />
</div> </div>
</div>
<section [formGroup]="settingsForm"> <section [formGroup]="settingsForm">
<div class="mt-4"> <div class="mt-4">
@ -281,42 +327,6 @@
</div> </div>
</section> </section>
</p-tabpanel> </p-tabpanel>
<p-tabpanel [value]="2">
<section [formGroup]="settingsForm">
<div class="mt-4 flex justify-between items-center">
<div>
<h1 class="font-semibold tracking-tight text-xl">Map parameters</h1>
<span class="text-xs text-gray-500 dark:text-gray-400">You can customize the default view on map
loading</span>
</div>
<p-button icon="pi pi-ethereum" pTooltip="Set current map center as default"
(click)="setMapCenterToCurrent()" text />
</div>
<div class="grid grid-cols-2 gap-4 mt-4">
<p-floatlabel variant="in">
<input id="map_lat" formControlName="map_lat" pInputText fluid />
<label for="map_lat">Lat.</label>
</p-floatlabel>
<p-floatlabel variant="in">
<input id="map_lng" formControlName="map_lng" pInputText fluid />
<label for="map_lng">Long.</label>
</p-floatlabel>
<p-floatlabel variant="in" class="col-span-full">
<input id="tile_layer" formControlName="tile_layer" pInputText fluid />
<label for="tile_layer">Tile Layer</label>
</p-floatlabel>
</div>
<div class="mt-2 w-full text-right">
<p-button (click)="updateSettings()" label="Update" text
[disabled]="!settingsForm.valid || settingsForm.pristine" />
</div>
</section>
</p-tabpanel>
<p-tabpanel [value]="3"> <p-tabpanel [value]="3">
<div class="mt-1 p-2 mb-2 flex justify-between items-center"> <div class="mt-1 p-2 mb-2 flex justify-between items-center">
<div> <div>
@ -346,8 +356,7 @@
</div> </div>
</p-tabpanel> </p-tabpanel>
<p-tabpanel [value]="4"> <p-tabpanel [value]="4">
<section class="grid gap-4"> <div class="mt-1 py-2 mb-2 flex justify-between items-center">
<div class="mt-4 flex justify-between items-center">
<div> <div>
<h1 class="font-semibold tracking-tight text-xl">Export data</h1> <h1 class="font-semibold tracking-tight text-xl">Export data</h1>
<span class="text-xs text-gray-500 dark:text-gray-400">Start an export and download it</span> <span class="text-xs text-gray-500 dark:text-gray-400">Start an export and download it</span>
@ -365,13 +374,13 @@
</div> </div>
<div class="mt-4 flex justify-center"> <div class="mt-4 flex justify-center">
<p-button icon="pi pi-cloud-download" label="Create backup" severity="help" (click)="createBackup()" <p-button icon="pi pi-cloud-download" label="Create backup" severity="help" (click)="createBackup()" text />
text />
</div> </div>
<div class="mt-4 flex flex-col overflow-y-auto h-full max-h-[500px] gap-4">
@for (backup of backups; track backup.id) { @for (backup of backups; track backup.id) {
<div class="mt-4 flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex flex-col"> <div class="flex flex-col min-w-0">
<div class="truncate font-mono text-md text-gray-900 dark:text-gray-100">{{ backup.filename }}</div> <div class="truncate font-mono text-md text-gray-900 dark:text-gray-100">{{ backup.filename }}</div>
<div class="flex items-center gap-2 mt-1 text-xs font-mono text-gray-500 dark:text-gray-400"> <div class="flex items-center gap-2 mt-1 text-xs font-mono text-gray-500 dark:text-gray-400">
@if (backup.file_size) {<span>{{ backup.file_size | fileSize }}</span>} @if (backup.file_size) {<span>{{ backup.file_size | fileSize }}</span>}
@ -412,7 +421,7 @@
<p class="text-sm text-gray-500 dark:text-gray-400">No backup</p> <p class="text-sm text-gray-500 dark:text-gray-400">No backup</p>
</div> </div>
} }
</section> </div>
</p-tabpanel> </p-tabpanel>
</p-tabpanels> </p-tabpanels>
</p-tabs> </p-tabs>

View File

@ -32,6 +32,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { PlaceGPXComponent } from '../../shared/place-gpx/place-gpx.component'; import { PlaceGPXComponent } from '../../shared/place-gpx/place-gpx.component';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FileSizePipe } from '../../shared/filesize.pipe'; import { FileSizePipe } from '../../shared/filesize.pipe';
import { TotpVerifyModalComponent } from '../../modals/totp-verify-modal/totp-verify-modal.component';
export interface ContextMenuItem { export interface ContextMenuItem {
text: string; text: string;
@ -75,7 +76,6 @@ export class DashboardComponent implements OnInit, AfterViewInit {
searchInput = new FormControl(''); searchInput = new FormControl('');
info?: Info; info?: Info;
isLowNet = false; isLowNet = false;
isDarkMode = false;
isGpxInPlaceMode = false; isGpxInPlaceMode = false;
viewSettings = false; viewSettings = false;
@ -167,9 +167,8 @@ export class DashboardComponent implements OnInit, AfterViewInit {
this.activeCategories = new Set(categories.map((c) => c.name)); this.activeCategories = new Set(categories.map((c) => c.name));
this.isLowNet = !!settings.mode_low_network; this.isLowNet = !!settings.mode_low_network;
this.isDarkMode = !!settings.mode_dark;
this.isGpxInPlaceMode = !!settings.mode_gpx_in_place; this.isGpxInPlaceMode = !!settings.mode_gpx_in_place;
if (this.isDarkMode) this.utilsService.enableDarkMode(); if (settings.mode_dark) this.utilsService.enableDarkMode();
this.resetFilters(); this.resetFilters();
this.places = [...places]; this.places = [...places];
@ -595,9 +594,8 @@ export class DashboardComponent implements OnInit, AfterViewInit {
this.settings = resp.settings; this.settings = resp.settings;
this.isLowNet = !!resp.settings.mode_low_network; this.isLowNet = !!resp.settings.mode_low_network;
this.isDarkMode = !!resp.settings.mode_dark;
this.isGpxInPlaceMode = !!resp.settings.mode_gpx_in_place; this.isGpxInPlaceMode = !!resp.settings.mode_gpx_in_place;
if (this.isDarkMode) this.utilsService.enableDarkMode(); if (resp.settings.mode_dark) this.utilsService.enableDarkMode();
this.resetFilters(); this.resetFilters();
this.map?.remove(); this.map?.remove();
@ -866,7 +864,7 @@ export class DashboardComponent implements OnInit, AfterViewInit {
toggleDarkMode() { toggleDarkMode() {
if (!this.settings) return; if (!this.settings) return;
let data: Partial<Settings> = { mode_dark: this.isDarkMode }; let data: Partial<Settings> = { mode_dark: !this.settings.mode_dark };
// If user uses default tile, we also update tile_layer to dark/voyager // If user uses default tile, we also update tile_layer to dark/voyager
if ( if (
!this.settings.mode_dark && !this.settings.mode_dark &&
@ -906,4 +904,80 @@ export class DashboardComponent implements OnInit, AfterViewInit {
}, },
}); });
} }
toggleTOTP() {
if (this.settings?.totp_enabled) this.disableTOTP();
else this.enableTOTP();
}
enableTOTP() {
this.apiService
.enableTOTP()
.pipe(take(1))
.subscribe({
next: (secret) => {
let modal = this.dialogService.open(TotpVerifyModalComponent, {
header: 'Verify TOTP',
modal: true,
closable: true,
breakpoints: {
'640px': '90vw',
},
data: {
message:
"Add this secret to your authentication app.\nEnter the generated code below to verify it's correct",
token: secret.secret,
},
})!;
modal.onClose.subscribe({
next: (code: string) => {
if (code)
this.apiService.verifyTOTP(code).subscribe({
next: () => (this.settings!.totp_enabled = true),
});
},
error: () => this.utilsService.toast('error', 'Error', 'Error enabling TOTP'),
});
},
});
}
disableTOTP() {
const modal = this.dialogService.open(TotpVerifyModalComponent, {
header: 'Verify TOTP',
modal: true,
closable: true,
breakpoints: {
'640px': '90vw',
},
})!;
modal.onClose.subscribe({
next: (code: string) => {
if (!code) return;
const modal = this.dialogService.open(YesNoModalComponent, {
header: 'Confirm',
modal: true,
closable: true,
dismissableMask: true,
breakpoints: {
'640px': '90vw',
},
data: 'Are you sure you want to disable TOTP?',
})!;
modal.onClose.subscribe({
next: (bool: boolean) => {
if (!bool) return;
this.apiService.disableTOTP(code).subscribe({
next: () => (this.settings!.totp_enabled = false),
error: () => this.utilsService.toast('error', 'Error', 'Error disabling TOTP'),
});
},
});
},
});
}
} }