Auto start with system
Some checks failed
Queue Release Build / prepare (push) Has been cancelled
Queue Release Build / build-linux (push) Has been cancelled
Queue Release Build / build-windows (push) Has been cancelled
Queue Release Build / finalize (push) Has been cancelled
Deploy Web Apps / deploy (push) Successful in 6m2s
Some checks failed
Queue Release Build / prepare (push) Has been cancelled
Queue Release Build / build-linux (push) Has been cancelled
Queue Release Build / build-windows (push) Has been cancelled
Queue Release Build / finalize (push) Has been cancelled
Deploy Web Apps / deploy (push) Successful in 6m2s
This commit is contained in:
58
electron/app/auto-start.ts
Normal file
58
electron/app/auto-start.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { app } from 'electron';
|
||||
import AutoLaunch from 'auto-launch';
|
||||
import { readDesktopSettings } from '../desktop-settings';
|
||||
|
||||
let autoLauncher: AutoLaunch | null = null;
|
||||
|
||||
function resolveLaunchPath(): string {
|
||||
// AppImage runs from a temporary mount; APPIMAGE points to the real file path.
|
||||
const appImagePath = process.platform === 'linux'
|
||||
? String(process.env['APPIMAGE'] || '').trim()
|
||||
: '';
|
||||
|
||||
return appImagePath || process.execPath;
|
||||
}
|
||||
|
||||
function getAutoLauncher(): AutoLaunch | null {
|
||||
if (!app.isPackaged) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!autoLauncher) {
|
||||
autoLauncher = new AutoLaunch({
|
||||
name: app.getName(),
|
||||
path: resolveLaunchPath()
|
||||
});
|
||||
}
|
||||
|
||||
return autoLauncher;
|
||||
}
|
||||
|
||||
async function setAutoStartEnabled(enabled: boolean): Promise<void> {
|
||||
const launcher = getAutoLauncher();
|
||||
|
||||
if (!launcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentlyEnabled = await launcher.isEnabled();
|
||||
|
||||
if (currentlyEnabled === enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
await launcher.enable();
|
||||
return;
|
||||
}
|
||||
|
||||
await launcher.disable();
|
||||
}
|
||||
|
||||
export async function synchronizeAutoStartSetting(enabled = readDesktopSettings().autoStart): Promise<void> {
|
||||
try {
|
||||
await setAutoStartEnabled(enabled);
|
||||
} catch {
|
||||
// Auto-launch integration should never block app startup or settings saves.
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
import { cleanupLinuxScreenShareAudioRouting } from '../audio/linux-screen-share-routing';
|
||||
import { initializeDesktopUpdater, shutdownDesktopUpdater } from '../update/desktop-updater';
|
||||
import { synchronizeAutoStartSetting } from './auto-start';
|
||||
import {
|
||||
initializeDatabase,
|
||||
destroyDatabase,
|
||||
@@ -24,6 +25,7 @@ export function registerAppLifecycle(): void {
|
||||
setupCqrsHandlers();
|
||||
setupWindowControlHandlers();
|
||||
setupSystemHandlers();
|
||||
await synchronizeAutoStartSetting();
|
||||
initializeDesktopUpdater();
|
||||
await createWindow();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ export type AutoUpdateMode = 'auto' | 'off' | 'version';
|
||||
|
||||
export interface DesktopSettings {
|
||||
autoUpdateMode: AutoUpdateMode;
|
||||
autoStart: boolean;
|
||||
hardwareAcceleration: boolean;
|
||||
manifestUrls: string[];
|
||||
preferredVersion: string | null;
|
||||
@@ -19,6 +20,7 @@ export interface DesktopSettingsSnapshot extends DesktopSettings {
|
||||
|
||||
const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
||||
autoUpdateMode: 'auto',
|
||||
autoStart: true,
|
||||
hardwareAcceleration: true,
|
||||
manifestUrls: [],
|
||||
preferredVersion: null,
|
||||
@@ -81,6 +83,9 @@ export function readDesktopSettings(): DesktopSettings {
|
||||
|
||||
return {
|
||||
autoUpdateMode: normalizeAutoUpdateMode(parsed.autoUpdateMode),
|
||||
autoStart: typeof parsed.autoStart === 'boolean'
|
||||
? parsed.autoStart
|
||||
: DEFAULT_DESKTOP_SETTINGS.autoStart,
|
||||
vaapiVideoEncode: typeof parsed.vaapiVideoEncode === 'boolean'
|
||||
? parsed.vaapiVideoEncode
|
||||
: DEFAULT_DESKTOP_SETTINGS.vaapiVideoEncode,
|
||||
@@ -102,6 +107,9 @@ export function updateDesktopSettings(patch: Partial<DesktopSettings>): DesktopS
|
||||
};
|
||||
const nextSettings: DesktopSettings = {
|
||||
autoUpdateMode: normalizeAutoUpdateMode(mergedSettings.autoUpdateMode),
|
||||
autoStart: typeof mergedSettings.autoStart === 'boolean'
|
||||
? mergedSettings.autoStart
|
||||
: DEFAULT_DESKTOP_SETTINGS.autoStart,
|
||||
hardwareAcceleration: typeof mergedSettings.hardwareAcceleration === 'boolean'
|
||||
? mergedSettings.hardwareAcceleration
|
||||
: DEFAULT_DESKTOP_SETTINGS.hardwareAcceleration,
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
type DesktopUpdateServerContext
|
||||
} from '../update/desktop-updater';
|
||||
import { consumePendingDeepLink } from '../app/deep-links';
|
||||
import { synchronizeAutoStartSetting } from '../app/auto-start';
|
||||
|
||||
const DEFAULT_MIME_TYPE = 'application/octet-stream';
|
||||
const FILE_CLIPBOARD_FORMATS = [
|
||||
@@ -329,6 +330,7 @@ export function setupSystemHandlers(): void {
|
||||
ipcMain.handle('set-desktop-settings', async (_event, patch: Partial<DesktopSettings>) => {
|
||||
const snapshot = updateDesktopSettings(patch);
|
||||
|
||||
await synchronizeAutoStartSetting(snapshot.autoStart);
|
||||
await handleDesktopSettingsChanged();
|
||||
return snapshot;
|
||||
});
|
||||
|
||||
@@ -119,6 +119,7 @@ export interface ElectronAPI {
|
||||
consumePendingDeepLink: () => Promise<string | null>;
|
||||
getDesktopSettings: () => Promise<{
|
||||
autoUpdateMode: 'auto' | 'off' | 'version';
|
||||
autoStart: boolean;
|
||||
hardwareAcceleration: boolean;
|
||||
manifestUrls: string[];
|
||||
preferredVersion: string | null;
|
||||
@@ -132,12 +133,14 @@ export interface ElectronAPI {
|
||||
onAutoUpdateStateChanged: (listener: (state: DesktopUpdateState) => void) => () => void;
|
||||
setDesktopSettings: (patch: {
|
||||
autoUpdateMode?: 'auto' | 'off' | 'version';
|
||||
autoStart?: boolean;
|
||||
hardwareAcceleration?: boolean;
|
||||
manifestUrls?: string[];
|
||||
preferredVersion?: string | null;
|
||||
vaapiVideoEncode?: boolean;
|
||||
}) => Promise<{
|
||||
autoUpdateMode: 'auto' | 'off' | 'version';
|
||||
autoStart: boolean;
|
||||
hardwareAcceleration: boolean;
|
||||
manifestUrls: string[];
|
||||
preferredVersion: string | null;
|
||||
|
||||
50
package-lock.json
generated
50
package-lock.json
generated
@@ -24,6 +24,7 @@
|
||||
"@spartan-ng/cli": "^0.0.1-alpha.589",
|
||||
"@spartan-ng/ui-core": "^0.0.1-alpha.380",
|
||||
"@timephy/rnnoise-wasm": "^1.0.0",
|
||||
"auto-launch": "^5.0.6",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cytoscape": "^3.33.1",
|
||||
@@ -45,11 +46,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/build": "^21.0.4",
|
||||
"@angular/cli": "^21.2.1",
|
||||
"@angular/cli": "^21.0.4",
|
||||
"@angular/compiler-cli": "^21.0.0",
|
||||
"@eslint/js": "^9.39.3",
|
||||
"@stylistic/eslint-plugin-js": "^4.4.1",
|
||||
"@stylistic/eslint-plugin-ts": "^4.4.1",
|
||||
"@types/auto-launch": "^5.0.5",
|
||||
"@types/simple-peer": "^9.11.9",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"angular-eslint": "21.2.0",
|
||||
@@ -10816,6 +10818,13 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/auto-launch": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/auto-launch/-/auto-launch-5.0.5.tgz",
|
||||
"integrity": "sha512-/nGvQZSzM/pvCMCh4Gt2kIeiUmOP/cKGJbjlInI+A+5MoV/7XmT56DJ6EU8bqc3+ItxEe4UC2GVspmPzcCc8cg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||
@@ -12875,6 +12884,11 @@
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/applescript": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz",
|
||||
"integrity": "sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ=="
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
@@ -12968,6 +12982,22 @@
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/auto-launch": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/auto-launch/-/auto-launch-5.0.6.tgz",
|
||||
"integrity": "sha512-OgxiAm4q9EBf9EeXdPBiVNENaWE3jUZofwrhAkWjHDYGezu1k3FRZHU8V2FBxGuSJOHzKmTJEd0G7L7/0xDGFA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"applescript": "^1.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"untildify": "^3.0.2",
|
||||
"winreg": "1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.23",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
|
||||
@@ -22285,9 +22315,7 @@
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
@@ -23745,7 +23773,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -29571,6 +29598,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/untildify": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz",
|
||||
"integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/upath": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz",
|
||||
@@ -31161,6 +31197,12 @@
|
||||
"integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/winreg": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz",
|
||||
"integrity": "sha512-IHpzORub7kYlb8A43Iig3reOvlcBJGX9gZ0WycHhghHtA65X0LYnMRuJs+aH1abVnMJztQkvQNlltnbPi5aGIA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"@spartan-ng/cli": "^0.0.1-alpha.589",
|
||||
"@spartan-ng/ui-core": "^0.0.1-alpha.380",
|
||||
"@timephy/rnnoise-wasm": "^1.0.0",
|
||||
"auto-launch": "^5.0.6",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cytoscape": "^3.33.1",
|
||||
@@ -96,6 +97,7 @@
|
||||
"@eslint/js": "^9.39.3",
|
||||
"@stylistic/eslint-plugin-js": "^4.4.1",
|
||||
"@stylistic/eslint-plugin-ts": "^4.4.1",
|
||||
"@types/auto-launch": "^5.0.5",
|
||||
"@types/simple-peer": "^9.11.9",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"angular-eslint": "21.2.0",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
export type SettingsPage = 'network' | 'voice' | 'updates' | 'debugging' | 'server' | 'members' | 'bans' | 'permissions';
|
||||
export type SettingsPage = 'general' | 'network' | 'voice' | 'updates' | 'debugging' | 'server' | 'members' | 'bans' | 'permissions';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SettingsModalService {
|
||||
readonly isOpen = signal(false);
|
||||
readonly activePage = signal<SettingsPage>('network');
|
||||
readonly activePage = signal<SettingsPage>('general');
|
||||
readonly targetServerId = signal<string | null>(null);
|
||||
|
||||
open(page: SettingsPage = 'network', serverId?: string): void {
|
||||
open(page: SettingsPage = 'general', serverId?: string): void {
|
||||
this.activePage.set(page);
|
||||
this.targetServerId.set(serverId ?? null);
|
||||
this.isOpen.set(true);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<div class="space-y-6 max-w-xl">
|
||||
<section>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<ng-icon
|
||||
name="lucidePower"
|
||||
class="w-5 h-5 text-muted-foreground"
|
||||
/>
|
||||
<h4 class="text-sm font-semibold text-foreground">Application</h4>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-lg border border-border bg-secondary/20 p-4 transition-opacity"
|
||||
[class.opacity-60]="!isElectron"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-foreground">Launch on system startup</p>
|
||||
|
||||
@if (isElectron) {
|
||||
<p class="text-xs text-muted-foreground">Automatically start MetoYou when you sign in</p>
|
||||
} @else {
|
||||
<p class="text-xs text-muted-foreground">This setting is only available in the desktop app.</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<label
|
||||
class="relative inline-flex items-center"
|
||||
[class.cursor-pointer]="isElectron && !savingAutoStart()"
|
||||
[class.cursor-not-allowed]="!isElectron || savingAutoStart()"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
[checked]="autoStart()"
|
||||
[disabled]="!isElectron || savingAutoStart()"
|
||||
(change)="onAutoStartChange($event)"
|
||||
id="general-auto-start-toggle"
|
||||
aria-label="Toggle launch on startup"
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div
|
||||
class="w-10 h-5 bg-secondary rounded-full peer peer-checked:bg-primary peer-disabled:bg-muted/80 peer-disabled:after:bg-muted-foreground/40 peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all"
|
||||
></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,92 @@
|
||||
/* eslint-disable @typescript-eslint/member-ordering */
|
||||
import {
|
||||
Component,
|
||||
inject,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import { lucidePower } from '@ng-icons/lucide';
|
||||
|
||||
import { PlatformService } from '../../../../core/services/platform.service';
|
||||
|
||||
interface DesktopSettingsSnapshot {
|
||||
autoStart: boolean;
|
||||
}
|
||||
|
||||
interface GeneralSettingsElectronApi {
|
||||
getDesktopSettings?: () => Promise<DesktopSettingsSnapshot>;
|
||||
setDesktopSettings?: (patch: { autoStart?: boolean }) => Promise<DesktopSettingsSnapshot>;
|
||||
}
|
||||
|
||||
type GeneralSettingsWindow = Window & {
|
||||
electronAPI?: GeneralSettingsElectronApi;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-general-settings',
|
||||
standalone: true,
|
||||
imports: [CommonModule, NgIcon],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
lucidePower
|
||||
})
|
||||
],
|
||||
templateUrl: './general-settings.component.html'
|
||||
})
|
||||
export class GeneralSettingsComponent {
|
||||
private platform = inject(PlatformService);
|
||||
|
||||
readonly isElectron = this.platform.isElectron;
|
||||
autoStart = signal(false);
|
||||
savingAutoStart = signal(false);
|
||||
|
||||
constructor() {
|
||||
if (this.isElectron) {
|
||||
void this.loadDesktopSettings();
|
||||
}
|
||||
}
|
||||
|
||||
async onAutoStartChange(event: Event): Promise<void> {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const enabled = !!input.checked;
|
||||
const api = this.getElectronApi();
|
||||
|
||||
if (!this.isElectron || !api?.setDesktopSettings) {
|
||||
input.checked = this.autoStart();
|
||||
return;
|
||||
}
|
||||
|
||||
this.savingAutoStart.set(true);
|
||||
|
||||
try {
|
||||
const snapshot = await api.setDesktopSettings({ autoStart: enabled });
|
||||
|
||||
this.autoStart.set(snapshot.autoStart);
|
||||
} catch {
|
||||
input.checked = this.autoStart();
|
||||
} finally {
|
||||
this.savingAutoStart.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadDesktopSettings(): Promise<void> {
|
||||
const api = this.getElectronApi();
|
||||
|
||||
if (!api?.getDesktopSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const snapshot = await api.getDesktopSettings();
|
||||
|
||||
this.autoStart.set(snapshot.autoStart);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
private getElectronApi(): GeneralSettingsElectronApi | null {
|
||||
return typeof window !== 'undefined'
|
||||
? (window as GeneralSettingsWindow).electronAPI ?? null
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -116,6 +116,9 @@
|
||||
<div class="flex items-center justify-between px-6 py-4 border-b border-border flex-shrink-0">
|
||||
<h3 class="text-lg font-semibold text-foreground">
|
||||
@switch (activePage()) {
|
||||
@case ('general') {
|
||||
General
|
||||
}
|
||||
@case ('network') {
|
||||
Network
|
||||
}
|
||||
@@ -157,6 +160,9 @@
|
||||
<!-- Scrollable Content Area -->
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
@switch (activePage()) {
|
||||
@case ('general') {
|
||||
<app-general-settings />
|
||||
}
|
||||
@case ('network') {
|
||||
<app-network-settings />
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import { Room, UserRole } from '../../../core/models/index';
|
||||
import { findRoomMember } from '../../../store/rooms/room-members.helpers';
|
||||
import { WebRTCService } from '../../../core/services/webrtc.service';
|
||||
|
||||
import { GeneralSettingsComponent } from './general-settings/general-settings.component';
|
||||
import { NetworkSettingsComponent } from './network-settings/network-settings.component';
|
||||
import { VoiceSettingsComponent } from './voice-settings/voice-settings.component';
|
||||
import { ServerSettingsComponent } from './server-settings/server-settings.component';
|
||||
@@ -48,6 +49,7 @@ import { THIRD_PARTY_LICENSES, type ThirdPartyLicense } from './third-party-lice
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
NgIcon,
|
||||
GeneralSettingsComponent,
|
||||
NetworkSettingsComponent,
|
||||
VoiceSettingsComponent,
|
||||
UpdatesSettingsComponent,
|
||||
@@ -89,6 +91,9 @@ export class SettingsModalComponent {
|
||||
activePage = this.modal.activePage;
|
||||
|
||||
readonly globalPages: { id: SettingsPage; label: string; icon: string }[] = [
|
||||
{ id: 'general',
|
||||
label: 'General',
|
||||
icon: 'lucideSettings' },
|
||||
{ id: 'network',
|
||||
label: 'Network',
|
||||
icon: 'lucideGlobe' },
|
||||
|
||||
Reference in New Issue
Block a user