fix: multiple bug fixes
isolated users, db backup, weird disconnect issues for long voice sessions,
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/member-ordering */
|
||||
import {
|
||||
Injectable,
|
||||
computed,
|
||||
effect,
|
||||
inject,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
@@ -13,7 +10,11 @@ import {
|
||||
throwError
|
||||
} from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { ServerDirectoryFacade } from '../../../server-directory';
|
||||
import {
|
||||
ServerDirectoryFacade,
|
||||
type RoomSignalSourceInput,
|
||||
type ServerSourceSelector
|
||||
} from '../../../server-directory';
|
||||
|
||||
export interface KlipyGif {
|
||||
id: string;
|
||||
@@ -37,51 +38,47 @@ export interface KlipyGifSearchResponse {
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 24;
|
||||
const KLIPY_CUSTOMER_ID_STORAGE_KEY = 'metoyou_klipy_customer_id';
|
||||
const DEFAULT_AVAILABILITY_KEY = 'default';
|
||||
|
||||
interface KlipyAvailabilityState {
|
||||
enabled: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class KlipyService {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly serverDirectory = inject(ServerDirectoryFacade);
|
||||
private readonly availabilityState = signal({
|
||||
enabled: false,
|
||||
loading: true
|
||||
});
|
||||
private lastAvailabilityKey = '';
|
||||
private readonly availabilityByKey = signal<Record<string, KlipyAvailabilityState>>({});
|
||||
|
||||
readonly isEnabled = computed(() => this.availabilityState().enabled);
|
||||
readonly isLoading = computed(() => this.availabilityState().loading);
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const activeServer = this.serverDirectory.activeServer();
|
||||
const apiBaseUrl = this.serverDirectory.getApiBaseUrl();
|
||||
const nextKey = `${activeServer?.id ?? 'default'}:${apiBaseUrl}`;
|
||||
|
||||
if (nextKey === this.lastAvailabilityKey)
|
||||
return;
|
||||
|
||||
this.lastAvailabilityKey = nextKey;
|
||||
void this.refreshAvailability();
|
||||
});
|
||||
isEnabled(source?: RoomSignalSourceInput | null): boolean {
|
||||
return this.getAvailabilityState(source).enabled;
|
||||
}
|
||||
|
||||
async refreshAvailability(): Promise<void> {
|
||||
this.availabilityState.set({ enabled: false,
|
||||
isLoading(source?: RoomSignalSourceInput | null): boolean {
|
||||
return this.getAvailabilityState(source).loading;
|
||||
}
|
||||
|
||||
async refreshAvailability(source?: RoomSignalSourceInput | null): Promise<void> {
|
||||
const selector = this.getSourceSelector(source);
|
||||
const key = this.getAvailabilityKey(selector);
|
||||
|
||||
this.setAvailabilityState(key, { enabled: false,
|
||||
loading: true });
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.http.get<KlipyAvailabilityResponse>(
|
||||
`${this.serverDirectory.getApiBaseUrl()}/klipy/config`
|
||||
`${this.serverDirectory.getApiBaseUrl(selector)}/klipy/config`
|
||||
)
|
||||
);
|
||||
|
||||
this.availabilityState.set({
|
||||
this.setAvailabilityState(key, {
|
||||
enabled: response.enabled === true,
|
||||
loading: false
|
||||
});
|
||||
} catch {
|
||||
this.availabilityState.set({ enabled: false,
|
||||
this.setAvailabilityState(key, { enabled: false,
|
||||
loading: false });
|
||||
}
|
||||
}
|
||||
@@ -89,8 +86,11 @@ export class KlipyService {
|
||||
searchGifs(
|
||||
query: string,
|
||||
page = 1,
|
||||
perPage = DEFAULT_PAGE_SIZE
|
||||
perPage = DEFAULT_PAGE_SIZE,
|
||||
source?: RoomSignalSourceInput | null
|
||||
): Observable<KlipyGifSearchResponse> {
|
||||
const selector = this.getSourceSelector(source);
|
||||
|
||||
let params = new HttpParams()
|
||||
.set('page', String(Math.max(1, Math.floor(page))))
|
||||
.set('per_page', String(Math.max(1, Math.floor(perPage))))
|
||||
@@ -109,7 +109,7 @@ export class KlipyService {
|
||||
}
|
||||
|
||||
return this.http
|
||||
.get<KlipyGifSearchResponse>(`${this.serverDirectory.getApiBaseUrl()}/klipy/gifs`, { params })
|
||||
.get<KlipyGifSearchResponse>(`${this.serverDirectory.getApiBaseUrl(selector)}/klipy/gifs`, { params })
|
||||
.pipe(
|
||||
map((response) => ({
|
||||
enabled: response.enabled !== false,
|
||||
@@ -138,7 +138,7 @@ export class KlipyService {
|
||||
return this.normalizeMediaUrl(url);
|
||||
}
|
||||
|
||||
buildImageProxyUrl(url: string): string {
|
||||
buildImageProxyUrl(url: string, source?: RoomSignalSourceInput | null): string {
|
||||
const trimmed = this.normalizeMediaUrl(url);
|
||||
|
||||
if (!trimmed)
|
||||
@@ -147,7 +147,36 @@ export class KlipyService {
|
||||
if (!/^https?:\/\//i.test(trimmed))
|
||||
return trimmed;
|
||||
|
||||
return `${this.serverDirectory.getApiBaseUrl()}/image-proxy?url=${encodeURIComponent(trimmed)}`;
|
||||
return `${this.serverDirectory.getApiBaseUrl(this.getSourceSelector(source))}/image-proxy?url=${encodeURIComponent(trimmed)}`;
|
||||
}
|
||||
|
||||
private getAvailabilityState(source?: RoomSignalSourceInput | null): KlipyAvailabilityState {
|
||||
return this.availabilityByKey()[this.getAvailabilityKey(this.getSourceSelector(source))]
|
||||
?? { enabled: false,
|
||||
loading: true };
|
||||
}
|
||||
|
||||
private setAvailabilityState(key: string, state: KlipyAvailabilityState): void {
|
||||
this.availabilityByKey.update((availabilityByKey) => ({
|
||||
...availabilityByKey,
|
||||
[key]: state
|
||||
}));
|
||||
}
|
||||
|
||||
private getSourceSelector(source?: RoomSignalSourceInput | null): ServerSourceSelector | undefined {
|
||||
return this.serverDirectory.buildRoomSignalSelector(source ?? undefined);
|
||||
}
|
||||
|
||||
private getAvailabilityKey(selector?: ServerSourceSelector): string {
|
||||
if (selector?.sourceId) {
|
||||
return `id:${selector.sourceId}`;
|
||||
}
|
||||
|
||||
if (selector?.sourceUrl) {
|
||||
return `url:${selector.sourceUrl}`;
|
||||
}
|
||||
|
||||
return DEFAULT_AVAILABILITY_KEY;
|
||||
}
|
||||
|
||||
private getPreferredLocale(): string | null {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import { KlipyService } from '../application/services/klipy.service';
|
||||
import type { RoomSignalSourceInput } from '../../server-directory';
|
||||
|
||||
@Directive({
|
||||
selector: 'img[appChatImageProxyFallback]',
|
||||
@@ -15,6 +16,7 @@ import { KlipyService } from '../application/services/klipy.service';
|
||||
})
|
||||
export class ChatImageProxyFallbackDirective {
|
||||
readonly sourceUrl = input('', { alias: 'appChatImageProxyFallback' });
|
||||
readonly signalSource = input<RoomSignalSourceInput | null>(null);
|
||||
|
||||
private readonly klipy = inject(KlipyService);
|
||||
private readonly renderedSource = signal('');
|
||||
@@ -38,7 +40,7 @@ export class ChatImageProxyFallbackDirective {
|
||||
return;
|
||||
}
|
||||
|
||||
const proxyUrl = this.klipy.buildImageProxyUrl(this.sourceUrl());
|
||||
const proxyUrl = this.klipy.buildImageProxyUrl(this.sourceUrl(), this.signalSource());
|
||||
|
||||
if (!proxyUrl || proxyUrl === this.renderedSource()) {
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
<app-chat-message-composer
|
||||
[replyTo]="replyTo()"
|
||||
[showKlipyGifPicker]="showKlipyGifPicker()"
|
||||
[klipyEnabled]="klipyEnabled()"
|
||||
[klipySignalSource]="currentRoom()"
|
||||
(messageSubmitted)="handleMessageSubmitted($event)"
|
||||
(typingStarted)="handleTypingStarted()"
|
||||
(replyCleared)="clearReply()"
|
||||
@@ -50,6 +52,7 @@
|
||||
[style.right.px]="klipyGifPickerAnchorRight()"
|
||||
>
|
||||
<app-klipy-gif-picker
|
||||
[signalSource]="currentRoom()"
|
||||
(gifSelected)="handleKlipyGifSelected($event)"
|
||||
(closed)="closeKlipyGifPicker()"
|
||||
/>
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
HostListener,
|
||||
ViewChild,
|
||||
computed,
|
||||
effect,
|
||||
inject,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
@@ -11,7 +12,7 @@ import { Store } from '@ngrx/store';
|
||||
import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service';
|
||||
import { RealtimeSessionFacade } from '../../../../core/realtime';
|
||||
import { Attachment, AttachmentFacade } from '../../../attachment';
|
||||
import { KlipyGif } from '../../application/services/klipy.service';
|
||||
import { KlipyGif, KlipyService } from '../../application/services/klipy.service';
|
||||
import { MessagesActions } from '../../../../store/messages/messages.actions';
|
||||
import {
|
||||
selectAllMessages,
|
||||
@@ -54,10 +55,11 @@ export class ChatMessagesComponent {
|
||||
private readonly store = inject(Store);
|
||||
private readonly webrtc = inject(RealtimeSessionFacade);
|
||||
private readonly attachmentsSvc = inject(AttachmentFacade);
|
||||
private readonly klipy = inject(KlipyService);
|
||||
|
||||
readonly allMessages = this.store.selectSignal(selectAllMessages);
|
||||
private readonly activeChannelId = this.store.selectSignal(selectActiveChannelId);
|
||||
private readonly currentRoom = this.store.selectSignal(selectCurrentRoom);
|
||||
readonly currentRoom = this.store.selectSignal(selectCurrentRoom);
|
||||
|
||||
readonly loading = this.store.selectSignal(selectMessagesLoading);
|
||||
readonly syncing = this.store.selectSignal(selectMessagesSyncing);
|
||||
@@ -78,6 +80,7 @@ export class ChatMessagesComponent {
|
||||
readonly conversationKey = computed(
|
||||
() => `${this.currentRoom()?.id ?? 'no-room'}:${this.activeChannelId() ?? 'general'}`
|
||||
);
|
||||
readonly klipyEnabled = computed(() => this.klipy.isEnabled(this.currentRoom()));
|
||||
readonly composerBottomPadding = signal(140);
|
||||
readonly klipyGifPickerAnchorRight = signal(16);
|
||||
readonly replyTo = signal<Message | null>(null);
|
||||
@@ -85,6 +88,12 @@ export class ChatMessagesComponent {
|
||||
readonly lightboxAttachment = signal<Attachment | null>(null);
|
||||
readonly imageContextMenu = signal<ChatMessageImageContextMenuEvent | null>(null);
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
void this.klipy.refreshAvailability(this.currentRoom());
|
||||
});
|
||||
}
|
||||
|
||||
@HostListener('window:resize')
|
||||
onWindowResize(): void {
|
||||
if (this.showKlipyGifPicker()) {
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
(drop)="onDrop($event)"
|
||||
>
|
||||
<div class="absolute bottom-3 right-3 z-10 flex items-center gap-2 m-0.5">
|
||||
@if (klipy.isEnabled()) {
|
||||
@if (klipyEnabled()) {
|
||||
<button
|
||||
#klipyTrigger
|
||||
type="button"
|
||||
@@ -189,8 +189,8 @@
|
||||
[class.border-primary]="dragActive()"
|
||||
[class.chat-textarea-expanded]="textareaExpanded()"
|
||||
[class.ctrl-resize]="ctrlHeld()"
|
||||
[class.pr-16]="!klipy.isEnabled()"
|
||||
[class.pr-40]="klipy.isEnabled()"
|
||||
[class.pr-16]="!klipyEnabled()"
|
||||
[class.pr-40]="klipyEnabled()"
|
||||
></textarea>
|
||||
|
||||
@if (dragActive()) {
|
||||
@@ -207,6 +207,7 @@
|
||||
<div class="relative h-12 w-12 overflow-hidden rounded-lg bg-secondary/80">
|
||||
<img
|
||||
[appChatImageProxyFallback]="pendingKlipyGif()!.previewUrl || pendingKlipyGif()!.url"
|
||||
[signalSource]="klipySignalSource()"
|
||||
[alt]="pendingKlipyGif()!.title || 'KLIPY GIF'"
|
||||
class="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
|
||||
@@ -23,6 +23,7 @@ import type { ClipboardFilePayload } from '../../../../../../core/platform/elect
|
||||
import { ElectronBridgeService } from '../../../../../../core/platform/electron/electron-bridge.service';
|
||||
import { KlipyGif, KlipyService } from '../../../../application/services/klipy.service';
|
||||
import { Message } from '../../../../../../shared-kernel';
|
||||
import type { RoomSignalSourceInput } from '../../../../../server-directory';
|
||||
import { ChatImageProxyFallbackDirective } from '../../../chat-image-proxy-fallback.directive';
|
||||
import { TypingIndicatorComponent } from '../../../typing-indicator/typing-indicator.component';
|
||||
import { ChatMarkdownService } from '../../services/chat-markdown.service';
|
||||
@@ -66,6 +67,8 @@ export class ChatMessageComposerComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
readonly replyTo = input<Message | null>(null);
|
||||
readonly showKlipyGifPicker = input(false);
|
||||
readonly klipyEnabled = input(false);
|
||||
readonly klipySignalSource = input<RoomSignalSourceInput | null>(null);
|
||||
|
||||
readonly messageSubmitted = output<ChatMessageComposerSubmitEvent>();
|
||||
readonly typingStarted = output();
|
||||
@@ -73,7 +76,7 @@ export class ChatMessageComposerComponent implements AfterViewInit, OnDestroy {
|
||||
readonly heightChanged = output<number>();
|
||||
readonly klipyGifPickerToggleRequested = output();
|
||||
|
||||
readonly klipy = inject(KlipyService);
|
||||
private readonly klipy = inject(KlipyService);
|
||||
private readonly markdown = inject(ChatMarkdownService);
|
||||
private readonly electronBridge = inject(ElectronBridgeService);
|
||||
|
||||
@@ -207,7 +210,7 @@ export class ChatMessageComposerComponent implements AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
toggleKlipyGifPicker(): void {
|
||||
if (!this.klipy.isEnabled())
|
||||
if (!this.klipyEnabled())
|
||||
return;
|
||||
|
||||
this.klipyGifPickerToggleRequested.emit();
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
>
|
||||
<img
|
||||
[appChatImageProxyFallback]="gif.previewUrl || gif.url"
|
||||
[signalSource]="signalSource()"
|
||||
[alt]="gif.title || 'KLIPY GIF'"
|
||||
class="h-full w-full object-contain p-1.5 transition-transform duration-200 group-hover:scale-[1.03]"
|
||||
loading="lazy"
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
OnInit,
|
||||
ViewChild,
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
lucideX
|
||||
} from '@ng-icons/lucide';
|
||||
import { KlipyGif, KlipyService } from '../../application/services/klipy.service';
|
||||
import type { RoomSignalSourceInput } from '../../../server-directory';
|
||||
import { ChatImageProxyFallbackDirective } from '../chat-image-proxy-fallback.directive';
|
||||
|
||||
const KLIPY_CARD_MIN_WIDTH = 140;
|
||||
@@ -48,6 +50,8 @@ const KLIPY_CARD_FALLBACK_SIZE = 160;
|
||||
templateUrl: './klipy-gif-picker.component.html'
|
||||
})
|
||||
export class KlipyGifPickerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
readonly signalSource = input<RoomSignalSourceInput | null>(null);
|
||||
|
||||
readonly gifSelected = output<KlipyGif>();
|
||||
readonly closed = output<undefined>();
|
||||
|
||||
@@ -128,7 +132,7 @@ export class KlipyGifPickerComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.klipy.searchGifs(this.searchQuery, this.currentPage)
|
||||
this.klipy.searchGifs(this.searchQuery, this.currentPage, undefined, this.signalSource())
|
||||
);
|
||||
|
||||
if (requestId !== this.requestId)
|
||||
|
||||
Reference in New Issue
Block a user