64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - ISTI-CNR
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
// Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM.
|
|
|
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, signal } from '@angular/core';
|
|
|
|
export type NodeListFieldItem = {
|
|
index: number;
|
|
summary: string;
|
|
/** Something the item can be opened to read in full, when it has one. */
|
|
definition?: unknown | null;
|
|
};
|
|
|
|
/**
|
|
* A node parameter that is a list: its name and how many items it has, the items themselves on
|
|
* demand. Shared by every node that shows lists - blocks, the execution view, and containers once
|
|
* one has a list parameter - so they all read and behave the same.
|
|
*
|
|
* <p>Starts closed. With no items there is nothing to open, and the add button is the way in; an
|
|
* item added opens the list, so what was just added is there to see rather than only counted.
|
|
*/
|
|
@Component({
|
|
selector: 'app-node-list-field',
|
|
standalone: true,
|
|
templateUrl: './node-list-field.html',
|
|
styleUrl: './node-list-field.css',
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
})
|
|
export class NodeListFieldComponent implements OnChanges {
|
|
@Input({ required: true }) label = '';
|
|
@Input() items: NodeListFieldItem[] = [];
|
|
/** Read-only views - an execution - show the items and offer nothing to change them. */
|
|
@Input() readonly = false;
|
|
|
|
@Output() readonly add = new EventEmitter<void>();
|
|
@Output() readonly edit = new EventEmitter<number>();
|
|
@Output() readonly remove = new EventEmitter<number>();
|
|
@Output() readonly view = new EventEmitter<unknown>();
|
|
|
|
readonly expanded = signal(false);
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
const change = changes['items'];
|
|
if (!change || change.firstChange) return;
|
|
const before = Array.isArray(change.previousValue) ? change.previousValue.length : 0;
|
|
const after = this.items?.length ?? 0;
|
|
if (after > before) this.expanded.set(true);
|
|
if (after === 0) this.expanded.set(false);
|
|
}
|
|
|
|
toggle(event: Event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
if (!this.items.length) return;
|
|
this.expanded.update((open) => !open);
|
|
}
|
|
|
|
emit<T>(emitter: EventEmitter<T>, value: T, event: Event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
emitter.emit(value);
|
|
}
|
|
}
|