feat: Theme engine
big changes
This commit is contained in:
@@ -3,8 +3,12 @@ import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
computed,
|
||||
effect,
|
||||
inject,
|
||||
HostListener
|
||||
HostListener,
|
||||
signal,
|
||||
Type
|
||||
} from '@angular/core';
|
||||
import {
|
||||
Router,
|
||||
@@ -37,6 +41,11 @@ import {
|
||||
STORAGE_KEY_CURRENT_USER_ID,
|
||||
STORAGE_KEY_LAST_VISITED_ROUTE
|
||||
} from './core/constants';
|
||||
import {
|
||||
ThemeNodeDirective,
|
||||
ThemePickerOverlayComponent,
|
||||
ThemeService
|
||||
} from './domains/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -48,12 +57,17 @@ import {
|
||||
FloatingVoiceControlsComponent,
|
||||
SettingsModalComponent,
|
||||
DebugConsoleComponent,
|
||||
ScreenShareSourcePickerComponent
|
||||
ScreenShareSourcePickerComponent,
|
||||
ThemeNodeDirective,
|
||||
ThemePickerOverlayComponent
|
||||
],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss'
|
||||
})
|
||||
export class App implements OnInit, OnDestroy {
|
||||
private static readonly THEME_STUDIO_CONTROLS_MARGIN = 16;
|
||||
private static readonly TITLE_BAR_HEIGHT = 40;
|
||||
|
||||
store = inject(Store);
|
||||
currentRoom = this.store.selectSignal(selectCurrentRoom);
|
||||
desktopUpdates = inject(DesktopAppUpdateService);
|
||||
@@ -65,17 +79,120 @@ export class App implements OnInit, OnDestroy {
|
||||
private notifications = inject(NotificationsFacade);
|
||||
private settingsModal = inject(SettingsModalService);
|
||||
private timeSync = inject(TimeSyncService);
|
||||
private theme = inject(ThemeService);
|
||||
private voiceSession = inject(VoiceSessionFacade);
|
||||
private externalLinks = inject(ExternalLinkService);
|
||||
private electronBridge = inject(ElectronBridgeService);
|
||||
private deepLinkCleanup: (() => void) | null = null;
|
||||
private themeStudioControlsDragOffset: { x: number; y: number } | null = null;
|
||||
private themeStudioControlsBounds: { width: number; height: number } | null = null;
|
||||
readonly themeStudioFullscreenComponent = signal<Type<unknown> | null>(null);
|
||||
readonly themeStudioControlsPosition = signal<{ x: number; y: number } | null>(null);
|
||||
readonly isDraggingThemeStudioControls = signal(false);
|
||||
|
||||
readonly appShellLayoutStyles = computed(() => this.theme.getLayoutContainerStyles('appShell'));
|
||||
readonly serversRailLayoutStyles = computed(() => this.theme.getLayoutItemStyles('serversRail'));
|
||||
readonly appWorkspaceLayoutStyles = computed(() => this.theme.getLayoutItemStyles('appWorkspace'));
|
||||
readonly isThemeStudioFullscreen = computed(() => {
|
||||
return this.settingsModal.isOpen()
|
||||
&& this.settingsModal.activePage() === 'theme'
|
||||
&& this.settingsModal.themeStudioFullscreen();
|
||||
});
|
||||
readonly isThemeStudioMinimized = computed(() => {
|
||||
return this.settingsModal.activePage() === 'theme'
|
||||
&& this.settingsModal.themeStudioMinimized();
|
||||
});
|
||||
readonly appWorkspaceShellStyles = computed(() => {
|
||||
const workspaceStyles = this.appWorkspaceLayoutStyles();
|
||||
|
||||
if (!this.isThemeStudioFullscreen()) {
|
||||
return workspaceStyles;
|
||||
}
|
||||
|
||||
return {
|
||||
...workspaceStyles,
|
||||
gridColumn: '1 / -1',
|
||||
gridRow: '1 / -1'
|
||||
};
|
||||
});
|
||||
readonly themeStudioControlsPositionStyles = computed(() => {
|
||||
const position = this.themeStudioControlsPosition();
|
||||
|
||||
if (!position) {
|
||||
return {
|
||||
right: `${App.THEME_STUDIO_CONTROLS_MARGIN}px`,
|
||||
bottom: `${App.THEME_STUDIO_CONTROLS_MARGIN}px`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
left: `${position.x}px`,
|
||||
top: `${position.y}px`
|
||||
};
|
||||
});
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
if (!this.isThemeStudioFullscreen() || this.themeStudioFullscreenComponent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
void import('./domains/theme/feature/settings/theme-settings.component')
|
||||
.then((module) => {
|
||||
this.themeStudioFullscreenComponent.set(module.ThemeSettingsComponent);
|
||||
});
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
if (this.isThemeStudioFullscreen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDraggingThemeStudioControls.set(false);
|
||||
this.themeStudioControlsDragOffset = null;
|
||||
this.themeStudioControlsBounds = null;
|
||||
});
|
||||
}
|
||||
|
||||
@HostListener('document:click', ['$event'])
|
||||
onGlobalLinkClick(evt: MouseEvent): void {
|
||||
this.externalLinks.handleClick(evt);
|
||||
}
|
||||
|
||||
@HostListener('document:keydown', ['$event'])
|
||||
onGlobalKeyDown(evt: KeyboardEvent): void {
|
||||
this.theme.handleGlobalShortcut(evt);
|
||||
}
|
||||
|
||||
@HostListener('document:pointermove', ['$event'])
|
||||
onDocumentPointerMove(event: PointerEvent): void {
|
||||
if (!this.isDraggingThemeStudioControls() || !this.themeStudioControlsDragOffset || !this.themeStudioControlsBounds) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.themeStudioControlsPosition.set(this.clampThemeStudioControlsPosition(
|
||||
event.clientX - this.themeStudioControlsDragOffset.x,
|
||||
event.clientY - this.themeStudioControlsDragOffset.y,
|
||||
this.themeStudioControlsBounds.width,
|
||||
this.themeStudioControlsBounds.height
|
||||
));
|
||||
}
|
||||
|
||||
@HostListener('document:pointerup')
|
||||
@HostListener('document:pointercancel')
|
||||
onDocumentPointerEnd(): void {
|
||||
if (!this.isDraggingThemeStudioControls()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDraggingThemeStudioControls.set(false);
|
||||
this.themeStudioControlsDragOffset = null;
|
||||
this.themeStudioControlsBounds = null;
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.theme.initialize();
|
||||
|
||||
void this.desktopUpdates.initialize();
|
||||
|
||||
await this.databaseService.initialize();
|
||||
@@ -143,6 +260,45 @@ export class App implements OnInit, OnDestroy {
|
||||
this.settingsModal.open('updates');
|
||||
}
|
||||
|
||||
startThemeStudioControlsDrag(event: PointerEvent, controlsElement: HTMLElement): void {
|
||||
if (event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = controlsElement.getBoundingClientRect();
|
||||
|
||||
this.themeStudioControlsBounds = {
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
};
|
||||
this.themeStudioControlsDragOffset = {
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top
|
||||
};
|
||||
this.themeStudioControlsPosition.set({
|
||||
x: rect.left,
|
||||
y: rect.top
|
||||
});
|
||||
this.isDraggingThemeStudioControls.set(true);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
reopenThemeStudio(): void {
|
||||
this.settingsModal.restoreMinimizedThemeStudio();
|
||||
}
|
||||
|
||||
minimizeThemeStudio(): void {
|
||||
this.settingsModal.minimizeThemeStudio();
|
||||
}
|
||||
|
||||
dismissMinimizedThemeStudio(): void {
|
||||
this.settingsModal.dismissMinimizedThemeStudio();
|
||||
}
|
||||
|
||||
closeThemeStudio(): void {
|
||||
this.settingsModal.close();
|
||||
}
|
||||
|
||||
async refreshDesktopUpdateContext(): Promise<void> {
|
||||
await this.desktopUpdates.refreshServerContext();
|
||||
}
|
||||
@@ -151,6 +307,18 @@ export class App implements OnInit, OnDestroy {
|
||||
await this.desktopUpdates.restartToApplyUpdate();
|
||||
}
|
||||
|
||||
private clampThemeStudioControlsPosition(x: number, y: number, width: number, height: number): { x: number; y: number } {
|
||||
const minX = App.THEME_STUDIO_CONTROLS_MARGIN;
|
||||
const minY = App.TITLE_BAR_HEIGHT + App.THEME_STUDIO_CONTROLS_MARGIN;
|
||||
const maxX = Math.max(minX, window.innerWidth - width - App.THEME_STUDIO_CONTROLS_MARGIN);
|
||||
const maxY = Math.max(minY, window.innerHeight - height - App.THEME_STUDIO_CONTROLS_MARGIN);
|
||||
|
||||
return {
|
||||
x: Math.min(Math.max(minX, x), maxX),
|
||||
y: Math.min(Math.max(minY, y), maxY)
|
||||
};
|
||||
}
|
||||
|
||||
private async setupDesktopDeepLinks(): Promise<void> {
|
||||
const electronApi = this.electronBridge.getApi();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user