feat: Theme studio v2
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
<!-- eslint-disable @angular-eslint/template/button-has-type, @angular-eslint/template/cyclomatic-complexity -->
|
||||
<aside class="flex h-full min-h-0 flex-col bg-card">
|
||||
<div class="border-b border-border px-3 py-3">
|
||||
<div
|
||||
appThemeNode="roomPanelHeader"
|
||||
class="border-b border-border px-3 py-3"
|
||||
>
|
||||
@if (panelMode() === 'channels') {
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="grid h-9 w-9 place-items-center rounded-md bg-secondary text-sm font-semibold text-foreground">
|
||||
@@ -33,7 +36,10 @@
|
||||
@if (panelMode() === 'channels') {
|
||||
<div class="flex-1 overflow-auto">
|
||||
<!-- Text Channels -->
|
||||
<section class="px-2 py-3">
|
||||
<section
|
||||
appThemeNode="roomTextChannelsSection"
|
||||
class="px-2 py-3"
|
||||
>
|
||||
<div class="mb-2 flex items-center justify-between px-1">
|
||||
<h4 class="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">Text Channels</h4>
|
||||
@if (canManageChannels()) {
|
||||
@@ -52,6 +58,7 @@
|
||||
<div class="space-y-1">
|
||||
@for (ch of textChannels(); track ch.id) {
|
||||
<button
|
||||
appThemeNode="roomTextChannelItem"
|
||||
class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors"
|
||||
[class.bg-secondary]="activeChannelId() === ch.id"
|
||||
[class.text-foreground]="activeChannelId() === ch.id"
|
||||
@@ -94,7 +101,10 @@
|
||||
</section>
|
||||
|
||||
<!-- Voice Channels -->
|
||||
<section class="border-t border-border px-2 py-3">
|
||||
<section
|
||||
appThemeNode="roomVoiceChannelsSection"
|
||||
class="border-t border-border px-2 py-3"
|
||||
>
|
||||
<div class="mb-2 flex items-center justify-between px-1">
|
||||
<h4 class="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">Voice Channels</h4>
|
||||
@if (canManageChannels()) {
|
||||
@@ -125,6 +135,7 @@
|
||||
(drop)="onVoiceChannelDrop($event, ch.id)"
|
||||
>
|
||||
<button
|
||||
appThemeNode="roomVoiceChannelItem"
|
||||
class="flex w-full items-center justify-between rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-secondary/60"
|
||||
(click)="joinVoice(ch.id)"
|
||||
(contextmenu)="openChannelContextMenu($event, ch)"
|
||||
@@ -174,6 +185,7 @@
|
||||
>
|
||||
@for (u of voiceUsersInRoom(ch.id); track u.id) {
|
||||
<div
|
||||
appThemeNode="roomVoiceUserItem"
|
||||
class="flex items-center gap-2 rounded-md px-2 py-1.5 transition-colors hover:bg-secondary/50"
|
||||
[class.cursor-pointer]="canDragVoiceUser(u)"
|
||||
[class.opacity-60]="draggedVoiceUserId() === (u.id || u.oderId)"
|
||||
|
||||
@@ -37,6 +37,7 @@ import { MessagesActions } from '../../../store/messages/messages.actions';
|
||||
import { RealtimeSessionFacade } from '../../../core/realtime';
|
||||
import { ScreenShareFacade } from '../../../domains/screen-share';
|
||||
import { NotificationsFacade } from '../../../domains/notifications';
|
||||
import { ThemeNodeDirective } from '../../../domains/theme';
|
||||
import {
|
||||
VoiceActivityService,
|
||||
VoiceConnectionFacade,
|
||||
@@ -82,7 +83,8 @@ type PanelMode = 'channels' | 'users';
|
||||
ContextMenuComponent,
|
||||
UserVolumeMenuComponent,
|
||||
UserAvatarComponent,
|
||||
ConfirmDialogComponent
|
||||
ConfirmDialogComponent,
|
||||
ThemeNodeDirective
|
||||
],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
@@ -142,10 +144,8 @@ export class RoomsSidePanelComponent {
|
||||
const memberIdentifiers = this.roomMemberIdentifiers();
|
||||
const roomId = this.currentRoom()?.id;
|
||||
|
||||
return this.onlineUsers().filter((user) =>
|
||||
!this.isCurrentUserIdentity(user)
|
||||
&& this.matchesIdentifiers(memberIdentifiers, user)
|
||||
&& this.isUserPresentInRoom(user, roomId)
|
||||
return this.onlineUsers().filter(
|
||||
(user) => !this.isCurrentUserIdentity(user) && this.matchesIdentifiers(memberIdentifiers, user) && this.isUserPresentInRoom(user, roomId)
|
||||
);
|
||||
});
|
||||
offlineRoomMembers = computed(() => {
|
||||
@@ -661,9 +661,7 @@ export class RoomsSidePanelComponent {
|
||||
}
|
||||
|
||||
private handleVoiceJoinFailure(error: unknown): void {
|
||||
const message = error instanceof Error
|
||||
? error.message
|
||||
: 'Failed to join voice channel.';
|
||||
const message = error instanceof Error ? error.message : 'Failed to join voice channel.';
|
||||
|
||||
this.voiceConnection.reportConnectionError(message);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<nav class="relative flex h-full w-full flex-col items-center gap-2 border-r border-border bg-secondary/35 px-2 py-3">
|
||||
<!-- Create button -->
|
||||
<button
|
||||
appThemeNode="serversRailCreateButton"
|
||||
type="button"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-md bg-primary text-primary-foreground transition-colors hover:bg-primary/90"
|
||||
title="Create Server"
|
||||
@@ -17,7 +18,10 @@
|
||||
}
|
||||
|
||||
<!-- Saved servers icons -->
|
||||
<div class="no-scrollbar mt-2 flex w-full flex-1 flex-col items-center gap-2 overflow-y-auto pt-0.5">
|
||||
<div
|
||||
appThemeNode="serversRailList"
|
||||
class="no-scrollbar mt-2 flex w-full flex-1 flex-col items-center gap-2 overflow-y-auto pt-0.5"
|
||||
>
|
||||
@for (room of visibleSavedRooms(); track room.id) {
|
||||
<div class="group/server relative flex w-full justify-center">
|
||||
<span
|
||||
@@ -27,6 +31,7 @@
|
||||
></span>
|
||||
|
||||
<button
|
||||
appThemeNode="serversRailItem"
|
||||
type="button"
|
||||
class="relative z-10 flex h-10 w-10 cursor-pointer flex-shrink-0 items-center justify-center border border-transparent transition-[border-radius,box-shadow,background-color] duration-100 hover:rounded-lg hover:bg-card"
|
||||
[ngClass]="isSelectedRoom(room) ? 'rounded-lg ring-2 ring-primary/40 bg-primary/10' : 'rounded-xl bg-card'"
|
||||
|
||||
@@ -36,6 +36,7 @@ import { RoomsActions } from '../../../store/rooms/rooms.actions';
|
||||
import { DatabaseService } from '../../../infrastructure/persistence';
|
||||
import { NotificationsFacade } from '../../../domains/notifications';
|
||||
import { type ServerInfo, ServerDirectoryFacade } from '../../../domains/server-directory';
|
||||
import { ThemeNodeDirective } from '../../../domains/theme';
|
||||
import { hasRoomBanForUser } from '../../../domains/access-control';
|
||||
import {
|
||||
ConfirmDialogComponent,
|
||||
@@ -54,6 +55,7 @@ import {
|
||||
ContextMenuComponent,
|
||||
LeaveServerDialogComponent,
|
||||
NgOptimizedImage,
|
||||
ThemeNodeDirective,
|
||||
UserBarComponent
|
||||
],
|
||||
viewProviders: [provideIcons({ lucidePlus })],
|
||||
@@ -221,8 +223,7 @@ export class ServersRailComponent {
|
||||
return;
|
||||
|
||||
this.joinPasswordError.set(null);
|
||||
this.savedRoomJoinRequests.next({ room,
|
||||
password: this.joinPassword() });
|
||||
this.savedRoomJoinRequests.next({ room, password: this.joinPassword() });
|
||||
}
|
||||
|
||||
isRoomMarkedBanned(room: Room): boolean {
|
||||
@@ -264,10 +265,12 @@ export class ServersRailComponent {
|
||||
|
||||
const isCurrentRoom = this.currentRoom()?.id === ctx.id;
|
||||
|
||||
this.store.dispatch(RoomsActions.forgetRoom({
|
||||
roomId: ctx.id,
|
||||
nextOwnerKey: result.nextOwnerKey
|
||||
}));
|
||||
this.store.dispatch(
|
||||
RoomsActions.forgetRoom({
|
||||
roomId: ctx.id,
|
||||
nextOwnerKey: result.nextOwnerKey
|
||||
})
|
||||
);
|
||||
|
||||
if (isCurrentRoom) {
|
||||
this.router.navigate(['/search']);
|
||||
@@ -354,8 +357,7 @@ export class ServersRailComponent {
|
||||
this.prepareVoiceContext(room);
|
||||
this.closePasswordDialog();
|
||||
this.store.dispatch(RoomsActions.setSignalServerReconnecting({ isReconnecting: false }));
|
||||
this.store.dispatch(RoomsActions.viewServer({ room,
|
||||
skipBanCheck: true }));
|
||||
this.store.dispatch(RoomsActions.viewServer({ room, skipBanCheck: true }));
|
||||
}
|
||||
|
||||
private requestJoinInBackground(room: Room, password?: string) {
|
||||
@@ -377,13 +379,17 @@ export class ServersRailComponent {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
return this.serverDirectory.requestJoin({
|
||||
roomId: room.id,
|
||||
userId: currentUserId,
|
||||
userPublicKey: currentUser?.oderId || currentUserId,
|
||||
displayName: currentUser?.displayName || 'Anonymous',
|
||||
password: password?.trim() || undefined
|
||||
}, joinTarget.selector)
|
||||
return this.serverDirectory
|
||||
.requestJoin(
|
||||
{
|
||||
roomId: room.id,
|
||||
userId: currentUserId,
|
||||
userPublicKey: currentUser?.oderId || currentUserId,
|
||||
displayName: currentUser?.displayName || 'Anonymous',
|
||||
password: password?.trim() || undefined
|
||||
},
|
||||
joinTarget.selector
|
||||
)
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
this.closePasswordDialog();
|
||||
@@ -447,22 +453,22 @@ export class ServersRailComponent {
|
||||
const errorCode = serverError?.error?.errorCode;
|
||||
const status = serverError?.status;
|
||||
|
||||
return errorCode === 'SERVER_NOT_FOUND'
|
||||
|| status === 0
|
||||
|| status === 404
|
||||
|| (typeof status === 'number' && status >= 500);
|
||||
return errorCode === 'SERVER_NOT_FOUND' || status === 0 || status === 404 || (typeof status === 'number' && status >= 500);
|
||||
}
|
||||
|
||||
private toRoomRefreshChanges(room: Room, server: ServerInfo, signalingUrl?: string): Partial<Room> {
|
||||
const resolvedSource = this.serverDirectory.normaliseRoomSignalSource({
|
||||
sourceId: server.sourceId ?? room.sourceId,
|
||||
sourceName: server.sourceName ?? room.sourceName,
|
||||
sourceUrl: server.sourceUrl ?? room.sourceUrl,
|
||||
signalingUrl,
|
||||
fallbackName: server.sourceName ?? room.sourceName ?? room.name
|
||||
}, {
|
||||
ensureEndpoint: true
|
||||
});
|
||||
const resolvedSource = this.serverDirectory.normaliseRoomSignalSource(
|
||||
{
|
||||
sourceId: server.sourceId ?? room.sourceId,
|
||||
sourceName: server.sourceName ?? room.sourceName,
|
||||
sourceUrl: server.sourceUrl ?? room.sourceUrl,
|
||||
signalingUrl,
|
||||
fallbackName: server.sourceName ?? room.sourceName ?? room.name
|
||||
},
|
||||
{
|
||||
ensureEndpoint: true
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
name: server.name,
|
||||
@@ -472,15 +478,10 @@ export class ServersRailComponent {
|
||||
userCount: server.userCount,
|
||||
maxUsers: server.maxUsers,
|
||||
hasPassword:
|
||||
typeof server.hasPassword === 'boolean'
|
||||
? server.hasPassword
|
||||
: (typeof room.hasPassword === 'boolean' ? room.hasPassword : !!room.password),
|
||||
typeof server.hasPassword === 'boolean' ? server.hasPassword : typeof room.hasPassword === 'boolean' ? room.hasPassword : !!room.password,
|
||||
isPrivate: server.isPrivate,
|
||||
createdAt: server.createdAt,
|
||||
channels:
|
||||
Array.isArray(server.channels) && server.channels.length > 0
|
||||
? server.channels
|
||||
: room.channels,
|
||||
channels: Array.isArray(server.channels) && server.channels.length > 0 ? server.channels : room.channels,
|
||||
...resolvedSource
|
||||
};
|
||||
}
|
||||
@@ -489,26 +490,33 @@ export class ServersRailComponent {
|
||||
room: Room;
|
||||
selector: ReturnType<ServerDirectoryFacade['buildRoomSignalSelector']>;
|
||||
}> {
|
||||
let resolvedRoom = this.applyResolvedRoomSource(room, this.serverDirectory.normaliseRoomSignalSource({
|
||||
sourceId: room.sourceId,
|
||||
sourceName: room.sourceName,
|
||||
sourceUrl: room.sourceUrl,
|
||||
fallbackName: room.sourceName ?? room.name
|
||||
}, {
|
||||
ensureEndpoint: !!room.sourceUrl
|
||||
}));
|
||||
let selector = this.serverDirectory.buildRoomSignalSelector({
|
||||
sourceId: resolvedRoom.sourceId,
|
||||
sourceName: resolvedRoom.sourceName,
|
||||
sourceUrl: resolvedRoom.sourceUrl,
|
||||
fallbackName: resolvedRoom.sourceName ?? resolvedRoom.name
|
||||
}, {
|
||||
ensureEndpoint: !!resolvedRoom.sourceUrl
|
||||
});
|
||||
let resolvedRoom = this.applyResolvedRoomSource(
|
||||
room,
|
||||
this.serverDirectory.normaliseRoomSignalSource(
|
||||
{
|
||||
sourceId: room.sourceId,
|
||||
sourceName: room.sourceName,
|
||||
sourceUrl: room.sourceUrl,
|
||||
fallbackName: room.sourceName ?? room.name
|
||||
},
|
||||
{
|
||||
ensureEndpoint: !!room.sourceUrl
|
||||
}
|
||||
)
|
||||
);
|
||||
let selector = this.serverDirectory.buildRoomSignalSelector(
|
||||
{
|
||||
sourceId: resolvedRoom.sourceId,
|
||||
sourceName: resolvedRoom.sourceName,
|
||||
sourceUrl: resolvedRoom.sourceUrl,
|
||||
fallbackName: resolvedRoom.sourceName ?? resolvedRoom.name
|
||||
},
|
||||
{
|
||||
ensureEndpoint: !!resolvedRoom.sourceUrl
|
||||
}
|
||||
);
|
||||
|
||||
const authoritativeServer = selector
|
||||
? await firstValueFrom(this.serverDirectory.getServer(room.id, selector))
|
||||
: null;
|
||||
const authoritativeServer = selector ? await firstValueFrom(this.serverDirectory.getServer(room.id, selector)) : null;
|
||||
|
||||
if (!authoritativeServer) {
|
||||
return {
|
||||
@@ -517,24 +525,30 @@ export class ServersRailComponent {
|
||||
};
|
||||
}
|
||||
|
||||
const authoritativeSource = this.serverDirectory.normaliseRoomSignalSource({
|
||||
sourceId: authoritativeServer.sourceId ?? resolvedRoom.sourceId,
|
||||
sourceName: authoritativeServer.sourceName ?? resolvedRoom.sourceName,
|
||||
sourceUrl: authoritativeServer.sourceUrl ?? resolvedRoom.sourceUrl,
|
||||
fallbackName: authoritativeServer.sourceName ?? resolvedRoom.sourceName ?? resolvedRoom.name
|
||||
}, {
|
||||
ensureEndpoint: !!(authoritativeServer.sourceUrl ?? resolvedRoom.sourceUrl)
|
||||
});
|
||||
const authoritativeSource = this.serverDirectory.normaliseRoomSignalSource(
|
||||
{
|
||||
sourceId: authoritativeServer.sourceId ?? resolvedRoom.sourceId,
|
||||
sourceName: authoritativeServer.sourceName ?? resolvedRoom.sourceName,
|
||||
sourceUrl: authoritativeServer.sourceUrl ?? resolvedRoom.sourceUrl,
|
||||
fallbackName: authoritativeServer.sourceName ?? resolvedRoom.sourceName ?? resolvedRoom.name
|
||||
},
|
||||
{
|
||||
ensureEndpoint: !!(authoritativeServer.sourceUrl ?? resolvedRoom.sourceUrl)
|
||||
}
|
||||
);
|
||||
|
||||
resolvedRoom = this.applyResolvedRoomSource(resolvedRoom, authoritativeSource);
|
||||
selector = this.serverDirectory.buildRoomSignalSelector({
|
||||
sourceId: resolvedRoom.sourceId,
|
||||
sourceName: resolvedRoom.sourceName,
|
||||
sourceUrl: resolvedRoom.sourceUrl,
|
||||
fallbackName: resolvedRoom.sourceName ?? resolvedRoom.name
|
||||
}, {
|
||||
ensureEndpoint: !!resolvedRoom.sourceUrl
|
||||
});
|
||||
selector = this.serverDirectory.buildRoomSignalSelector(
|
||||
{
|
||||
sourceId: resolvedRoom.sourceId,
|
||||
sourceName: resolvedRoom.sourceName,
|
||||
sourceUrl: resolvedRoom.sourceUrl,
|
||||
fallbackName: resolvedRoom.sourceName ?? resolvedRoom.name
|
||||
},
|
||||
{
|
||||
ensureEndpoint: !!resolvedRoom.sourceUrl
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
room: resolvedRoom,
|
||||
@@ -550,22 +564,20 @@ export class ServersRailComponent {
|
||||
sourceUrl: source.sourceUrl
|
||||
};
|
||||
|
||||
if (
|
||||
room.sourceId === nextRoom.sourceId
|
||||
&& room.sourceName === nextRoom.sourceName
|
||||
&& room.sourceUrl === nextRoom.sourceUrl
|
||||
) {
|
||||
if (room.sourceId === nextRoom.sourceId && room.sourceName === nextRoom.sourceName && room.sourceUrl === nextRoom.sourceUrl) {
|
||||
return room;
|
||||
}
|
||||
|
||||
this.store.dispatch(RoomsActions.updateRoom({
|
||||
roomId: room.id,
|
||||
changes: {
|
||||
sourceId: nextRoom.sourceId,
|
||||
sourceName: nextRoom.sourceName,
|
||||
sourceUrl: nextRoom.sourceUrl
|
||||
}
|
||||
}));
|
||||
this.store.dispatch(
|
||||
RoomsActions.updateRoom({
|
||||
roomId: room.id,
|
||||
changes: {
|
||||
sourceId: nextRoom.sourceId,
|
||||
sourceName: nextRoom.sourceName,
|
||||
sourceUrl: nextRoom.sourceUrl
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return nextRoom;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<!-- Modal -->
|
||||
<div class="fixed inset-0 z-[91] flex items-center justify-center p-4 pointer-events-none">
|
||||
<div
|
||||
appThemeNode="settingsModalSurface"
|
||||
class="pointer-events-auto relative flex w-full max-w-5xl overflow-hidden rounded-lg border border-border bg-card shadow-lg transition-all duration-200"
|
||||
style="height: min(720px, 88vh)"
|
||||
[class.scale-100]="animating()"
|
||||
@@ -30,7 +31,10 @@
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- Side Navigation -->
|
||||
<nav class="flex w-56 flex-shrink-0 flex-col border-r border-border bg-card">
|
||||
<nav
|
||||
appThemeNode="settingsModalNav"
|
||||
class="flex w-56 flex-shrink-0 flex-col border-r border-border bg-card"
|
||||
>
|
||||
<div class="border-b border-border px-3 py-3">
|
||||
<h2 class="text-lg font-semibold text-foreground">Settings</h2>
|
||||
</div>
|
||||
@@ -116,7 +120,10 @@
|
||||
<!-- Content -->
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-border px-5 py-3 flex-shrink-0">
|
||||
<div
|
||||
appThemeNode="settingsModalHeader"
|
||||
class="flex items-center justify-between border-b border-border px-5 py-3 flex-shrink-0"
|
||||
>
|
||||
<h3 class="text-lg font-semibold text-foreground">
|
||||
@switch (activePage()) {
|
||||
@case ('general') {
|
||||
@@ -169,7 +176,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Scrollable Content Area -->
|
||||
<div class="flex-1 overflow-y-auto bg-background px-4 py-4 sm:px-5 sm:py-4">
|
||||
<div
|
||||
appThemeNode="settingsModalContent"
|
||||
class="flex-1 overflow-y-auto bg-background px-4 py-4 sm:px-5 sm:py-4"
|
||||
>
|
||||
@switch (activePage()) {
|
||||
@case ('general') {
|
||||
<app-general-settings />
|
||||
|
||||
@@ -45,7 +45,11 @@ import { PermissionsSettingsComponent } from './permissions-settings/permissions
|
||||
import { DebuggingSettingsComponent } from './debugging-settings/debugging-settings.component';
|
||||
import { UpdatesSettingsComponent } from './updates-settings/updates-settings.component';
|
||||
import { THIRD_PARTY_LICENSES, type ThirdPartyLicense } from './third-party-licenses';
|
||||
import { ThemeLibraryService, ThemeService } from '../../../domains/theme';
|
||||
import {
|
||||
ThemeLibraryService,
|
||||
ThemeNodeDirective,
|
||||
ThemeService
|
||||
} from '../../../domains/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-modal',
|
||||
@@ -63,7 +67,8 @@ import { ThemeLibraryService, ThemeService } from '../../../domains/theme';
|
||||
ServerSettingsComponent,
|
||||
MembersSettingsComponent,
|
||||
BansSettingsComponent,
|
||||
PermissionsSettingsComponent
|
||||
PermissionsSettingsComponent,
|
||||
ThemeNodeDirective
|
||||
],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
@@ -109,41 +114,19 @@ export class SettingsModalComponent {
|
||||
selectedSavedTheme = this.themeLibrary.selectedEntry;
|
||||
|
||||
readonly globalPages: { id: SettingsPage; label: string; icon: string }[] = [
|
||||
{ id: 'general',
|
||||
label: 'General',
|
||||
icon: 'lucideSettings' },
|
||||
{ id: 'theme',
|
||||
label: 'Theme Studio',
|
||||
icon: 'lucidePalette' },
|
||||
{ id: 'network',
|
||||
label: 'Network',
|
||||
icon: 'lucideGlobe' },
|
||||
{ id: 'notifications',
|
||||
label: 'Notifications',
|
||||
icon: 'lucideBell' },
|
||||
{ id: 'voice',
|
||||
label: 'Voice & Audio',
|
||||
icon: 'lucideAudioLines' },
|
||||
{ id: 'updates',
|
||||
label: 'Updates',
|
||||
icon: 'lucideDownload' },
|
||||
{ id: 'debugging',
|
||||
label: 'Debugging',
|
||||
icon: 'lucideBug' }
|
||||
{ id: 'general', label: 'General', icon: 'lucideSettings' },
|
||||
{ id: 'theme', label: 'Theme Studio', icon: 'lucidePalette' },
|
||||
{ id: 'network', label: 'Network', icon: 'lucideGlobe' },
|
||||
{ id: 'notifications', label: 'Notifications', icon: 'lucideBell' },
|
||||
{ id: 'voice', label: 'Voice & Audio', icon: 'lucideAudioLines' },
|
||||
{ id: 'updates', label: 'Updates', icon: 'lucideDownload' },
|
||||
{ id: 'debugging', label: 'Debugging', icon: 'lucideBug' }
|
||||
];
|
||||
readonly serverPages: { id: SettingsPage; label: string; icon: string }[] = [
|
||||
{ id: 'server',
|
||||
label: 'Server',
|
||||
icon: 'lucideSettings' },
|
||||
{ id: 'members',
|
||||
label: 'Members',
|
||||
icon: 'lucideUsers' },
|
||||
{ id: 'bans',
|
||||
label: 'Bans',
|
||||
icon: 'lucideBan' },
|
||||
{ id: 'permissions',
|
||||
label: 'Permissions',
|
||||
icon: 'lucideShield' }
|
||||
{ id: 'server', label: 'Server', icon: 'lucideSettings' },
|
||||
{ id: 'members', label: 'Members', icon: 'lucideUsers' },
|
||||
{ id: 'bans', label: 'Bans', icon: 'lucideBan' },
|
||||
{ id: 'permissions', label: 'Permissions', icon: 'lucideShield' }
|
||||
];
|
||||
|
||||
manageableRooms = computed<Room[]>(() => {
|
||||
@@ -153,16 +136,18 @@ export class SettingsModalComponent {
|
||||
return [];
|
||||
|
||||
return this.savedRooms().filter((room) => {
|
||||
const viewedRoom = this.currentRoom()?.id === room.id ? this.currentRoom() ?? room : room;
|
||||
const viewedRoom = this.currentRoom()?.id === room.id ? (this.currentRoom() ?? room) : room;
|
||||
const role = resolveLegacyRole(viewedRoom, user);
|
||||
|
||||
return role === 'host'
|
||||
|| resolveRoomPermission(viewedRoom, user, 'manageServer')
|
||||
|| resolveRoomPermission(viewedRoom, user, 'manageRoles')
|
||||
|| resolveRoomPermission(viewedRoom, user, 'manageChannels')
|
||||
|| resolveRoomPermission(viewedRoom, user, 'manageBans')
|
||||
|| resolveRoomPermission(viewedRoom, user, 'kickMembers')
|
||||
|| resolveRoomPermission(viewedRoom, user, 'banMembers');
|
||||
return (
|
||||
role === 'host' ||
|
||||
resolveRoomPermission(viewedRoom, user, 'manageServer') ||
|
||||
resolveRoomPermission(viewedRoom, user, 'manageRoles') ||
|
||||
resolveRoomPermission(viewedRoom, user, 'manageChannels') ||
|
||||
resolveRoomPermission(viewedRoom, user, 'manageBans') ||
|
||||
resolveRoomPermission(viewedRoom, user, 'kickMembers') ||
|
||||
resolveRoomPermission(viewedRoom, user, 'banMembers')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -187,21 +172,23 @@ export class SettingsModalComponent {
|
||||
if (!server || !user)
|
||||
return null;
|
||||
|
||||
return resolveLegacyRole(this.currentRoom()?.id === server.id ? this.currentRoom() ?? server : server, user);
|
||||
return resolveLegacyRole(this.currentRoom()?.id === server.id ? (this.currentRoom() ?? server) : server, user);
|
||||
});
|
||||
|
||||
canAccessSelectedServer = computed(() => {
|
||||
const server = this.selectedServer();
|
||||
const user = this.currentUser();
|
||||
|
||||
return !!server && !!user && (
|
||||
resolveLegacyRole(server, user) === 'host'
|
||||
|| resolveRoomPermission(server, user, 'manageServer')
|
||||
|| resolveRoomPermission(server, user, 'manageRoles')
|
||||
|| resolveRoomPermission(server, user, 'manageChannels')
|
||||
|| resolveRoomPermission(server, user, 'manageBans')
|
||||
|| resolveRoomPermission(server, user, 'kickMembers')
|
||||
|| resolveRoomPermission(server, user, 'banMembers')
|
||||
return (
|
||||
!!server &&
|
||||
!!user &&
|
||||
(resolveLegacyRole(server, user) === 'host' ||
|
||||
resolveRoomPermission(server, user, 'manageServer') ||
|
||||
resolveRoomPermission(server, user, 'manageRoles') ||
|
||||
resolveRoomPermission(server, user, 'manageChannels') ||
|
||||
resolveRoomPermission(server, user, 'manageBans') ||
|
||||
resolveRoomPermission(server, user, 'kickMembers') ||
|
||||
resolveRoomPermission(server, user, 'banMembers'))
|
||||
);
|
||||
});
|
||||
|
||||
@@ -209,11 +196,13 @@ export class SettingsModalComponent {
|
||||
const server = this.selectedServer();
|
||||
const user = this.currentUser();
|
||||
|
||||
return !!server && !!user && (
|
||||
resolveLegacyRole(server, user) === 'host'
|
||||
|| resolveRoomPermission(server, user, 'manageRoles')
|
||||
|| resolveRoomPermission(server, user, 'kickMembers')
|
||||
|| resolveRoomPermission(server, user, 'banMembers')
|
||||
return (
|
||||
!!server &&
|
||||
!!user &&
|
||||
(resolveLegacyRole(server, user) === 'host' ||
|
||||
resolveRoomPermission(server, user, 'manageRoles') ||
|
||||
resolveRoomPermission(server, user, 'kickMembers') ||
|
||||
resolveRoomPermission(server, user, 'banMembers'))
|
||||
);
|
||||
});
|
||||
|
||||
@@ -221,20 +210,19 @@ export class SettingsModalComponent {
|
||||
const server = this.selectedServer();
|
||||
const user = this.currentUser();
|
||||
|
||||
return !!server && !!user && (
|
||||
resolveLegacyRole(server, user) === 'host'
|
||||
|| resolveRoomPermission(server, user, 'manageBans')
|
||||
);
|
||||
return !!server && !!user && (resolveLegacyRole(server, user) === 'host' || resolveRoomPermission(server, user, 'manageBans'));
|
||||
});
|
||||
|
||||
canManageSelectedPermissions = computed(() => {
|
||||
const server = this.selectedServer();
|
||||
const user = this.currentUser();
|
||||
|
||||
return !!server && !!user && (
|
||||
resolveLegacyRole(server, user) === 'host'
|
||||
|| resolveRoomPermission(server, user, 'manageRoles')
|
||||
|| resolveRoomPermission(server, user, 'manageServer')
|
||||
return (
|
||||
!!server &&
|
||||
!!user &&
|
||||
(resolveLegacyRole(server, user) === 'host' ||
|
||||
resolveRoomPermission(server, user, 'manageRoles') ||
|
||||
resolveRoomPermission(server, user, 'manageServer'))
|
||||
);
|
||||
});
|
||||
|
||||
@@ -259,9 +247,8 @@ export class SettingsModalComponent {
|
||||
const hasSelected = !!selectedId && rooms.some((room) => room.id === selectedId);
|
||||
|
||||
if (!hasSelected) {
|
||||
const fallbackId = [targetId, currentRoomId].find((candidateId) =>
|
||||
!!candidateId && rooms.some((room) => room.id === candidateId)
|
||||
) ?? rooms[0]?.id ?? null;
|
||||
const fallbackId =
|
||||
[targetId, currentRoomId].find((candidateId) => !!candidateId && rooms.some((room) => room.id === candidateId)) ?? rooms[0]?.id ?? null;
|
||||
|
||||
this.selectedServerId.set(fallbackId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user