fix: Fix multiple bugs with new authentication flow

This commit is contained in:
2026-06-07 15:04:21 +02:00
parent 9fc26b1ccf
commit 83456c018c
137 changed files with 4710 additions and 281 deletions

View File

@@ -21,12 +21,15 @@
<input
#searchInput
type="text"
appAutoFocus
appSelectOnFocus
appSubmitOnEnter
(submitOnEnter)="submitSearch()"
[attr.aria-label]="'dashboard.searchAriaLabel' | translate"
class="h-12 w-full min-w-0 rounded-xl border border-border bg-secondary py-2 pl-11 pr-4 text-base text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary sm:pr-20"
[placeholder]="isMobile() ? ('dashboard.searchPlaceholderMobile' | translate) : ('dashboard.searchPlaceholderDesktop' | translate)"
[ngModel]="searchQuery()"
(ngModelChange)="onSearchChange($event)"
(keydown.enter)="submitSearch()"
/>
<kbd
class="pointer-events-none absolute right-3 top-1/2 hidden -translate-y-1/2 items-center gap-1 rounded border border-border bg-card px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground sm:flex"

View File

@@ -46,6 +46,11 @@ import { FriendButtonComponent } from '../../domains/direct-message/feature/frie
import { UserAvatarComponent } from '../../shared/components/user-avatar/user-avatar.component';
import { parseInviteQuery } from './invite-query.util';
import { AppI18nService, APP_TRANSLATE_IMPORTS } from '../../core/i18n';
import {
AutoFocusDirective,
SelectOnFocusDirective,
SubmitOnEnterDirective
} from '../../shared/directives';
/** Maximum quick-search rows shown per group on the dashboard. */
const QUICK_RESULT_LIMIT = 5;
@@ -72,6 +77,9 @@ const RECENT_SEARCHES_STORAGE_KEY = 'metoyou_dashboard_recent_searches';
NgIcon,
FriendButtonComponent,
UserAvatarComponent,
AutoFocusDirective,
SelectOnFocusDirective,
SubmitOnEnterDirective,
...APP_TRANSLATE_IMPORTS
],
viewProviders: [

View File

@@ -17,7 +17,7 @@
[class.shadow-[0_0_0_6px_rgba(16,185,129,0.12)]]="speaking() && compact()"
[class.shadow-[0_0_0_8px_rgba(16,185,129,0.12)]]="speaking() && !compact()"
[class.ring-border]="!speaking()"
[class.opacity-55]="!connected()"
[class.opacity-55]="!connected() || passive()"
>
@if (user().avatarUrl) {
<img

View File

@@ -18,6 +18,7 @@ export class PrivateCallParticipantCardComponent {
readonly speaking = input.required<boolean>();
readonly issueLabel = input<string | null>(null);
readonly compact = input(false);
readonly passive = input(false);
avatarSize(): string {
return this.compact() ? '5.75rem' : 'clamp(6.5rem, 38vw, 13rem)';

View File

@@ -148,6 +148,7 @@
[connected]="isParticipantConnected(user)"
[speaking]="isSpeaking(user)"
[issueLabel]="participantIssueLabel(user)"
[passive]="isPassiveCallParticipant(user)"
/>
}
</div>
@@ -164,6 +165,7 @@
[connected]="isParticipantConnected(user)"
[speaking]="isSpeaking(user)"
[issueLabel]="participantIssueLabel(user)"
[passive]="isPassiveCallParticipant(user)"
[compact]="true"
/>
}

View File

@@ -44,6 +44,11 @@ import {
import { loadVoiceSettingsFromStorage, saveVoiceSettingsToStorage } from '../../domains/voice-session';
import { ScreenShareQualityDialogComponent } from '../../shared';
import { ViewportService } from '../../core/platform';
import { RealtimeSessionFacade } from '../../core/realtime';
import {
isLocalVoiceOwner,
isVoiceOnAnotherClient
} from '../../domains/voice-session';
import { MobileMediaService, MobilePlatformService } from '../../infrastructure/mobile';
import { selectAllUsers, selectCurrentUser } from '../../store/users/users.selectors';
import { UsersActions } from '../../store/users/users.actions';
@@ -85,6 +90,7 @@ export class PrivateCallComponent {
private readonly destroyRef = inject(DestroyRef);
private readonly store = inject(Store);
private readonly calls = inject(DirectCallService);
private readonly realtime = inject(RealtimeSessionFacade);
private readonly voice = inject(VoiceConnectionFacade);
private readonly voiceActivity = inject(VoiceActivityService);
private readonly playback = inject(VoicePlaybackService);
@@ -437,18 +443,38 @@ export class PrivateCallComponent {
isParticipantConnected(user: User): boolean {
const session = this.session();
const userId = this.userKey(user);
const current = this.currentUser();
if (!session) {
return false;
}
return (
!!session.participants[userId]?.joined ||
!!(user.voiceState?.isConnected && user.voiceState.roomId === session.callId && user.voiceState.serverId === session.callId)
const inCallVoice = !!(
user.voiceState?.isConnected
&& user.voiceState.roomId === session.callId
&& user.voiceState.serverId === session.callId
);
const isSelf = !!current && (user.id === current.id || user.oderId === current.oderId);
if (isSelf && inCallVoice) {
return isLocalVoiceOwner(user.voiceState, this.realtime.getClientInstanceId());
}
return !!session.participants[userId]?.joined || inCallVoice;
}
isPassiveCallParticipant(user: User): boolean {
const current = this.currentUser();
const isSelf = !!current && (user.id === current.id || user.oderId === current.oderId);
return isSelf && isVoiceOnAnotherClient(user.voiceState, this.realtime.getClientInstanceId());
}
participantIssueLabel(user: User): string | null {
if (this.isPassiveCallParticipant(user)) {
return this.i18n.instant('call.private.voiceOnOtherDevice');
}
return this.isParticipantConnected(user) ? null : this.i18n.instant('call.private.waiting');
}

View File

@@ -98,6 +98,8 @@
<input
#renameInput
type="text"
appAutoFocus
appSelectOnFocus
[value]="ch.name"
[class.border-destructive]="renamingChannelId() === ch.id && !!channelNameError()"
[title]="renamingChannelId() === ch.id ? (channelNameError() ? (channelNameError()! | translate) : '') : ''"
@@ -173,7 +175,7 @@
(contextmenu)="openChannelContextMenu($event, ch)"
[class.bg-secondary]="isCurrentRoom(ch.id)"
[disabled]="!voiceEnabled()"
[title]="isCurrentRoom(ch.id) ? ('room.panel.openStreamWorkspace' | translate) : ('room.panel.joinVoiceChannel' | translate)"
[title]="voiceChannelActionLabel(ch.id)"
data-channel-type="voice"
[attr.data-channel-name]="ch.name"
>
@@ -186,6 +188,8 @@
<input
#renameInput
type="text"
appAutoFocus
appSelectOnFocus
[value]="ch.name"
[class.border-destructive]="renamingChannelId() === ch.id && !!channelNameError()"
[title]="renamingChannelId() === ch.id ? (channelNameError() ? (channelNameError()! | translate) : '') : ''"
@@ -205,6 +209,10 @@
<span class="rounded-full bg-primary/15 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-primary">
{{ isVoiceWorkspaceExpanded() ? ('room.panel.open' | translate) : ('room.panel.view' | translate) }}
</span>
} @else if (isPassiveInVoiceRoom(ch.id)) {
<span class="rounded-full bg-muted px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
{{ 'room.panel.takeOverVoice' | translate }}
</span>
} @else if (voiceOccupancy(ch.id) > 0) {
<span class="text-xs text-muted-foreground">{{ voiceOccupancy(ch.id) }}</span>
}
@@ -220,6 +228,7 @@
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-50]="isPassiveVoiceUser(u)"
[class.opacity-60]="draggedVoiceUserId() === (u.id || u.oderId)"
[draggable]="canDragVoiceUser(u)"
(dragstart)="onVoiceUserDragStart($event, u)"
@@ -368,6 +377,7 @@
<h4 class="text-xs uppercase tracking-wide text-muted-foreground font-medium mb-2 px-1">{{ 'room.panel.you' | translate }}</h4>
<div
class="flex items-center gap-2 rounded-md bg-secondary/60 px-3 py-2 hover:bg-secondary/80 transition-colors cursor-pointer"
[class.opacity-50]="isPassiveVoiceClient()"
role="button"
tabindex="0"
(click)="openProfileCard($event, currentUser()!, true); $event.stopPropagation()"
@@ -413,7 +423,11 @@
name="lucideMic"
class="w-2.5 h-2.5"
/>
{{ 'room.panel.inVoice' | translate }}
@if (isPassiveVoiceClient()) {
{{ 'room.panel.voiceOnOtherDevice' | translate }}
} @else {
{{ 'room.panel.inVoice' | translate }}
}
</p>
}
@if (currentUser() && isUserStreaming(currentUser()!.oderId || currentUser()!.id)) {
@@ -763,7 +777,6 @@
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
[class.border-destructive]="!!channelNameError()"
(ngModelChange)="clearChannelNameError()"
(keydown.enter)="confirmCreateChannel()"
/>
@if (channelNameError()) {
<p class="mt-2 text-sm text-destructive">{{ channelNameError()! | translate }}</p>

View File

@@ -52,7 +52,12 @@ import {
VoiceConnectionFacade,
VoiceConnectivityHealthService
} from '../../../domains/voice-connection';
import { VoiceSessionFacade, VoiceWorkspaceService } from '../../../domains/voice-session';
import {
VoiceSessionFacade,
VoiceWorkspaceService,
isLocalVoiceOwner,
isVoiceOnAnotherClient
} from '../../../domains/voice-session';
import { DirectMessageService } from '../../../domains/direct-message';
import { DirectCallService } from '../../../domains/direct-call';
import { VoicePlaybackService } from '../../../domains/voice-connection';
@@ -88,6 +93,7 @@ import {
import { v4 as uuidv4 } from 'uuid';
import { visibilityAwareInterval$ } from '../../../shared/rxjs';
import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../core/i18n';
import { AutoFocusDirective, SelectOnFocusDirective } from '../../../shared/directives';
type PanelMode = 'channels' | 'users';
@@ -109,6 +115,8 @@ const SKELETON_REVEAL_DELAY_MS = 180;
ThemeNodeDirective,
SkeletonComponent,
SkeletonListComponent,
AutoFocusDirective,
SelectOnFocusDirective,
...APP_TRANSLATE_IMPORTS
],
viewProviders: [
@@ -689,7 +697,13 @@ export class RoomsSidePanelComponent implements OnDestroy {
}
private openExistingVoiceWorkspace(room: Room | null, current: User | null, roomId: string): boolean {
if (!room || !current?.voiceState?.isConnected || current.voiceState.roomId !== roomId || current.voiceState.serverId !== room.id) {
if (
!room
|| !current?.voiceState?.isConnected
|| current.voiceState.roomId !== roomId
|| current.voiceState.serverId !== room.id
|| !isLocalVoiceOwner(current.voiceState, this.realtime.getClientInstanceId())
) {
return false;
}
@@ -697,6 +711,47 @@ export class RoomsSidePanelComponent implements OnDestroy {
return true;
}
isPassiveInVoiceRoom(roomId: string): boolean {
const current = this.currentUser();
const room = this.currentRoom();
return !!current?.voiceState?.isConnected
&& current.voiceState.roomId === roomId
&& current.voiceState.serverId === room?.id
&& isVoiceOnAnotherClient(current.voiceState, this.realtime.getClientInstanceId());
}
isPassiveVoiceClient(): boolean {
const current = this.currentUser();
return isVoiceOnAnotherClient(current?.voiceState, this.realtime.getClientInstanceId());
}
isPassiveVoiceUser(user: User | null): boolean {
const current = this.currentUser();
if (!user || !current) {
return false;
}
return (user.id === current.id || user.oderId === current.oderId)
&& this.isPassiveVoiceClient();
}
voiceChannelActionLabel(roomId: string): string {
if (this.isCurrentRoom(roomId)) {
return this.isVoiceWorkspaceExpanded()
? this.appI18n.instant('room.panel.open')
: this.appI18n.instant('room.panel.view');
}
if (this.isPassiveInVoiceRoom(roomId)) {
return this.appI18n.instant('room.panel.takeOverVoice');
}
return this.appI18n.instant('room.panel.joinVoiceChannel');
}
private canJoinRequestedVoiceRoom(room: Room, current: User | null, roomId: string): boolean {
return !current || resolveRoomPermission(room, current, 'joinVoice', roomId);
}
@@ -737,9 +792,19 @@ export class RoomsSidePanelComponent implements OnDestroy {
this.directCalls.leaveCurrentJoinedCall();
this.prepareVoiceJoin(room, current ?? null);
this.enableVoiceForJoin(room, current ?? null, roomId)
.then(() => this.onVoiceJoinSucceeded(roomId, room, current ?? null))
.catch((error) => this.handleVoiceJoinFailure(error));
const startJoin = () => {
this.enableVoiceForJoin(room, current ?? null, roomId)
.then(() => this.onVoiceJoinSucceeded(roomId, room, current ?? null))
.catch((error) => this.handleVoiceJoinFailure(error));
};
if (this.isPassiveInVoiceRoom(roomId) || this.isPassiveVoiceClient()) {
this.realtime.requestVoiceClientTakeover();
window.setTimeout(startJoin, 300);
return;
}
startJoin();
}
private onVoiceJoinSucceeded(roomId: string, room: Room, current: User | null): void {
@@ -786,7 +851,8 @@ export class RoomsSidePanelComponent implements OnDestroy {
isMuted: current.voiceState?.isMuted ?? false,
isDeafened: current.voiceState?.isDeafened ?? false,
roomId,
serverId: room.id
serverId: room.id,
clientInstanceId: this.realtime.getClientInstanceId()
}
})
);
@@ -797,6 +863,8 @@ export class RoomsSidePanelComponent implements OnDestroy {
}
private broadcastVoiceConnected(roomId: string, room: Room, current: User | null): void {
const clientInstanceId = this.realtime.getClientInstanceId();
this.voiceConnection.broadcastMessage({
type: 'voice-state',
oderId: current?.oderId || current?.id,
@@ -806,7 +874,8 @@ export class RoomsSidePanelComponent implements OnDestroy {
isMuted: current?.voiceState?.isMuted ?? false,
isDeafened: current?.voiceState?.isDeafened ?? false,
roomId,
serverId: room.id
serverId: room.id,
clientInstanceId
}
});
}
@@ -851,7 +920,8 @@ export class RoomsSidePanelComponent implements OnDestroy {
isMuted: false,
isDeafened: false,
roomId: undefined,
serverId: undefined
serverId: undefined,
clientInstanceId: undefined
}
})
);
@@ -873,7 +943,8 @@ export class RoomsSidePanelComponent implements OnDestroy {
isMuted: false,
isDeafened: false,
roomId: previousVoiceState?.roomId,
serverId: previousVoiceState?.serverId
serverId: previousVoiceState?.serverId,
clientInstanceId: undefined
}
});
@@ -1110,7 +1181,12 @@ export class RoomsSidePanelComponent implements OnDestroy {
const me = this.currentUser();
const room = this.currentRoom();
return !!(me?.voiceState?.isConnected && me.voiceState?.roomId === roomId && me.voiceState?.serverId === room?.id);
return !!(
me?.voiceState?.isConnected
&& me.voiceState.roomId === roomId
&& me.voiceState.serverId === room?.id
&& isLocalVoiceOwner(me.voiceState, this.realtime.getClientInstanceId())
);
}
voiceEnabled(): boolean {

View File

@@ -27,6 +27,7 @@ import {
} from 'rxjs';
import { Room, User } from '../../../shared-kernel';
import { buildLoginReturnQueryParams } from '../../../domains/authentication/domain/logic/auth-navigation.rules';
import { UserBarComponent } from '../../../domains/authentication/feature/user-bar/user-bar.component';
import { VoiceSessionFacade } from '../../../domains/voice-session';
import { selectSavedRooms, selectCurrentRoom } from '../../../store/rooms/rooms.selectors';
@@ -276,7 +277,9 @@ export class ServersRailComponent {
const currentUserId = localStorage.getItem('metoyou_currentUserId');
if (!currentUserId) {
this.router.navigate(['/login']);
this.router.navigate(['/login'], {
queryParams: buildLoginReturnQueryParams(this.router.url)
});
return;
}

View File

@@ -154,11 +154,13 @@
<div class="flex items-center gap-2">
<input
type="text"
appSelectOnFocus
appSubmitOnEnter
(submitOnEnter)="addIgnoredProcess()"
class="flex-1 rounded-md border border-border bg-background px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
[placeholder]="'settings.general.gameDetection.processPlaceholder' | translate"
[value]="ignoredProcessDraft()"
(input)="onIgnoredProcessDraftChange($event)"
(keydown.enter)="addIgnoredProcess()"
[attr.aria-label]="'settings.general.gameDetection.processAria' | translate"
/>
<button

View File

@@ -14,11 +14,21 @@ import { ElectronBridgeService } from '../../../../core/platform/electron/electr
import { PlatformService } from '../../../../core/platform';
import { ExperimentalMediaSettingsService } from '../../../../domains/experimental-media/application/services/experimental-media-settings.service';
import { APP_TRANSLATE_IMPORTS } from '../../../../core/i18n';
import {
SelectOnFocusDirective,
SubmitOnEnterDirective
} from '../../../../shared/directives';
@Component({
selector: 'app-general-settings',
standalone: true,
imports: [CommonModule, NgIcon, ...APP_TRANSLATE_IMPORTS],
imports: [
CommonModule,
NgIcon,
SelectOnFocusDirective,
SubmitOnEnterDirective,
...APP_TRANSLATE_IMPORTS
],
viewProviders: [
provideIcons({
lucidePower

View File

@@ -113,6 +113,10 @@
</select>
<input
type="text"
appAutoFocus
appSelectOnFocus
appSubmitOnEnter
(submitOnEnter)="addEntry()"
[(ngModel)]="newUrl"
data-testid="ice-url-input"
[placeholder]="(newType === 'stun' ? 'settings.network.ice.stunPlaceholder' : 'settings.network.ice.turnPlaceholder') | translate"
@@ -123,6 +127,7 @@
<div class="flex gap-2">
<input
type="text"
appSelectOnFocus
[(ngModel)]="newUsername"
data-testid="ice-username-input"
[placeholder]="'settings.network.ice.username' | translate"
@@ -130,6 +135,8 @@
/>
<input
type="password"
appSubmitOnEnter
(submitOnEnter)="addEntry()"
[(ngModel)]="newCredential"
data-testid="ice-credential-input"
[placeholder]="'settings.network.ice.credential' | translate"

View File

@@ -18,6 +18,11 @@ import {
import { IceServerSettingsService, IceServerEntry } from '../../../../infrastructure/realtime/ice-server-settings.service';
import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../../core/i18n';
import {
AutoFocusDirective,
SelectOnFocusDirective,
SubmitOnEnterDirective
} from '../../../../shared/directives';
@Component({
selector: 'app-ice-server-settings',
@@ -29,6 +34,9 @@ import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../../core/i18n';
CommonModule,
FormsModule,
NgIcon,
AutoFocusDirective,
SelectOnFocusDirective,
SubmitOnEnterDirective,
...APP_TRANSLATE_IMPORTS
],
viewProviders: [

View File

@@ -126,12 +126,16 @@
<div class="flex-1 space-y-1.5">
<input
type="text"
appSelectOnFocus
[(ngModel)]="newServerName"
[placeholder]="'settings.network.serverEndpoints.serverNamePlaceholderShort' | translate"
class="w-full px-3 py-1.5 bg-secondary rounded-lg border border-border text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
/>
<input
type="url"
appSelectOnFocus
appSubmitOnEnter
(submitOnEnter)="addServer()"
[(ngModel)]="newServerUrl"
[placeholder]="'settings.network.serverEndpoints.serverUrlPlaceholder' | translate"
class="w-full px-3 py-1.5 bg-secondary rounded-lg border border-border text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"

View File

@@ -22,6 +22,10 @@ import { ServerDirectoryFacade } from '../../../../domains/server-directory';
import { STORAGE_KEY_CONNECTION_SETTINGS } from '../../../../core/constants';
import { IceServerSettingsComponent } from '../ice-server-settings/ice-server-settings.component';
import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../../core/i18n';
import {
SelectOnFocusDirective,
SubmitOnEnterDirective
} from '../../../../shared/directives';
@Component({
selector: 'app-network-settings',
@@ -31,6 +35,8 @@ import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../../core/i18n';
FormsModule,
NgIcon,
IceServerSettingsComponent,
SelectOnFocusDirective,
SubmitOnEnterDirective,
...APP_TRANSLATE_IMPORTS
],
viewProviders: [

View File

@@ -110,6 +110,9 @@
}}</span>
<input
type="text"
appSelectOnFocus
appSubmitOnEnter
(submitOnEnter)="saveRoleDetails()"
[ngModel]="roleName"
(ngModelChange)="roleName = $event"
[disabled]="!canEditSelectedRoleMetadata()"

View File

@@ -39,6 +39,10 @@ import {
withUpdatedRole
} from '../../../../domains/access-control';
import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../../core/i18n';
import {
SelectOnFocusDirective,
SubmitOnEnterDirective
} from '../../../../shared/directives';
function upsertRoleChannelOverride(
overrides: readonly ChannelPermissionOverride[] | undefined,
@@ -75,6 +79,8 @@ function upsertRoleChannelOverride(
CommonModule,
FormsModule,
NgIcon,
SelectOnFocusDirective,
SubmitOnEnterDirective,
...APP_TRANSLATE_IMPORTS
],
viewProviders: [

View File

@@ -82,6 +82,9 @@
[(ngModel)]="roomName"
[readOnly]="!isAdmin()"
id="room-name"
appSelectOnFocus
appSubmitOnEnter
(submitOnEnter)="saveServerSettings()"
class="w-full px-3 py-2 bg-secondary rounded-lg border border-border text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary"
[class.opacity-60]="!isAdmin()"
[class.cursor-not-allowed]="!isAdmin()"

View File

@@ -26,6 +26,10 @@ import { ConfirmDialogComponent } from '../../../../shared';
import { SettingsModalService } from '../../../../core/services/settings-modal.service';
import { ServerIconImageService } from '../../../../domains/server-directory/infrastructure/services/server-icon-image.service';
import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../../core/i18n';
import {
SelectOnFocusDirective,
SubmitOnEnterDirective
} from '../../../../shared/directives';
@Component({
selector: 'app-server-settings',
@@ -35,6 +39,8 @@ import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../../core/i18n';
FormsModule,
NgIcon,
ConfirmDialogComponent,
SelectOnFocusDirective,
SubmitOnEnterDirective,
...APP_TRANSLATE_IMPORTS
],
viewProviders: [

View File

@@ -163,12 +163,16 @@
<div class="flex-1 space-y-2">
<input
type="text"
appSelectOnFocus
[(ngModel)]="newServerName"
[placeholder]="'settings.network.serverEndpoints.serverNamePlaceholder' | translate"
class="w-full px-3 py-2 bg-secondary rounded-lg border border-border text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
/>
<input
type="url"
appSelectOnFocus
appSubmitOnEnter
(submitOnEnter)="addServer()"
[(ngModel)]="newServerUrl"
[placeholder]="'settings.network.serverEndpoints.serverUrlPlaceholder' | translate"
class="w-full px-3 py-2 bg-secondary rounded-lg border border-border text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"

View File

@@ -29,6 +29,10 @@ import { VoiceConnectionFacade } from '../../domains/voice-connection';
import { NotificationAudioService, AppSound } from '../../core/services/notification-audio.service';
import { STORAGE_KEY_CONNECTION_SETTINGS, STORAGE_KEY_VOICE_SETTINGS } from '../../core/constants';
import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../core/i18n';
import {
SelectOnFocusDirective,
SubmitOnEnterDirective
} from '../../shared/directives';
@Component({
selector: 'app-settings',
@@ -37,6 +41,8 @@ import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../core/i18n';
CommonModule,
FormsModule,
NgIcon,
SelectOnFocusDirective,
SubmitOnEnterDirective,
...APP_TRANSLATE_IMPORTS
],
viewProviders: [

View File

@@ -40,6 +40,7 @@ import { RealtimeSessionFacade } from '../../../core/realtime';
import { ServerDirectoryFacade } from '../../../domains/server-directory';
import { PlatformService } from '../../../core/platform';
import { clearStoredCurrentUserId } from '../../../core/storage/current-user-storage';
import { buildLoginReturnQueryParams } from '../../../domains/authentication/domain/logic/auth-navigation.rules';
import { SettingsModalService } from '../../../core/services/settings-modal.service';
import { LeaveServerDialogComponent, ModalBackdropComponent } from '../../../shared';
import { Room, type PluginRequirementSummary } from '../../../shared-kernel';
@@ -211,7 +212,9 @@ export class TitleBarComponent {
/** Navigate to the login page. */
goLogin() {
this.router.navigate(['/login']);
this.router.navigate(['/login'], {
queryParams: buildLoginReturnQueryParams(this.router.url)
});
}
openPluginStore(): void {