feat: Rename to Toju and add translation
Some checks failed
Build Android APK / build-android-apk (push) Has been cancelled
Deploy Web Apps / deploy (push) Has been cancelled
Queue Release Build / prepare (push) Has been cancelled
Queue Release Build / build-linux (push) Has been cancelled
Queue Release Build / build-windows (push) Has been cancelled
Queue Release Build / finalize (push) Has been cancelled
Some checks failed
Build Android APK / build-android-apk (push) Has been cancelled
Deploy Web Apps / deploy (push) Has been cancelled
Queue Release Build / prepare (push) Has been cancelled
Queue Release Build / build-linux (push) Has been cancelled
Queue Release Build / build-windows (push) Has been cancelled
Queue Release Build / finalize (push) Has been cancelled
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
<div
|
||||
class="flex h-[min(70vh,42rem)] w-full flex-col overflow-hidden rounded-[1.65rem] border border-border/80 shadow-2xl ring-1 ring-white/5"
|
||||
role="dialog"
|
||||
aria-label="KLIPY GIF picker"
|
||||
[attr.aria-label]="'chat.gifPicker.ariaLabel' | translate"
|
||||
style="background: hsl(var(--background) / 0.85); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px)"
|
||||
>
|
||||
@if (!isMobile()) {
|
||||
<div class="flex items-start justify-between gap-4 border-b border-border/70 bg-secondary/15 px-5 py-4">
|
||||
<div>
|
||||
<div class="text-[11px] font-semibold uppercase tracking-[0.28em] text-primary">KLIPY</div>
|
||||
<h3 class="mt-1 text-lg font-semibold text-foreground">Choose a GIF</h3>
|
||||
<div class="text-[11px] font-semibold uppercase tracking-[0.28em] text-primary">{{ 'chat.gifPicker.klipy' | translate }}</div>
|
||||
<h3 class="mt-1 text-lg font-semibold text-foreground">{{ 'chat.gifPicker.chooseGif' | translate }}</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
{{ searchQuery.trim() ? 'Search results from KLIPY.' : 'Trending GIFs from KLIPY.' }}
|
||||
{{ searchQuery.trim() ? ('chat.gifPicker.searchResults' | translate) : ('chat.gifPicker.trending' | translate) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
type="button"
|
||||
(click)="close()"
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-border/70 bg-secondary/30 text-muted-foreground transition-colors hover:bg-secondary/80 hover:text-foreground"
|
||||
aria-label="Close GIF picker"
|
||||
[attr.aria-label]="'chat.gifPicker.closeAria' | translate"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideX"
|
||||
@@ -39,7 +39,7 @@
|
||||
type="text"
|
||||
[ngModel]="searchQuery"
|
||||
(ngModelChange)="onSearchQueryChanged($event)"
|
||||
[placeholder]="isMobile() ? 'Search KLIPY and add a gif to the chat' : 'Search KLIPY'"
|
||||
[placeholder]="isMobile() ? ('chat.gifPicker.searchMobile' | translate) : ('chat.gifPicker.searchDesktop' | translate)"
|
||||
class="relative z-0 w-full rounded-xl border border-border/80 bg-background/70 px-10 py-3 text-sm text-foreground placeholder:text-muted-foreground shadow-sm backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
/>
|
||||
</label>
|
||||
@@ -56,7 +56,7 @@
|
||||
(click)="retry()"
|
||||
class="rounded-lg bg-destructive px-3 py-1.5 text-xs font-medium text-destructive-foreground transition-colors hover:bg-destructive/90"
|
||||
>
|
||||
Retry
|
||||
{{ 'chat.gifPicker.retry' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@@ -64,7 +64,7 @@
|
||||
@if (loading() && results().length === 0) {
|
||||
<div class="flex h-full min-h-56 flex-col items-center justify-center gap-3 text-muted-foreground">
|
||||
<span class="h-6 w-6 animate-spin rounded-full border-2 border-primary/20 border-t-primary"></span>
|
||||
<p class="text-sm">Loading GIFs from KLIPY...</p>
|
||||
<p class="text-sm">{{ 'chat.gifPicker.loading' | translate }}</p>
|
||||
</div>
|
||||
} @else if (results().length === 0) {
|
||||
<div
|
||||
@@ -77,8 +77,8 @@
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-foreground">No GIFs found</p>
|
||||
<p class="mt-1 text-sm">Try another search term or clear the search to browse trending GIFs.</p>
|
||||
<p class="text-sm font-medium text-foreground">{{ 'chat.gifPicker.noGifsFound' | translate }}</p>
|
||||
<p class="mt-1 text-sm">{{ 'chat.gifPicker.noGifsHint' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
@@ -101,22 +101,22 @@
|
||||
<img
|
||||
[appChatImageProxyFallback]="gif.previewUrl || gif.url"
|
||||
[signalSource]="signalSource()"
|
||||
[alt]="gif.title || 'KLIPY GIF'"
|
||||
[alt]="gif.title || ('chat.gifPicker.klipyGif' | translate)"
|
||||
class="h-full w-full object-contain p-1.5 transition-transform duration-200 group-hover:scale-[1.03]"
|
||||
loading="lazy"
|
||||
/>
|
||||
<span
|
||||
class="pointer-events-none absolute bottom-2 left-2 rounded-full bg-black/70 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.24em] text-white/90 backdrop-blur-sm"
|
||||
>
|
||||
KLIPY
|
||||
{{ 'chat.gifPicker.klipy' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
@if (!isMobile()) {
|
||||
<div class="px-3 py-2">
|
||||
<p class="truncate text-xs font-medium text-foreground">
|
||||
{{ gif.title || 'KLIPY GIF' }}
|
||||
{{ gif.title || ('chat.gifPicker.klipyGif' | translate) }}
|
||||
</p>
|
||||
<p class="mt-1 text-[10px] uppercase tracking-[0.22em] text-muted-foreground">Click to select</p>
|
||||
<p class="mt-1 text-[10px] uppercase tracking-[0.22em] text-muted-foreground">{{ 'chat.gifPicker.clickToSelect' | translate }}</p>
|
||||
</div>
|
||||
}
|
||||
</button>
|
||||
@@ -130,7 +130,7 @@
|
||||
(click)="loadMore()"
|
||||
[disabled]="loading()"
|
||||
class="inline-flex h-10 w-10 items-center justify-center rounded-full border border-border/80 bg-background/60 text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground disabled:cursor-not-allowed disabled:opacity-60"
|
||||
[attr.aria-label]="loading() ? 'Loading more GIFs' : 'Load more GIFs'"
|
||||
[attr.aria-label]="loading() ? ('chat.gifPicker.loadingMoreAria' | translate) : ('chat.gifPicker.loadMoreAria' | translate)"
|
||||
>
|
||||
@if (loading()) {
|
||||
<span class="h-4 w-4 animate-spin rounded-full border-2 border-primary/20 border-t-primary"></span>
|
||||
@@ -148,7 +148,7 @@
|
||||
|
||||
@if (!isMobile()) {
|
||||
<div class="flex items-center justify-between gap-4 border-t border-border/70 bg-secondary/10 px-5 py-4">
|
||||
<p class="text-xs text-muted-foreground">Click a GIF to select it. Powered by KLIPY.</p>
|
||||
<p class="text-xs text-muted-foreground">{{ 'chat.gifPicker.footer' | translate }}</p>
|
||||
|
||||
@if (hasNext()) {
|
||||
<button
|
||||
@@ -157,7 +157,7 @@
|
||||
[disabled]="loading()"
|
||||
class="rounded-full border border-border/80 bg-background/60 px-4 py-2 text-xs font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
{{ loading() ? 'Loading...' : 'Load more' }}
|
||||
{{ loading() ? ('chat.gifPicker.loadingMore' | translate) : ('chat.gifPicker.loadMore' | translate) }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import { KlipyGif, KlipyService } from '../../application/services/klipy.service';
|
||||
import type { RoomSignalSourceInput } from '../../../server-directory';
|
||||
import { ChatImageProxyFallbackDirective } from '../chat-image-proxy-fallback.directive';
|
||||
import { AppI18nService, APP_TRANSLATE_IMPORTS } from '../../../../core/i18n';
|
||||
import { ViewportService } from '../../../../core/platform';
|
||||
|
||||
const KLIPY_CARD_MIN_WIDTH = 140;
|
||||
@@ -40,7 +41,8 @@ const KLIPY_CARD_FALLBACK_SIZE = 160;
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
NgIcon,
|
||||
ChatImageProxyFallbackDirective
|
||||
ChatImageProxyFallbackDirective,
|
||||
...APP_TRANSLATE_IMPORTS
|
||||
],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
@@ -62,6 +64,7 @@ export class KlipyGifPickerComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
|
||||
private readonly klipy = inject(KlipyService);
|
||||
private readonly viewport = inject(ViewportService);
|
||||
private readonly appI18n = inject(AppI18nService);
|
||||
readonly isMobile = this.viewport.isMobile;
|
||||
private currentPage = 1;
|
||||
private searchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -136,29 +139,19 @@ export class KlipyGifPickerComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
this.errorMessage.set('');
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.klipy.searchGifs(this.searchQuery, this.currentPage, undefined, this.signalSource())
|
||||
);
|
||||
const response = await firstValueFrom(this.klipy.searchGifs(this.searchQuery, this.currentPage, undefined, this.signalSource()));
|
||||
|
||||
if (requestId !== this.requestId)
|
||||
return;
|
||||
|
||||
this.results.set(
|
||||
reset
|
||||
? response.results
|
||||
: this.mergeResults(this.results(), response.results)
|
||||
);
|
||||
this.results.set(reset ? response.results : this.mergeResults(this.results(), response.results));
|
||||
|
||||
this.hasNext.set(response.hasNext);
|
||||
} catch (error) {
|
||||
if (requestId !== this.requestId)
|
||||
return;
|
||||
|
||||
this.errorMessage.set(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Failed to load GIFs from KLIPY.'
|
||||
);
|
||||
this.errorMessage.set(error instanceof Error ? error.message : this.appI18n.instant('chat.gifPicker.loadFailed'));
|
||||
|
||||
if (reset) {
|
||||
this.results.set([]);
|
||||
@@ -202,17 +195,9 @@ export class KlipyGifPickerComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
};
|
||||
}
|
||||
|
||||
const maxScale = Math.min(
|
||||
KLIPY_CARD_MAX_WIDTH / gif.width,
|
||||
KLIPY_CARD_MAX_HEIGHT / gif.height
|
||||
);
|
||||
const minScale = Math.max(
|
||||
KLIPY_CARD_MIN_WIDTH / gif.width,
|
||||
KLIPY_CARD_MIN_HEIGHT / gif.height
|
||||
);
|
||||
const scale = minScale <= maxScale
|
||||
? Math.min(maxScale, Math.max(minScale, 1))
|
||||
: maxScale;
|
||||
const maxScale = Math.min(KLIPY_CARD_MAX_WIDTH / gif.width, KLIPY_CARD_MAX_HEIGHT / gif.height);
|
||||
const minScale = Math.max(KLIPY_CARD_MIN_WIDTH / gif.width, KLIPY_CARD_MIN_HEIGHT / gif.height);
|
||||
const scale = minScale <= maxScale ? Math.min(maxScale, Math.max(minScale, 1)) : maxScale;
|
||||
const scaledWidth = Math.round(gif.width * scale);
|
||||
const scaledHeight = Math.round(gif.height * scale);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user