Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"serve": {
"builder": "@angular/build:dev-server",
"options": {
"proxyConfig": "proxy.conf.ts"
"proxyConfig": "proxy.conf.json"
},
"configurations": {
"production": {
Expand Down
18 changes: 18 additions & 0 deletions proxy.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"/api": {
"target": "http://backend:8080",
"secure": false,
"changeOrigin": true,
"headers": {
"X-Forwarded-User": "traP"
}
},
"/traq-api": {
"target": "http://backend:8080",
"secure": false,
"changeOrigin": true,
"headers": {
"X-Forwarded-User": "traP"
}
}
}
30 changes: 0 additions & 30 deletions proxy.conf.ts

This file was deleted.

6 changes: 0 additions & 6 deletions src/app/core/models/stamp.model.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
export interface Stamp {
id: string;
name: string;
creatorId?: string;
createdAt?: string;
updatedAt?: string;
fileId?: string;
isUnicode?: boolean;
hasThumbnail?: boolean;
}

//GIF用
Expand Down
12 changes: 6 additions & 6 deletions src/app/core/services/stamp.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ describe('StampService', () => {
req.flush(mockStamps);

expect(service.getStamps().length).toBe(2);
expect(service.getStamps()[0]).toBe('stamp1');
expect(service.getStamps()[1]).toBe('stamp2');
expect(service.getStamps()[0]).toEqual({ id: 'stamp-id-1', name: 'stamp1' });
expect(service.getStamps()[1]).toEqual({ id: 'stamp-id-2', name: 'stamp2' });
});

it('should not send duplicate request if stamps are already loaded', () => {
Expand Down Expand Up @@ -106,8 +106,8 @@ describe('StampService', () => {
expect(result).toBeNull();
});
it('should return existing observable if request is in-flight', () => {
let result1: Map<string, string> | undefined;
let result2: Map<string, string> | undefined;
let result1: Stamp[] | undefined;
let result2: Stamp[] | undefined;

service.loadStamps().subscribe((data) => (result1 = data));
const req = httpTestingController.expectOne('/traq-api/stamps');
Expand All @@ -116,8 +116,8 @@ describe('StampService', () => {
httpTestingController.expectNone('/traq-api/stamps');
req.flush(mockStamps);

expect(result1?.get('stamp1')).toBe('stamp-id-1');
expect(result2?.get('stamp1')).toBe('stamp-id-1');
expect(result1?.[0].id).toBe('stamp-id-1');
expect(result2?.[0].id).toBe('stamp-id-1');
});

it('should create and cache Image element when stamp is found', () => {
Expand Down
27 changes: 14 additions & 13 deletions src/app/core/services/stamp.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,39 @@ export class StampService {
private http = inject(HttpClient);
private readonly traQApiUrl = '/traq-api/stamps';

private stampSinal = signal<Map<string, string>>(new Map());
private stampsSignal = signal<Stamp[]>([]);
private stampMap = new Map<string, string>();
private stampCache = new Map<string, AnimatedStampData>();
private loadStamps$?: Observable<Map<string, string>>;
private loadStamps$?: Observable<Stamp[]>;

loadStamps() {
if (this.stampSinal().size > 0) return of(this.stampSinal());
loadStamps(): Observable<Stamp[]> {
if (this.stampsSignal().length > 0) return of(this.stampsSignal());
if (this.loadStamps$) return this.loadStamps$;

this.loadStamps$ = this.http.get<Stamp[]>(this.traQApiUrl).pipe(
map((stamps) => {
const stampMap = new Map<string, string>();
this.stampMap.clear();
stamps.forEach((stamp) => {
stampMap.set(stamp.name, stamp.id);
this.stampMap.set(stamp.name, stamp.id);
});
this.stampSinal.set(stampMap);
return stampMap;
this.stampsSignal.set(stamps);
return stamps;
}),
catchError((err) => {
console.error('Failed to load stamps:', err);
return of(new Map<string, string>());
return of([]);
}),
shareReplay(1),
);
return this.loadStamps$;
}

getStamps(): string[] {
return Array.from(this.stampSinal().keys());
getStamps(): Stamp[] {
return this.stampsSignal();
}

getStampImage(stampName: string): AnimatedStampData | null {
const stampId = this.stampSinal().get(stampName);
const stampId = this.stampMap.get(stampName);
if (!stampId) return null;

if (this.stampCache.has(stampId)) {
Expand Down Expand Up @@ -99,7 +100,7 @@ export class StampService {
}

getStampURL(stampName: string): string | null {
const stampId = this.stampSinal().get(stampName);
const stampId = this.stampMap.get(stampName);
if (!stampId) return null;
return `${this.traQApiUrl}/${stampId}/image`;
}
Expand Down
13 changes: 7 additions & 6 deletions src/app/features/player/player.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@
<!-- 仮想スクロール ビューポート -->
<cdk-virtual-scroll-viewport itemSize="44" class="stamp-viewport">
<div *cdkVirtualFor="let row of stampRows; trackBy: trackByRow" class="stamp-row">
@for (stamp of row; track stamp) {
<button type="button" class="stamp-item-btn" [title]="':' + stamp + ':'" (click)="insertStamp(stamp)"
(mouseenter)="onStampHover(stamp)">
<img [src]="getStampImageUrl(stamp)" [alt]="stamp" loading="lazy" class="stamp-img" />
@for (stamp of row; track stamp.id) {
<button type="button" class="stamp-item-btn" [title]="':' + stamp.name + ':'"
(click)="insertStamp(stamp.name)" (mouseenter)="onStampHover(stamp)">
<img [src]="'/traq-api/stamps/' + stamp.id + '/image'" [alt]="stamp.name" loading="lazy"
class="stamp-img" />
</button>
}
</div>
Expand All @@ -89,8 +90,8 @@
</cdk-virtual-scroll-viewport>
<div class="stamp-preview-footer">
@if (hoveredStamp) {
<img [src]="getStampImageUrl(hoveredStamp)" [alt]="hoveredStamp" class="preview-img" />
<span class="preview-name">:{{ hoveredStamp }}:</span>
<img [src]="'/traq-api/stamps/' + hoveredStamp.id + '/image'" [alt]="hoveredStamp.name" class="preview-img" />
<span class="preview-name">:{{ hoveredStamp.name }}:</span>
}
</div>
</div>
Expand Down
58 changes: 33 additions & 25 deletions src/app/features/player/player.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { vi } from 'vitest';
import { By } from '@angular/platform-browser';
import Hls from 'hls.js';
import { Video } from '../../core/models/video.model';
import { Stamp } from '../../core/models/stamp.model';
import { environment } from '../../../environments/environment';

let mockPlayingCallback: Function | null = null;
Expand Down Expand Up @@ -1111,36 +1112,36 @@ describe('PlayerComponent', () => {
});
// スタンプ検索テスト
it('should return stamp rows based on search input', () => {
const mockStamps: string[] = [
'stamp1',
'stamp2',
'stamp3',
'stamp4',
'stamp5',
'stamp6',
'stamp7',
'stamp8',
'stamp9',
'stamp10',
'test1',
'test2',
'test3',
'test4',
'test5',
const mockStamps: Stamp[] = [
{ id: 'id-1', name: 'stamp1' },
{ id: 'id-2', name: 'stamp2' },
{ id: 'id-3', name: 'stamp3' },
{ id: 'id-4', name: 'stamp4' },
{ id: 'id-5', name: 'stamp5' },
{ id: 'id-6', name: 'stamp6' },
{ id: 'id-7', name: 'stamp7' },
{ id: 'id-8', name: 'stamp8' },
{ id: 'id-9', name: 'stamp9' },
{ id: 'id-10', name: 'stamp10' },
{ id: 'id-11', name: 'test1' },
{ id: 'id-12', name: 'test2' },
{ id: 'id-13', name: 'test3' },
{ id: 'id-14', name: 'test4' },
{ id: 'id-15', name: 'test5' },
];
vi.spyOn(stampService, 'getStamps').mockReturnValue(mockStamps);
component.stampSearchQuery = 'stamp';
expect(component.stampRows).toEqual([
['stamp1', 'stamp2', 'stamp3', 'stamp4', 'stamp5', 'stamp6', 'stamp7', 'stamp8'],
['stamp9', 'stamp10'],
mockStamps.slice(0, 8),
mockStamps.slice(8, 10),
]);
});
// スタンプピッカーの開閉テスト
it('should toggle stamp picker', () => {
vi.spyOn(stampService, 'getStamps').mockReturnValue([]);
const mockLoadStampsSpy = vi
.spyOn(stampService, 'loadStamps')
.mockReturnValue(of(new Map<string, string>()));
.mockReturnValue(of([]));
mockLoadStampsSpy.mockClear();
expect(component.isStampPickerOpen).toBe(false);
// 開
Expand All @@ -1154,7 +1155,10 @@ describe('PlayerComponent', () => {
expect(mockLoadStampsSpy).not.toHaveBeenCalled();

mockLoadStampsSpy.mockClear();
vi.spyOn(stampService, 'getStamps').mockReturnValue(['stamp1', 'stamp2']);
vi.spyOn(stampService, 'getStamps').mockReturnValue([
{ id: 'id-1', name: 'stamp1' },
{ id: 'id-2', name: 'stamp2' },
]);
// 開
component.toggleStampPicker();
expect(component.isStampPickerOpen).toBe(true);
Expand All @@ -1173,16 +1177,20 @@ describe('PlayerComponent', () => {
expect(getStampURLSpy).toHaveBeenCalledWith('stamp1');
});
it('should store hovered stamp name when mouse hovered', () => {
component.onStampHover('stamp1');
expect(component.hoveredStamp).toBe('stamp1');
const mockStamp: Stamp = { id: 'stamp-1', name: 'stamp1' };
component.onStampHover(mockStamp);
expect(component.hoveredStamp).toEqual(mockStamp);
});
it('should trackByRow function return index', () => {
const index = 5;
const row = ['stamp1', 'stamp2'];
expect(component.trackByRow(index, row)).toBe('stamp1');
const row: Stamp[] = [
{ id: 'stamp-id-1', name: 'stamp1' },
{ id: 'stamp-id-2', name: 'stamp2' },
];
expect(component.trackByRow(index, row)).toBe('stamp-id-1');

const index2 = 3;
const row2: string[] = [];
const row2: Stamp[] = [];
expect(component.trackByRow(index2, row2)).toBe('3');
});
// 枠外クリックでメニューを閉じるテスト
Expand Down
28 changes: 15 additions & 13 deletions src/app/features/player/player.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { UserService } from '../../core/services/user.service';
import { StampService } from '../../core/services/stamp.service';
import { Video } from '../../core/models/video.model';
import { Tag } from '../../core/models/tag.model';
import { Stamp } from '../../core/models/stamp.model';
import { environment } from '../../../environments/environment';
import { Comment } from './comment';
import { EditVideoDialogComponent } from './edit-video-dialog.component';
Expand Down Expand Up @@ -114,7 +115,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit {
likeCount = 0;
userIconUrl: string | null = null;
stampSearchQuery = '';
hoveredStamp: string | null = null;
hoveredStamp: Stamp | null = null;

ngOnInit(): void {
this.route.paramMap.subscribe((params) => {
Expand Down Expand Up @@ -830,12 +831,13 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit {
);
}

get stampRows(): string[][] {
get stampRows(): Stamp[][] {
const query = this.stampSearchQuery.trim().toLowerCase();
const stamps = this.stampService.getStamps();
const filtered = query
? this.stampService.getStamps().filter((s) => s.toLowerCase().includes(query))
: this.stampService.getStamps();
const rows: string[][] = [];
? stamps.filter((s) => s.name.toLowerCase().includes(query))
: stamps;
const rows: Stamp[][] = [];
for (let i = 0; i < filtered.length; i += this.StampRowNum) {
rows.push(filtered.slice(i, i + this.StampRowNum));
}
Expand All @@ -845,12 +847,12 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit {
toggleStampPicker(): void {
this.isStampPickerOpen = !this.isStampPickerOpen;
if (this.isStampPickerOpen && this.stampService.getStamps().length === 0) {
this.stampService.loadStamps();
this.stampService.loadStamps().subscribe();
}
}

insertStamp(stamp: string): void {
const stampText = `:${stamp}:`;
insertStamp(stampName: string): void {
const stampText = `:${stampName}:`;
const input = this.commentInputRef.nativeElement;
const start = input.selectionStart ?? input.value.length;
const end = input.selectionEnd ?? input.value.length;
Expand All @@ -860,16 +862,16 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit {
input.setSelectionRange(newPos, newPos);
}

getStampImageUrl(stamp: string): string | null {
return this.stampService.getStampURL(stamp);
getStampImageUrl(stampName: string): string | null {
return this.stampService.getStampURL(stampName);
}

onStampHover(stamp: string | null): void {
onStampHover(stamp: Stamp | null): void {
this.hoveredStamp = stamp;
}

trackByRow(index: number, row: string[]): string {
return row[0] ?? index.toString();
trackByRow(index: number, row: Stamp[]): string {
return row[0]?.id ?? index.toString();
}

ngOnDestroy(): void {
Expand Down
Loading