💄 Dashboard: update settings tabs layout, integrate account tab
This commit is contained in:
parent
040f58fafe
commit
6861860b9d
@ -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>
|
||||||
|
|||||||
@ -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'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user