perf: Optimizing the image loading
Does no longer load all klipy images through image proxy from signal server. Improves loading performance.
This commit is contained in:
@@ -109,7 +109,7 @@ sequenceDiagram
|
||||
|
||||
## GIF integration
|
||||
|
||||
`KlipyService` checks availability on the active server, then proxies search requests through the server API. Images are rendered via an image proxy endpoint to avoid mixed-content issues.
|
||||
`KlipyService` checks availability on the active server, then proxies search requests through the server API. Rendered remote images now attempt a direct load first and only fall back to the image proxy after the browser reports a load failure, which is the practical approximation of a CORS or mixed-content fallback path in the renderer.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
|
||||
@@ -135,6 +135,10 @@ export class KlipyService {
|
||||
}
|
||||
|
||||
buildRenderableImageUrl(url: string): string {
|
||||
return this.normalizeMediaUrl(url);
|
||||
}
|
||||
|
||||
buildImageProxyUrl(url: string): string {
|
||||
const trimmed = this.normalizeMediaUrl(url);
|
||||
|
||||
if (!trimmed)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
Directive,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import { KlipyService } from '../application/klipy.service';
|
||||
|
||||
@Directive({
|
||||
selector: 'img[appChatImageProxyFallback]',
|
||||
standalone: true
|
||||
})
|
||||
export class ChatImageProxyFallbackDirective {
|
||||
readonly sourceUrl = input('', { alias: 'appChatImageProxyFallback' });
|
||||
|
||||
private readonly klipy = inject(KlipyService);
|
||||
private readonly renderedSource = signal('');
|
||||
private hasAppliedProxyFallback = false;
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
this.hasAppliedProxyFallback = false;
|
||||
this.renderedSource.set(this.klipy.buildRenderableImageUrl(this.sourceUrl()));
|
||||
});
|
||||
}
|
||||
|
||||
@HostBinding('src')
|
||||
get src(): string {
|
||||
return this.renderedSource();
|
||||
}
|
||||
|
||||
@HostListener('error')
|
||||
handleError(): void {
|
||||
if (this.hasAppliedProxyFallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
const proxyUrl = this.klipy.buildImageProxyUrl(this.sourceUrl());
|
||||
|
||||
if (!proxyUrl || proxyUrl === this.renderedSource()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasAppliedProxyFallback = true;
|
||||
this.renderedSource.set(proxyUrl);
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,7 @@
|
||||
<div class="group flex max-w-sm items-center gap-3 rounded-xl border border-border bg-secondary/60 px-2.5 py-2">
|
||||
<div class="relative h-12 w-12 overflow-hidden rounded-lg bg-secondary/80">
|
||||
<img
|
||||
[src]="getPendingKlipyGifPreviewUrl()"
|
||||
[appChatImageProxyFallback]="pendingKlipyGif()!.previewUrl || pendingKlipyGif()!.url"
|
||||
[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/klipy.service';
|
||||
import { Message } from '../../../../../../shared-kernel';
|
||||
import { ChatImageProxyFallbackDirective } from '../../../chat-image-proxy-fallback.directive';
|
||||
import { TypingIndicatorComponent } from '../../../typing-indicator/typing-indicator.component';
|
||||
import { ChatMarkdownService } from '../../services/chat-markdown.service';
|
||||
import { ChatMessageComposerSubmitEvent } from '../../models/chat-messages.models';
|
||||
@@ -40,6 +41,7 @@ const DEFAULT_TEXTAREA_HEIGHT = 62;
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
NgIcon,
|
||||
ChatImageProxyFallbackDirective,
|
||||
TypingIndicatorComponent
|
||||
],
|
||||
viewProviders: [
|
||||
@@ -231,12 +233,6 @@ export class ChatMessageComposerComponent implements AfterViewInit, OnDestroy {
|
||||
requestAnimationFrame(() => this.messageInputRef?.nativeElement.focus());
|
||||
}
|
||||
|
||||
getPendingKlipyGifPreviewUrl(): string {
|
||||
const gif = this.pendingKlipyGif();
|
||||
|
||||
return gif ? this.klipy.buildRenderableImageUrl(gif.previewUrl || gif.url) : '';
|
||||
}
|
||||
|
||||
formatBytes(bytes: number): string {
|
||||
const units = [
|
||||
'B',
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
>
|
||||
<div class="relative mt-2 inline-block overflow-hidden rounded-md border border-border/60 bg-secondary/20">
|
||||
<img
|
||||
[src]="getMarkdownImageSource(node.url)"
|
||||
[appChatImageProxyFallback]="node.url"
|
||||
[alt]="node.alt || 'Shared image'"
|
||||
class="block max-h-80 max-w-full w-auto"
|
||||
loading="lazy"
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
ChatVideoPlayerComponent,
|
||||
UserAvatarComponent
|
||||
} from '../../../../../../shared';
|
||||
import { ChatImageProxyFallbackDirective } from '../../../chat-image-proxy-fallback.directive';
|
||||
import {
|
||||
ChatMessageDeleteEvent,
|
||||
ChatMessageEditEvent,
|
||||
@@ -102,6 +103,7 @@ interface ChatMessageAttachmentViewModel extends Attachment {
|
||||
ChatVideoPlayerComponent,
|
||||
RemarkModule,
|
||||
MermaidComponent,
|
||||
ChatImageProxyFallbackDirective,
|
||||
UserAvatarComponent
|
||||
],
|
||||
viewProviders: [
|
||||
@@ -318,10 +320,6 @@ export class ChatMessageItemComponent {
|
||||
);
|
||||
}
|
||||
|
||||
getMarkdownImageSource(url?: string): string {
|
||||
return url ? this.klipy.buildRenderableImageUrl(url) : '';
|
||||
}
|
||||
|
||||
getMermaidCode(code?: string): string {
|
||||
return (code ?? '').replace(MERMAID_LINE_BREAK_PATTERN, '\n').trim();
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
[style.aspect-ratio]="gifAspectRatio(gif)"
|
||||
>
|
||||
<img
|
||||
[src]="gifPreviewUrl(gif)"
|
||||
[appChatImageProxyFallback]="gif.previewUrl || gif.url"
|
||||
[alt]="gif.title || 'KLIPY GIF'"
|
||||
class="h-full w-full object-cover transition-transform duration-200 group-hover:scale-[1.03]"
|
||||
loading="lazy"
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
lucideX
|
||||
} from '@ng-icons/lucide';
|
||||
import { KlipyGif, KlipyService } from '../../application/klipy.service';
|
||||
import { ChatImageProxyFallbackDirective } from '../chat-image-proxy-fallback.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'app-klipy-gif-picker',
|
||||
@@ -28,7 +29,8 @@ import { KlipyGif, KlipyService } from '../../application/klipy.service';
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
NgIcon
|
||||
NgIcon,
|
||||
ChatImageProxyFallbackDirective
|
||||
],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
@@ -112,10 +114,6 @@ export class KlipyGifPickerComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
return '1 / 1';
|
||||
}
|
||||
|
||||
gifPreviewUrl(gif: KlipyGif): string {
|
||||
return this.klipy.buildRenderableImageUrl(gif.previewUrl || gif.url);
|
||||
}
|
||||
|
||||
private async loadResults(reset: boolean): Promise<void> {
|
||||
if (reset) {
|
||||
this.currentPage = 1;
|
||||
|
||||
Reference in New Issue
Block a user