RSS Git Download  Clone
Raw Blame History 9kB 228 lines
import { Component, Inject, OnInit, OnChanges, SimpleChanges, ChangeDetectorRef, CUSTOM_ELEMENTS_SCHEMA, ViewEncapsulation, effect } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BreakpointObserver } from '@angular/cdk/layout';
import { I18nService } from '../../../services/i18n.service';
import { SocketService } from '../../../services/socket.service';
import { CommonService } from '../../../services/common.service';
import { JsonViewDialogService } from '../../../dialogs/json-view-dialog.service';
import { KeyNewOrSetDialogService } from '../../../dialogs/key-new-or-set-dialog.service';
import { MainCommandService } from '../../../services/main-command.service';
import { RedisStateService } from '../../../services/redis-state.service';
import { SettingsService } from '../../../services/settings.service';
import { KeyTypeBase } from './key-type-base';
import { KeyPaging } from './key-paging';
import { KeyPagerInlineComponent } from './key-pager-inline.component';

const intlLocaleMap: Record<string, string> = { 'zn': 'zh-CN', 'no': 'nb', 'fil': 'tl' };

@Component({
    selector: 'p3xr-key-stream',
    standalone: true,
    imports: [CommonModule, FormsModule, MatButtonModule, MatIconModule, MatTooltipModule, KeyPagerInlineComponent],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
    templateUrl: './key-stream.component.html',
    encapsulation: ViewEncapsulation.None,
})
export class KeyStreamComponent extends KeyTypeBase implements OnInit, OnChanges {
    paging: KeyPaging;
    pagedEntries: Array<{ id: string; fields: Array<[string, string]>; data: any; displayData: any; hasDuplicateFields: boolean }> = [];
    private allEntries: Array<{ id: string; fields: Array<[string, string]>; data: any; displayData: any; hasDuplicateFields: boolean }> = [];

    constructor(
        @Inject(I18nService) i18n: I18nService, @Inject(SocketService) socket: SocketService,
        @Inject(CommonService) common: CommonService, @Inject(JsonViewDialogService) jsonViewDialog: JsonViewDialogService,
        @Inject(KeyNewOrSetDialogService) keyNewOrSetDialog: KeyNewOrSetDialogService,
        @Inject(BreakpointObserver) breakpointObserver: BreakpointObserver,
        @Inject(MainCommandService) cmd: MainCommandService,
        @Inject(ChangeDetectorRef) cdr: ChangeDetectorRef,
        @Inject(RedisStateService) redisState: RedisStateService,
        @Inject(SettingsService) settingsService: SettingsService,
    ) {
        super(i18n, socket, common, jsonViewDialog, keyNewOrSetDialog, breakpointObserver, cmd, cdr, redisState, settingsService);
        this.paging = new KeyPaging({ settingsService });

        effect(() => {
            this.i18n.strings();
            if (this.allEntries.length === 0) {
                return;
            }

            this.allEntries = this.allEntries.map((entry) => ({
                ...entry,
                displayData: this.toDisplayData(entry.data, entry.hasDuplicateFields),
            }));
            this.updatePagedItems();
            this.cdr.markForCheck();
        });
    }

    ngOnInit(): void { this.updatePaging(); }
    ngOnChanges(c: SimpleChanges): void { if (c['p3xrValue']) this.updatePaging(); }

    updatePaging(): void {
        if (!this.p3xrValue) return;
        this.allEntries = this.p3xrValue.map((entry: any) => {
            const id = entry[0];
            const rawData = entry[1];
            const fields: Array<[string, string]> = [];
            for (let i = 0; i < rawData.length; i += 2) {
                fields.push([rawData[i], rawData[i + 1]]);
            }
            const hasDuplicateFields = this.hasDuplicateFields(fields);
            const data = hasDuplicateFields ? this.fieldsToArray(fields) : this.fieldsToObject(fields);
            return {
                id,
                fields,
                data,
                displayData: this.toDisplayData(data, hasDuplicateFields),
                hasDuplicateFields,
            };
        });
        this.paging.figurePaging(this.allEntries.length);
        this.updatePagedItems();
    }

    updatePagedItems(): void {
        this.pagedEntries = this.allEntries.slice(this.paging.startIndex, this.paging.endIndex);
    }

    showTimestamp(id: string): string {
        try {
            const ms = parseInt(id.slice(0, id.indexOf('-')));
            const lang = this.i18n.currentLang() || 'en';
            const locale = intlLocaleMap[lang] || lang;
            const date = new Date(ms);
            return date.toLocaleString(locale, { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' });
        } catch { return id; }
    }

    isJsonValue(value: string): boolean {
        if (!value || value.length < 2) return false;
        const first = value.charAt(0);
        if (first !== '{' && first !== '[') return false;
        try {
            JSON.parse(value);
            return true;
        } catch {
            return false;
        }
    }

    formatJsonValue(value: string): string {
        try {
            return JSON.stringify(JSON.parse(value), null, this.settingsService.jsonFormat() ?? 2);
        } catch {
            return value;
        }
    }

    private parseFieldValue(value: string): any {
        try {
            return JSON.parse(value);
        } catch {
            return value;
        }
    }

    private hasDuplicateFields(fields: Array<[string, string]>): boolean {
        const seen = new Set<string>();
        for (const [key] of fields) {
            if (seen.has(key)) {
                return true;
            }
            seen.add(key);
        }
        return false;
    }

    private fieldsToObject(fields: Array<[string, string]>): any {
        const obj: any = {};
        for (const [key, value] of fields) {
            obj[key] = this.parseFieldValue(value);
        }
        return obj;
    }

    private fieldsToArray(fields: Array<[string, string]>): Array<{ field: string; value: any }> {
        return fields.map(([field, value]) => ({
            field,
            value: this.parseFieldValue(value),
        }));
    }

    private toDisplayData(data: any, hasDuplicateFields: boolean): any {
        if (!hasDuplicateFields) {
            return data;
        }

        const fieldLabel = this.strings?.page?.key?.stream?.table?.field || 'Field';
        const valueLabel = this.strings?.page?.key?.stream?.table?.value || 'Value';
        return data.map((item: { field: string; value: any }) => ({
            [fieldLabel]: item.field,
            [valueLabel]: item.value,
        }));
    }

    private entryToExport(entry: { id: string; fields: Array<[string, string]>; data: any; displayData: any; hasDuplicateFields: boolean }): any {
        if (entry.hasDuplicateFields) {
            return {
                id: entry.id,
                fields: entry.data,
            };
        }
        return { id: entry.id, ...entry.data };
    }

    downloadEntry(entry: { id: string; fields: Array<[string, string]> }): void {
        const lines = [entry.id];
        for (const [field, value] of entry.fields) {
            lines.push(field);
            lines.push(value);
        }
        const text = lines.join('\n');
        const blob = new Blob([text], { type: 'text/plain' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `${this.p3xrKey}-${entry.id}.txt`;
        a.click();
        URL.revokeObjectURL(url);
    }

    async copyEntry(entry: { id: string; fields: Array<[string, string]>; data: any; displayData: any; hasDuplicateFields: boolean }): Promise<void> {
        const obj = this.entryToExport(entry);
        await this.copy(JSON.stringify(obj, null, 2));
    }

    async viewEntryJson(entry: { id: string; fields: Array<[string, string]>; data: any; displayData: any; hasDuplicateFields: boolean }, event?: Event): Promise<void> {
        const obj = this.entryToExport(entry);
        await this.showJson(JSON.stringify(obj), event);
    }

    async viewFieldJson(value: string, event?: Event): Promise<void> {
        await this.showJson(value, event);
    }

    copyField(value: any): void { this.copy(value); }

    async addStream(event: Event): Promise<void> {
        try {
            await this.keyNewOrSetDialog.show({ $event: event, type: 'append', model: { type: 'stream', key: this.p3xrKey } });
            this.refreshKey();
        } catch (e) { this.common.generalHandleError(e); }
    }

    async deleteStreamTimestamp(id: string, event: Event): Promise<void> {
        try {
            await this.common.confirm({ message: this.i18n.strings().confirm?.deleteStreamTimestamp });
            await this.socket.request({ action: 'key-stream-delete-timestamp', payload: { key: this.p3xrKey, streamTimestamp: id } });
            this.common.toast(this.i18n.strings().status?.deletedStreamTimestamp || this.i18n.strings().status?.deletedKey || 'Deleted');
            this.refreshKey();
        } catch (e) { if (e) this.common.generalHandleError(e); }
    }
}