Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-ng/display/src/lib/components/data-selector/source-picker.component.ts is written in an unsupported language. File is not indexed.

0001 import { ChangeDetectionStrategy, Component, computed, effect, input, output, signal, untracked, viewChild } from '@angular/core';
0002 import { MatIcon } from '@angular/material/icon';
0003 import { MatIconButton } from '@angular/material/button';
0004 import { MatTooltip } from '@angular/material/tooltip';
0005 import type { DataSource } from '@dexvis/firebird-core';
0006 import { ResourceSelectComponent } from '../resource-select/resource-select.component';
0007 
0008 /**
0009  * One data source field of the Manual tab: pick a known URL or type one
0010  * (Select), or drop/choose a local file (Upload). Used once for geometry and
0011  * once for events. Emits the URL string, the File, or '' when cleared. The
0012  * file is never uploaded anywhere: loaders read it in place.
0013  */
0014 @Component({
0015   selector: 'firebird-source-picker',
0016   changeDetection: ChangeDetectionStrategy.OnPush,
0017   imports: [MatIcon, MatIconButton, MatTooltip, ResourceSelectComponent],
0018   template: `
0019     <div class="picker-header">
0020       <span class="picker-label">{{ label() }}</span>
0021       <div class="mode-switch" role="tablist">
0022         <button type="button" class="mode" [class.active]="mode() === 'select'" (click)="setMode('select')">Select</button>
0023         <button type="button" class="mode" [class.active]="mode() === 'upload'" (click)="setMode('upload')">Upload</button>
0024       </div>
0025     </div>
0026 
0027     @if (mode() === 'select') {
0028       <firebird-resource-select
0029         #urlSelect
0030         [options]="options()"
0031         [selected]="urlValue()"
0032         [label]="placeholder()"
0033         (valueChange)="onUrlTyped($event)">
0034       </firebird-resource-select>
0035     } @else {
0036       @if (fileValue(); as file) {
0037         <div class="file-row">
0038           <mat-icon>insert_drive_file</mat-icon>
0039           <span class="file-name" [title]="file.name">{{ file.name }}</span>
0040           <button mat-icon-button class="clear-btn" matTooltip="Remove this file" (click)="clearFile()">
0041             <mat-icon>close</mat-icon>
0042           </button>
0043         </div>
0044       }
0045       <label class="drop-zone"
0046              [class.drag-over]="dragOver()"
0047              (dragover)="onDragOver($event)"
0048              (dragleave)="onDragLeave($event)"
0049              (drop)="onDrop($event)">
0050         <input type="file" [accept]="accept()" hidden (change)="onFilePicked($event)"/>
0051         <mat-icon>upload_file</mat-icon>
0052         <span class="drop-hint">Drop a file here, or click to choose</span>
0053         @if (hint()) {
0054           <span class="drop-note">{{ hint() }}</span>
0055         }
0056       </label>
0057     }
0058   `,
0059   styles: `
0060     @use './shared';
0061 
0062     :host {
0063       display: flex;
0064       flex-direction: column;
0065       gap: 6px;
0066     }
0067 
0068     .picker-header {
0069       display: flex;
0070       align-items: center;
0071       justify-content: space-between;
0072       gap: 8px;
0073     }
0074 
0075     .picker-label {
0076       @include shared.section-label;
0077     }
0078 
0079     .mode-switch {
0080       display: inline-flex;
0081       border: 1px solid var(--mat-sys-outline, rgba(128, 128, 128, 0.6));
0082       border-radius: 6px;
0083       overflow: hidden;
0084     }
0085 
0086     .mode {
0087       padding: 2px 10px;
0088       border: none;
0089       background: transparent;
0090       color: inherit;
0091       font: inherit;
0092       font-size: 0.75rem;
0093       cursor: pointer;
0094       opacity: 0.75;
0095 
0096       &.active {
0097         opacity: 1;
0098         background: color-mix(in srgb, var(--mat-sys-primary, #4bac84) 25%, transparent);
0099       }
0100     }
0101 
0102     .drop-zone {
0103       display: flex;
0104       flex-direction: column;
0105       align-items: center;
0106       gap: 4px;
0107       padding: 14px 12px;
0108       border: 2px dashed var(--mat-sys-outline, rgba(128, 128, 128, 0.6));
0109       border-radius: 10px;
0110       cursor: pointer;
0111       text-align: center;
0112       transition: border-color 0.15s, background 0.15s;
0113 
0114       &:hover,
0115       &.drag-over {
0116         border-color: var(--mat-sys-primary, #4bac84);
0117         background: color-mix(in srgb, var(--mat-sys-primary, #4bac84) 8%, transparent);
0118       }
0119 
0120       mat-icon {
0121         font-size: 26px;
0122         width: 26px;
0123         height: 26px;
0124         opacity: 0.8;
0125       }
0126     }
0127 
0128     .drop-hint {
0129       font-size: 0.8rem;
0130     }
0131 
0132     .drop-note {
0133       font-size: 0.72rem;
0134       opacity: 0.7;
0135     }
0136 
0137     .file-row {
0138       display: flex;
0139       align-items: center;
0140       gap: 6px;
0141       font-size: 0.82rem;
0142 
0143       mat-icon {
0144         font-size: 18px;
0145         width: 18px;
0146         height: 18px;
0147         opacity: 0.8;
0148       }
0149     }
0150 
0151     .file-name {
0152       flex: 1;
0153       min-width: 0;
0154       overflow: hidden;
0155       text-overflow: ellipsis;
0156       white-space: nowrap;
0157     }
0158 
0159     .clear-btn {
0160       width: 28px;
0161       height: 28px;
0162 
0163       mat-icon {
0164         font-size: 18px;
0165         width: 18px;
0166         height: 18px;
0167       }
0168     }
0169   `,
0170 })
0171 export class SourcePickerComponent {
0172   readonly label = input.required<string>();
0173   /** URLs offered in the Select drop-down; any other URL can be typed. */
0174   readonly options = input<string[]>([]);
0175   readonly placeholder = input('URL');
0176   /** File input accept list, e.g. '.root' or '.root,.json,.zip'. */
0177   readonly accept = input('.root');
0178   /** Small note under the drop zone. */
0179   readonly hint = input('');
0180   /** Current value; the host owns it and passes it back after routing. */
0181   readonly value = input<DataSource | ''>('');
0182   /**
0183    * Bump to make the text field show `value` again when the host kept the
0184    * value but the typed text went elsewhere (a geometry file typed into the
0185    * events field moves to the geometry field; this field must then show what
0186    * it still holds, not the moved text).
0187    */
0188   readonly resyncToken = input(0);
0189   readonly valueChange = output<DataSource | ''>();
0190 
0191   private readonly urlSelect = viewChild<ResourceSelectComponent>('urlSelect');
0192 
0193   readonly mode = signal<'select' | 'upload'>('select');
0194   readonly dragOver = signal(false);
0195 
0196   readonly urlValue = computed(() => (typeof this.value() === 'string' ? this.value() as string : ''));
0197   readonly fileValue = computed(() => (this.value() instanceof File ? this.value() as File : null));
0198 
0199   constructor() {
0200     effect(() => {
0201       this.resyncToken();
0202       const select = this.urlSelect();
0203       if (select) select.selected = untracked(this.urlValue);
0204     });
0205   }
0206 
0207   setMode(mode: 'select' | 'upload'): void {
0208     this.mode.set(mode);
0209   }
0210 
0211   onUrlTyped(url: string): void {
0212     this.valueChange.emit((url ?? '').trim());
0213   }
0214 
0215   onFilePicked(event: Event): void {
0216     const input = event.target as HTMLInputElement;
0217     const file = input.files?.[0];
0218     // Clear the input so picking the same file again still fires a change
0219     input.value = '';
0220     if (file) this.valueChange.emit(file);
0221   }
0222 
0223   clearFile(): void {
0224     this.valueChange.emit('');
0225   }
0226 
0227   onDragOver(event: DragEvent): void {
0228     event.preventDefault();
0229     this.dragOver.set(true);
0230   }
0231 
0232   onDragLeave(event: DragEvent): void {
0233     event.preventDefault();
0234     this.dragOver.set(false);
0235   }
0236 
0237   onDrop(event: DragEvent): void {
0238     event.preventDefault();
0239     this.dragOver.set(false);
0240     const file = event.dataTransfer?.files?.[0];
0241     if (file) this.valueChange.emit(file);
0242   }
0243 }