@mohamedatia/fly-design-system 2.4.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/mohamedatia-fly-design-system.mjs +1446 -122
- package/fesm2022/mohamedatia-fly-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/scss/_business-app-buttons.scss +209 -208
- package/scss/_theme-auto.scss +10 -9
- package/scss/_theme-dark-vars.scss +50 -0
- package/scss/_theme-dark.scss +142 -79
- package/scss/_theme-light.scss +60 -47
- package/scss/_theme-spatial-vars.scss +56 -66
- package/scss/_theme-spatial.scss +23 -22
- package/scss/_tokens.scss +115 -126
- package/scss/apps.scss +886 -0
- package/types/mohamedatia-fly-design-system.d.ts +626 -3
- package/types/mohamedatia-fly-design-system.d.ts.map +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, signal, computed, Injectable, inject, ErrorHandler, PLATFORM_ID, Pipe, DOCUMENT, ElementRef, input, output, HostListener, ViewChild, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, Output, Input, forwardRef, Injector, viewChild, effect, afterNextRender, ViewEncapsulation, model } from '@angular/core';
|
|
2
|
+
import { InjectionToken, signal, computed, Injectable, inject, ErrorHandler, PLATFORM_ID, Pipe, DOCUMENT, ElementRef, input, output, HostListener, ViewChild, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, Output, Input, forwardRef, Injector, viewChild, effect, afterNextRender, ViewEncapsulation, model, Directive } from '@angular/core';
|
|
3
3
|
import * as i1$1 from '@angular/common';
|
|
4
|
-
import { isPlatformBrowser, CommonModule } from '@angular/common';
|
|
5
|
-
import { Router } from '@angular/router';
|
|
4
|
+
import { isPlatformBrowser, CommonModule, DOCUMENT as DOCUMENT$1 } from '@angular/common';
|
|
5
|
+
import { Router, NavigationEnd } from '@angular/router';
|
|
6
6
|
import { of, ReplaySubject, Subject } from 'rxjs';
|
|
7
7
|
import * as i1 from '@angular/forms';
|
|
8
8
|
import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
|
|
@@ -375,6 +375,152 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
375
375
|
type: Injectable
|
|
376
376
|
}] });
|
|
377
377
|
|
|
378
|
+
/**
|
|
379
|
+
* FlyOS standard navigation surface for Business / Supporting App remotes.
|
|
380
|
+
*
|
|
381
|
+
* Problem this solves
|
|
382
|
+
* -------------------
|
|
383
|
+
* A federated remote ships with its own Angular `Routes` table that works
|
|
384
|
+
* perfectly in **standalone** mode (e.g. `http://localhost:7202/signals/:id`).
|
|
385
|
+
* Once the same remote is mounted inside the FlyOS desktop shell via
|
|
386
|
+
* `*ngComponentOutlet`, `inject(Router)` resolves to the **host shell's**
|
|
387
|
+
* Router — which has no knowledge of `/signals/:id`. Calls like
|
|
388
|
+
* `router.navigate(['/signals', id])` then silently no-op and the click
|
|
389
|
+
* "does nothing."
|
|
390
|
+
*
|
|
391
|
+
* The fix
|
|
392
|
+
* -------
|
|
393
|
+
* Inject `FlyRemoteRouter` instead of `Router`. It mirrors `Router.navigate` /
|
|
394
|
+
* `Router.navigateByUrl` and:
|
|
395
|
+
*
|
|
396
|
+
* - **Standalone** (no `WINDOW_DATA`): delegates to the real Angular Router,
|
|
397
|
+
* so the URL bar, browser back button, and `RouterLink` continue to work
|
|
398
|
+
* exactly as before.
|
|
399
|
+
* - **Embedded** (mounted in the shell via `WINDOW_DATA`): writes the target
|
|
400
|
+
* URL to an internal signal (`url`, `segments`) and does **not** touch the
|
|
401
|
+
* host's Router. The remote's root component watches the signal and renders
|
|
402
|
+
* the right view (typically a `@switch` on the first segment, plus an inline
|
|
403
|
+
* overlay for detail pages).
|
|
404
|
+
*
|
|
405
|
+
* Usage in a list page
|
|
406
|
+
* --------------------
|
|
407
|
+
* ```ts
|
|
408
|
+
* private readonly router = inject(FlyRemoteRouter);
|
|
409
|
+
*
|
|
410
|
+
* openDetail(item: Signal): void {
|
|
411
|
+
* this.router.navigate(['/signals', item.id]); // works in both modes
|
|
412
|
+
* }
|
|
413
|
+
* ```
|
|
414
|
+
*
|
|
415
|
+
* Usage in the remote's root component (embedded dispatcher)
|
|
416
|
+
* ----------------------------------------------------------
|
|
417
|
+
* ```ts
|
|
418
|
+
* private readonly router = inject(FlyRemoteRouter);
|
|
419
|
+
*
|
|
420
|
+
* readonly view = computed(() => {
|
|
421
|
+
* if (!this.router.isEmbedded) return null; // standalone: <router-outlet> handles it
|
|
422
|
+
* const [head, id] = this.router.segments();
|
|
423
|
+
* if (head === 'signals' && id) return { kind: 'signal-detail', id };
|
|
424
|
+
* if (head === 'signals') return { kind: 'signals-list' };
|
|
425
|
+
* return { kind: head ?? 'home' };
|
|
426
|
+
* });
|
|
427
|
+
* ```
|
|
428
|
+
*
|
|
429
|
+
* Notes
|
|
430
|
+
* -----
|
|
431
|
+
* - `WINDOW_DATA` is the canonical "I am running inside the shell" marker; the
|
|
432
|
+
* shell provides it per window, and it is `null` everywhere else.
|
|
433
|
+
* - `Router` is injected as `optional`. A remote that does not register a Router
|
|
434
|
+
* (e.g. a tiny chromeless utility app) still gets a working `navigate` that
|
|
435
|
+
* updates the `url` signal in embedded mode.
|
|
436
|
+
*/
|
|
437
|
+
class FlyRemoteRouter {
|
|
438
|
+
windowData = inject(WINDOW_DATA, { optional: true });
|
|
439
|
+
router = inject(Router, { optional: true });
|
|
440
|
+
/** True when this remote is rendered inside the FlyOS desktop shell. */
|
|
441
|
+
isEmbedded = this.windowData != null;
|
|
442
|
+
_url = signal('/', ...(ngDevMode ? [{ debugName: "_url" }] : /* istanbul ignore next */ []));
|
|
443
|
+
/**
|
|
444
|
+
* Current logical URL of the remote — `/signals/abc` etc.
|
|
445
|
+
*
|
|
446
|
+
* Standalone: mirrors `Router.url` and is updated on every `NavigationEnd`.
|
|
447
|
+
* Embedded: updated by `navigate` / `navigateByUrl` only.
|
|
448
|
+
*/
|
|
449
|
+
url = this._url.asReadonly();
|
|
450
|
+
/**
|
|
451
|
+
* `url` parsed into path segments, e.g. `'/signals/abc'` → `['signals', 'abc']`.
|
|
452
|
+
* Query string and hash are stripped. Empty segments removed.
|
|
453
|
+
*/
|
|
454
|
+
segments = computed(() => {
|
|
455
|
+
const u = this._url();
|
|
456
|
+
return u.split('?')[0].split('#')[0].split('/').filter(Boolean);
|
|
457
|
+
}, ...(ngDevMode ? [{ debugName: "segments" }] : /* istanbul ignore next */ []));
|
|
458
|
+
constructor() {
|
|
459
|
+
if (!this.isEmbedded && this.router) {
|
|
460
|
+
// Seed with the current standalone URL so consumers can read `segments()`
|
|
461
|
+
// synchronously during initial render, before any NavigationEnd fires.
|
|
462
|
+
this._url.set(this.router.url);
|
|
463
|
+
this.router.events.subscribe(event => {
|
|
464
|
+
if (event instanceof NavigationEnd) {
|
|
465
|
+
this._url.set(event.urlAfterRedirects);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Navigate to a route, identified by an array of commands as you would pass
|
|
472
|
+
* to Angular's `Router.navigate`. In embedded mode `extras` is ignored — the
|
|
473
|
+
* shell owns history and query-param handling for the window's URL.
|
|
474
|
+
*/
|
|
475
|
+
navigate(commands, extras) {
|
|
476
|
+
if (!this.isEmbedded && this.router) {
|
|
477
|
+
return this.router.navigate(commands, extras);
|
|
478
|
+
}
|
|
479
|
+
this._url.set(this.buildUrl(commands));
|
|
480
|
+
return Promise.resolve(true);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Navigate to a route given as a full URL string. Mirrors `Router.navigateByUrl`.
|
|
484
|
+
*/
|
|
485
|
+
navigateByUrl(url, extras) {
|
|
486
|
+
if (!this.isEmbedded && this.router) {
|
|
487
|
+
return this.router.navigateByUrl(url, extras);
|
|
488
|
+
}
|
|
489
|
+
this._url.set(url.startsWith('/') ? url : '/' + url);
|
|
490
|
+
return Promise.resolve(true);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Convenience: pop the last two segments (the typical "go back from detail
|
|
494
|
+
* to list" gesture). In standalone mode this falls through to `history.back()`
|
|
495
|
+
* so the browser's back stack is respected.
|
|
496
|
+
*/
|
|
497
|
+
back() {
|
|
498
|
+
if (!this.isEmbedded) {
|
|
499
|
+
if (typeof history !== 'undefined')
|
|
500
|
+
history.back();
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const segs = [...this.segments()];
|
|
504
|
+
if (segs.length === 0)
|
|
505
|
+
return;
|
|
506
|
+
segs.pop();
|
|
507
|
+
this._url.set('/' + segs.join('/'));
|
|
508
|
+
}
|
|
509
|
+
buildUrl(commands) {
|
|
510
|
+
const parts = commands
|
|
511
|
+
.map(c => (c == null ? '' : String(c)))
|
|
512
|
+
.map(s => s.replace(/^\/+|\/+$/g, ''))
|
|
513
|
+
.filter(Boolean);
|
|
514
|
+
return '/' + parts.join('/');
|
|
515
|
+
}
|
|
516
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
517
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouter, providedIn: 'root' });
|
|
518
|
+
}
|
|
519
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouter, decorators: [{
|
|
520
|
+
type: Injectable,
|
|
521
|
+
args: [{ providedIn: 'root' }]
|
|
522
|
+
}], ctorParameters: () => [] });
|
|
523
|
+
|
|
378
524
|
/**
|
|
379
525
|
* Translates a key using the shared I18nService.
|
|
380
526
|
*
|
|
@@ -619,11 +765,11 @@ class ContextMenuComponent {
|
|
|
619
765
|
this.previouslyFocused = null;
|
|
620
766
|
}
|
|
621
767
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ContextMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
622
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ContextMenuComponent, isStandalone: true, selector: "fly-context-menu", inputs: { x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: true, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: true, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { action: "action", closed: "closed" }, host: { listeners: { "document:mousedown": "onClickOutside($event)", "document:keydown.escape": "onEscape()", "document:contextmenu": "onContextMenu($event)", "keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["contextMenu"], descendants: true }], ngImport: i0, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72))
|
|
768
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ContextMenuComponent, isStandalone: true, selector: "fly-context-menu", inputs: { x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: true, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: true, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { action: "action", closed: "closed" }, host: { listeners: { "document:mousedown": "onClickOutside($event)", "document:keydown.escape": "onEscape()", "document:contextmenu": "onContextMenu($event)", "keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["contextMenu"], descendants: true }], ngImport: i0, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72));-webkit-backdrop-filter:blur(40px) saturate(180%);backdrop-filter:blur(40px) saturate(180%);border:1px solid var(--glass-border, rgba(255, 255, 255, .15));box-shadow:0 12px 40px #00000059,0 2px 8px #0003,inset 0 .5px #ffffff1a;animation:menuEnter .18s cubic-bezier(.22,1,.36,1) both;transform-origin:top left}@keyframes menuEnter{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.menu-section-label{padding:6px 12px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-color-secondary, rgba(255, 255, 255, .45));pointer-events:none}.menu-item{gap:10px;width:100%;padding:8px 12px;color:var(--text-color, #fff);font-size:var(--link-font-size, 13px);font-weight:var(--btn-symbol-font-weight, 510);justify-content:flex-start}.menu-item i{font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item:hover{background:var(--primary-color, #E8732A);color:#fff}.menu-item:hover i{opacity:1}.menu-item:focus-visible{background:var(--primary-color, #E8732A);outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass-border, rgba(255, 255, 255, .12))}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
623
769
|
}
|
|
624
770
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ContextMenuComponent, decorators: [{
|
|
625
771
|
type: Component,
|
|
626
|
-
args: [{ selector: 'fly-context-menu', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72))
|
|
772
|
+
args: [{ selector: 'fly-context-menu', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72));-webkit-backdrop-filter:blur(40px) saturate(180%);backdrop-filter:blur(40px) saturate(180%);border:1px solid var(--glass-border, rgba(255, 255, 255, .15));box-shadow:0 12px 40px #00000059,0 2px 8px #0003,inset 0 .5px #ffffff1a;animation:menuEnter .18s cubic-bezier(.22,1,.36,1) both;transform-origin:top left}@keyframes menuEnter{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.menu-section-label{padding:6px 12px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-color-secondary, rgba(255, 255, 255, .45));pointer-events:none}.menu-item{gap:10px;width:100%;padding:8px 12px;color:var(--text-color, #fff);font-size:var(--link-font-size, 13px);font-weight:var(--btn-symbol-font-weight, 510);justify-content:flex-start}.menu-item i{font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item:hover{background:var(--primary-color, #E8732A);color:#fff}.menu-item:hover i{opacity:1}.menu-item:focus-visible{background:var(--primary-color, #E8732A);outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass-border, rgba(255, 255, 255, .12))}\n"] }]
|
|
627
773
|
}], propDecorators: { menuEl: [{
|
|
628
774
|
type: ViewChild,
|
|
629
775
|
args: ['contextMenu']
|
|
@@ -676,29 +822,65 @@ class MessageBoxService {
|
|
|
676
822
|
message = signal('', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
677
823
|
icon = signal(MessageBoxIcon.None, ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
678
824
|
buttons = signal([], ...(ngDevMode ? [{ debugName: "buttons" }] : /* istanbul ignore next */ []));
|
|
825
|
+
/**
|
|
826
|
+
* Active "Don't ask again" config — populated only by `showAcknowledged()`.
|
|
827
|
+
* `show()` callers cannot reach this signal because the type system rejects
|
|
828
|
+
* `dontAskAgain` on the base `MessageBoxOptions`.
|
|
829
|
+
*/
|
|
830
|
+
dontAskAgain = signal(undefined, ...(ngDevMode ? [{ debugName: "dontAskAgain" }] : /* istanbul ignore next */ []));
|
|
831
|
+
/**
|
|
832
|
+
* Component-side checkbox state. The component (`MessageBoxComponent`) wires
|
|
833
|
+
* its own signal to this one; the service explicitly re-seeds it on every
|
|
834
|
+
* `_open()` so reusing a constant `options` literal across two opens does
|
|
835
|
+
* NOT leak the previous user's checkbox state through signal `===` dedupe.
|
|
836
|
+
* Exposed for the component to read/write; not for general callers.
|
|
837
|
+
*/
|
|
838
|
+
dontAskAgainChecked = signal(false, ...(ngDevMode ? [{ debugName: "dontAskAgainChecked" }] : /* istanbul ignore next */ []));
|
|
679
839
|
resolver = null;
|
|
680
840
|
show(options) {
|
|
681
|
-
|
|
841
|
+
return this._open(options).then((r) => r.result);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Opens a message box with a "Don't ask again" checkbox and resolves to both
|
|
845
|
+
* the dialog result and the checkbox state. Throws when `options.dontAskAgain`
|
|
846
|
+
* is missing — the checkbox config is mandatory for this entry point. The
|
|
847
|
+
* compile-time signature already guarantees presence; the runtime check is
|
|
848
|
+
* defense-in-depth for callers that bypass the type system via `as never`.
|
|
849
|
+
*/
|
|
850
|
+
showAcknowledged(options) {
|
|
851
|
+
if (!options.dontAskAgain) {
|
|
852
|
+
throw new Error('MessageBoxService.showAcknowledged() requires options.dontAskAgain — use show() for plain dialogs.');
|
|
853
|
+
}
|
|
854
|
+
return this._open(options);
|
|
855
|
+
}
|
|
856
|
+
resolve(result, dontAskAgain = false) {
|
|
857
|
+
this.visible.set(false);
|
|
858
|
+
if (this.resolver) {
|
|
859
|
+
this.resolver({ result, dontAskAgain });
|
|
860
|
+
this.resolver = null;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
_open(options) {
|
|
864
|
+
// Dismiss any pending dialog so its Promise resolves rather than leaking.
|
|
682
865
|
if (this.resolver) {
|
|
683
|
-
this.resolver(DialogResult.None);
|
|
866
|
+
this.resolver({ result: DialogResult.None, dontAskAgain: false });
|
|
684
867
|
this.resolver = null;
|
|
685
868
|
}
|
|
869
|
+
const daa = 'dontAskAgain' in options ? options.dontAskAgain : undefined;
|
|
686
870
|
this.title.set(options.title);
|
|
687
871
|
this.message.set(options.message);
|
|
688
872
|
this.icon.set(options.icon ?? MessageBoxIcon.None);
|
|
689
873
|
this.buttons.set(this.resolveButtons(options.buttons ?? MessageBoxButtons.OK));
|
|
874
|
+
this.dontAskAgain.set(daa);
|
|
875
|
+
// Explicit re-seed: signal `set` dedupes by `===`, so an `effect()` on
|
|
876
|
+
// `dontAskAgain` would not re-fire when a caller passes the SAME options
|
|
877
|
+
// literal twice. Set the checkbox state directly each open.
|
|
878
|
+
this.dontAskAgainChecked.set(daa?.defaultChecked ?? false);
|
|
690
879
|
this.visible.set(true);
|
|
691
880
|
return new Promise((resolve) => {
|
|
692
881
|
this.resolver = resolve;
|
|
693
882
|
});
|
|
694
883
|
}
|
|
695
|
-
resolve(result) {
|
|
696
|
-
this.visible.set(false);
|
|
697
|
-
if (this.resolver) {
|
|
698
|
-
this.resolver(result);
|
|
699
|
-
this.resolver = null;
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
884
|
resolveButtons(buttons) {
|
|
703
885
|
const t = (key) => this.i18n.t(key);
|
|
704
886
|
switch (buttons) {
|
|
@@ -746,6 +928,19 @@ class MessageBoxComponent {
|
|
|
746
928
|
elRef = inject(ElementRef);
|
|
747
929
|
MessageBoxIcon = MessageBoxIcon;
|
|
748
930
|
DialogResult = DialogResult;
|
|
931
|
+
/** Mirror of the service's `dontAskAgain` config so the template can read it directly. */
|
|
932
|
+
dontAskAgainConfig = computed(() => this.service.dontAskAgain(), ...(ngDevMode ? [{ debugName: "dontAskAgainConfig" }] : /* istanbul ignore next */ []));
|
|
933
|
+
/**
|
|
934
|
+
* User-driven checkbox state, owned by the service and re-seeded on every
|
|
935
|
+
* `_open()` so reusing a constant options literal cannot leak prior state.
|
|
936
|
+
*/
|
|
937
|
+
dontAskAgainChecked = this.service.dontAskAgainChecked;
|
|
938
|
+
/**
|
|
939
|
+
* Concatenated id list for the dialog's `aria-describedby`. Always includes
|
|
940
|
+
* the message; appends the checkbox label id when the checkbox is rendered
|
|
941
|
+
* so screen readers announce the checkbox as part of the description.
|
|
942
|
+
*/
|
|
943
|
+
ariaDescribedBy = computed(() => this.dontAskAgainConfig() ? 'mb-message mb-dont-ask' : 'mb-message', ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
|
|
749
944
|
previouslyFocused = null;
|
|
750
945
|
ngAfterViewInit() {
|
|
751
946
|
// Store focus origin so we can restore it on close
|
|
@@ -754,18 +949,22 @@ class MessageBoxComponent {
|
|
|
754
949
|
}
|
|
755
950
|
onEscape() {
|
|
756
951
|
if (this.service.visible()) {
|
|
757
|
-
this.service.resolve(DialogResult.Cancel);
|
|
952
|
+
this.service.resolve(DialogResult.Cancel, this.dontAskAgainChecked());
|
|
758
953
|
this.restoreFocus();
|
|
759
954
|
}
|
|
760
955
|
}
|
|
761
956
|
onBackdropClick() {
|
|
762
|
-
this.service.resolve(DialogResult.Cancel);
|
|
957
|
+
this.service.resolve(DialogResult.Cancel, this.dontAskAgainChecked());
|
|
763
958
|
this.restoreFocus();
|
|
764
959
|
}
|
|
765
960
|
onButtonClick(result) {
|
|
766
|
-
this.service.resolve(result);
|
|
961
|
+
this.service.resolve(result, this.dontAskAgainChecked());
|
|
767
962
|
this.restoreFocus();
|
|
768
963
|
}
|
|
964
|
+
onDontAskAgainToggle(ev) {
|
|
965
|
+
const target = ev.target;
|
|
966
|
+
this.dontAskAgainChecked.set(!!target?.checked);
|
|
967
|
+
}
|
|
769
968
|
iconClass() {
|
|
770
969
|
switch (this.service.icon()) {
|
|
771
970
|
case MessageBoxIcon.Information: return 'pi pi-info-circle';
|
|
@@ -788,11 +987,11 @@ class MessageBoxComponent {
|
|
|
788
987
|
this.previouslyFocused = null;
|
|
789
988
|
}
|
|
790
989
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
791
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageBoxComponent, isStandalone: true, selector: "fly-message-box", host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (service.visible()) {\n <div\n class=\"mb-backdrop\"\n (click)=\"onBackdropClick()\"\n role=\"presentation\">\n <div\n class=\"mb-dialog\"\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\n (click)=\"$event.stopPropagation()\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"mb-title\"\n aria-describedby=\"
|
|
990
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageBoxComponent, isStandalone: true, selector: "fly-message-box", host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (service.visible()) {\r\n <div\r\n class=\"mb-backdrop\"\r\n (click)=\"onBackdropClick()\"\r\n role=\"presentation\">\r\n <div\r\n class=\"mb-dialog\"\r\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\r\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\r\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\r\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\r\n (click)=\"$event.stopPropagation()\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby=\"mb-title\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\">\r\n\r\n @if (iconClass()) {\r\n <div class=\"mb-icon-wrap\">\r\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\r\n </div>\r\n }\r\n\r\n <div class=\"mb-title\" id=\"mb-title\">{{ service.title() }}</div>\r\n <div class=\"mb-message\" id=\"mb-message\">{{ service.message() }}</div>\r\n\r\n @if (dontAskAgainConfig(); as daa) {\r\n <label class=\"mb-dont-ask-again\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dontAskAgainChecked()\"\r\n (change)=\"onDontAskAgainToggle($event)\" />\r\n <span id=\"mb-dont-ask\">{{ daa.labelKey | translate }}</span>\r\n </label>\r\n }\r\n\r\n <div class=\"mb-actions\">\r\n @for (btn of service.buttons(); track btn.result) {\r\n <button\r\n type=\"button\"\r\n class=\"vos-btn sm mb-btn\"\r\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\r\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\r\n (click)=\"onButtonClick(btn.result)\">\r\n {{ btn.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;background:#00000073;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-dont-ask-again{display:flex;align-items:center;gap:8px;margin-block-start:12px;font-size:12px;color:var(--text-color-secondary);cursor:pointer;align-self:flex-start;-webkit-user-select:none;user-select:none}.mb-dont-ask-again input[type=checkbox]{margin:0;cursor:pointer}.mb-dont-ask-again span{line-height:1.3}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color, #E8732A);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
792
991
|
}
|
|
793
992
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageBoxComponent, decorators: [{
|
|
794
993
|
type: Component,
|
|
795
|
-
args: [{ selector: 'fly-message-box', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (service.visible()) {\n <div\n class=\"mb-backdrop\"\n (click)=\"onBackdropClick()\"\n role=\"presentation\">\n <div\n class=\"mb-dialog\"\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\n (click)=\"$event.stopPropagation()\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"mb-title\"\n aria-describedby=\"
|
|
994
|
+
args: [{ selector: 'fly-message-box', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (service.visible()) {\r\n <div\r\n class=\"mb-backdrop\"\r\n (click)=\"onBackdropClick()\"\r\n role=\"presentation\">\r\n <div\r\n class=\"mb-dialog\"\r\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\r\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\r\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\r\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\r\n (click)=\"$event.stopPropagation()\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby=\"mb-title\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\">\r\n\r\n @if (iconClass()) {\r\n <div class=\"mb-icon-wrap\">\r\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\r\n </div>\r\n }\r\n\r\n <div class=\"mb-title\" id=\"mb-title\">{{ service.title() }}</div>\r\n <div class=\"mb-message\" id=\"mb-message\">{{ service.message() }}</div>\r\n\r\n @if (dontAskAgainConfig(); as daa) {\r\n <label class=\"mb-dont-ask-again\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dontAskAgainChecked()\"\r\n (change)=\"onDontAskAgainToggle($event)\" />\r\n <span id=\"mb-dont-ask\">{{ daa.labelKey | translate }}</span>\r\n </label>\r\n }\r\n\r\n <div class=\"mb-actions\">\r\n @for (btn of service.buttons(); track btn.result) {\r\n <button\r\n type=\"button\"\r\n class=\"vos-btn sm mb-btn\"\r\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\r\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\r\n (click)=\"onButtonClick(btn.result)\">\r\n {{ btn.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;background:#00000073;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-dont-ask-again{display:flex;align-items:center;gap:8px;margin-block-start:12px;font-size:12px;color:var(--text-color-secondary);cursor:pointer;align-self:flex-start;-webkit-user-select:none;user-select:none}.mb-dont-ask-again input[type=checkbox]{margin:0;cursor:pointer}.mb-dont-ask-again span{line-height:1.3}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color, #E8732A);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}\n"] }]
|
|
796
995
|
}], propDecorators: { onEscape: [{
|
|
797
996
|
type: HostListener,
|
|
798
997
|
args: ['document:keydown.escape']
|
|
@@ -1832,11 +2031,11 @@ class FlyBlockUiComponent {
|
|
|
1832
2031
|
return k && k.length > 0 ? k : 'common.loading';
|
|
1833
2032
|
}, ...(ngDevMode ? [{ debugName: "resolvedMessageKey" }] : /* istanbul ignore next */ []));
|
|
1834
2033
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyBlockUiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1835
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyBlockUiComponent, isStandalone: true, selector: "fly-block-ui", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: true, transformFunction: null }, messageKey: { classPropertyName: "messageKey", publicName: "messageKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent)
|
|
2034
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyBlockUiComponent, isStandalone: true, selector: "fly-block-ui", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: true, transformFunction: null }, messageKey: { classPropertyName: "messageKey", publicName: "messageKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);-webkit-backdrop-filter:blur(6px) saturate(120%);backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1836
2035
|
}
|
|
1837
2036
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyBlockUiComponent, decorators: [{
|
|
1838
2037
|
type: Component,
|
|
1839
|
-
args: [{ selector: 'fly-block-ui', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent)
|
|
2038
|
+
args: [{ selector: 'fly-block-ui', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);-webkit-backdrop-filter:blur(6px) saturate(120%);backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"] }]
|
|
1840
2039
|
}], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: true }] }], messageKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageKey", required: false }] }] } });
|
|
1841
2040
|
|
|
1842
2041
|
/**
|
|
@@ -2017,110 +2216,110 @@ class FlyImageUploadComponent {
|
|
|
2017
2216
|
});
|
|
2018
2217
|
}
|
|
2019
2218
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyImageUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2020
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyImageUploadComponent, isStandalone: true, selector: "fly-image-upload", inputs: { aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, maxSizeBytes: { classPropertyName: "maxSizeBytes", publicName: "maxSizeBytes", isSignal: true, isRequired: false, transformFunction: null }, currentImageId: { classPropertyName: "currentImageId", publicName: "currentImageId", isSignal: true, isRequired: false, transformFunction: null }, sourceApp: { classPropertyName: "sourceApp", publicName: "sourceApp", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityType: { classPropertyName: "sourceEntityType", publicName: "sourceEntityType", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityId: { classPropertyName: "sourceEntityId", publicName: "sourceEntityId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploaded: "uploaded", removed: "removed" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }, { propertyName: "cropImage", first: true, predicate: ["cropImage"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
2021
|
-
<div class="fly-image-upload">
|
|
2022
|
-
@if (previewUrl()) {
|
|
2023
|
-
<div class="fly-image-upload__preview">
|
|
2024
|
-
<img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
|
|
2025
|
-
<div class="fly-image-upload__overlay">
|
|
2026
|
-
<button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
|
|
2027
|
-
<span class="pi pi-pencil" aria-hidden="true"></span>
|
|
2028
|
-
</button>
|
|
2029
|
-
<button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
|
|
2030
|
-
<span class="pi pi-trash" aria-hidden="true"></span>
|
|
2031
|
-
</button>
|
|
2032
|
-
</div>
|
|
2033
|
-
</div>
|
|
2034
|
-
} @else {
|
|
2035
|
-
<button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
|
|
2036
|
-
@if (uploading()) {
|
|
2037
|
-
<span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
|
|
2038
|
-
<span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
|
|
2039
|
-
} @else {
|
|
2040
|
-
<span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
|
|
2041
|
-
<span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
|
|
2042
|
-
<span class="fly-image-upload__hint">{{ sizeHint() }}</span>
|
|
2043
|
-
}
|
|
2044
|
-
</button>
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
@if (error()) {
|
|
2048
|
-
<div class="fly-image-upload__error">{{ error() }}</div>
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
<input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
|
|
2052
|
-
|
|
2053
|
-
@if (showCropper()) {
|
|
2054
|
-
<div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
|
|
2055
|
-
<div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
|
|
2056
|
-
<div class="fly-image-upload__crop-header">
|
|
2057
|
-
<h3>{{ 'files.imageUpload.crop' | translate }}</h3>
|
|
2058
|
-
</div>
|
|
2059
|
-
<div class="fly-image-upload__crop-body">
|
|
2060
|
-
<img #cropImage [src]="rawImageUrl()" alt="" />
|
|
2061
|
-
</div>
|
|
2062
|
-
<div class="fly-image-upload__crop-footer">
|
|
2063
|
-
<button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
|
|
2064
|
-
<button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
|
|
2065
|
-
</div>
|
|
2066
|
-
</div>
|
|
2067
|
-
</div>
|
|
2068
|
-
}
|
|
2069
|
-
</div>
|
|
2219
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyImageUploadComponent, isStandalone: true, selector: "fly-image-upload", inputs: { aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, maxSizeBytes: { classPropertyName: "maxSizeBytes", publicName: "maxSizeBytes", isSignal: true, isRequired: false, transformFunction: null }, currentImageId: { classPropertyName: "currentImageId", publicName: "currentImageId", isSignal: true, isRequired: false, transformFunction: null }, sourceApp: { classPropertyName: "sourceApp", publicName: "sourceApp", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityType: { classPropertyName: "sourceEntityType", publicName: "sourceEntityType", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityId: { classPropertyName: "sourceEntityId", publicName: "sourceEntityId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploaded: "uploaded", removed: "removed" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }, { propertyName: "cropImage", first: true, predicate: ["cropImage"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
2220
|
+
<div class="fly-image-upload">
|
|
2221
|
+
@if (previewUrl()) {
|
|
2222
|
+
<div class="fly-image-upload__preview">
|
|
2223
|
+
<img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
|
|
2224
|
+
<div class="fly-image-upload__overlay">
|
|
2225
|
+
<button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
|
|
2226
|
+
<span class="pi pi-pencil" aria-hidden="true"></span>
|
|
2227
|
+
</button>
|
|
2228
|
+
<button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
|
|
2229
|
+
<span class="pi pi-trash" aria-hidden="true"></span>
|
|
2230
|
+
</button>
|
|
2231
|
+
</div>
|
|
2232
|
+
</div>
|
|
2233
|
+
} @else {
|
|
2234
|
+
<button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
|
|
2235
|
+
@if (uploading()) {
|
|
2236
|
+
<span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
|
|
2237
|
+
<span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
|
|
2238
|
+
} @else {
|
|
2239
|
+
<span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
|
|
2240
|
+
<span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
|
|
2241
|
+
<span class="fly-image-upload__hint">{{ sizeHint() }}</span>
|
|
2242
|
+
}
|
|
2243
|
+
</button>
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
@if (error()) {
|
|
2247
|
+
<div class="fly-image-upload__error">{{ error() }}</div>
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
<input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
|
|
2251
|
+
|
|
2252
|
+
@if (showCropper()) {
|
|
2253
|
+
<div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
|
|
2254
|
+
<div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
|
|
2255
|
+
<div class="fly-image-upload__crop-header">
|
|
2256
|
+
<h3>{{ 'files.imageUpload.crop' | translate }}</h3>
|
|
2257
|
+
</div>
|
|
2258
|
+
<div class="fly-image-upload__crop-body">
|
|
2259
|
+
<img #cropImage [src]="rawImageUrl()" alt="" />
|
|
2260
|
+
</div>
|
|
2261
|
+
<div class="fly-image-upload__crop-footer">
|
|
2262
|
+
<button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
|
|
2263
|
+
<button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
|
|
2264
|
+
</div>
|
|
2265
|
+
</div>
|
|
2266
|
+
</div>
|
|
2267
|
+
}
|
|
2268
|
+
</div>
|
|
2070
2269
|
`, isInline: true, styles: [".cropper-container{-webkit-touch-callout:none;direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;user-select:none}.cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{inset:0;position:absolute}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:\" \";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}\n", ".fly-image-upload{display:flex;flex-direction:column;gap:6px}.fly-image-upload__hidden{display:none}.fly-image-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:24px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:12px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s;min-height:120px}.fly-image-upload__dropzone:hover{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__icon{font-size:28px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-image-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__preview{position:relative;border-radius:12px;overflow:hidden}.fly-image-upload__img{display:block;width:100%;max-height:280px;object-fit:cover;border-radius:12px}.fly-image-upload__overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:12px;background:#00000059;opacity:0;transition:opacity .15s}.fly-image-upload__preview:hover .fly-image-upload__overlay{opacity:1}.fly-image-upload__action-btn{width:36px;height:36px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;background:#ffffffe6;color:var(--label-primary, #1f2937);font-size:14px;transition:background .15s}.fly-image-upload__action-btn:hover{background:#fff}.fly-image-upload__action-btn--danger:hover{background:#fee2e2;color:#dc2626}.fly-image-upload__error{font-size:12px;color:var(--system-red, #ef4444)}.fly-image-upload__crop-backdrop{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:#0009;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.fly-image-upload__crop-modal{background:var(--bg-primary, #fff);color:var(--label-primary, #1f2937);border-radius:16px;overflow:hidden;max-width:700px;width:90vw;box-shadow:0 20px 60px #0006;border:1px solid var(--separator-primary, rgba(0,0,0,.1))}.fly-image-upload__crop-header{padding:16px 20px;border-bottom:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}.fly-image-upload__crop-header h3{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1f2937)}.fly-image-upload__crop-body{max-height:60vh;overflow:hidden;background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__crop-body img{display:block;max-width:100%}.fly-image-upload__crop-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}html.spatial-theme .fly-image-upload__crop-modal{background:#ffffffa6;backdrop-filter:blur(40px) saturate(1.8);-webkit-backdrop-filter:blur(40px) saturate(1.8);border:1px solid rgba(255,255,255,.4)}html.spatial-theme .fly-image-upload__crop-header,html.spatial-theme .fly-image-upload__crop-footer{background:transparent}html.spatial-theme .fly-image-upload__crop-body{background:#0000000d}html.dark-theme .fly-image-upload__crop-modal{background:var(--bg-primary, #1c1c1e);border:1px solid var(--separator-primary, rgba(255,255,255,.1))}html.dark-theme .fly-image-upload__crop-body{background:var(--fill-tertiary, #2c2c2e)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2071
2270
|
}
|
|
2072
2271
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyImageUploadComponent, decorators: [{
|
|
2073
2272
|
type: Component,
|
|
2074
|
-
args: [{ selector: 'fly-image-upload', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
2075
|
-
<div class="fly-image-upload">
|
|
2076
|
-
@if (previewUrl()) {
|
|
2077
|
-
<div class="fly-image-upload__preview">
|
|
2078
|
-
<img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
|
|
2079
|
-
<div class="fly-image-upload__overlay">
|
|
2080
|
-
<button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
|
|
2081
|
-
<span class="pi pi-pencil" aria-hidden="true"></span>
|
|
2082
|
-
</button>
|
|
2083
|
-
<button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
|
|
2084
|
-
<span class="pi pi-trash" aria-hidden="true"></span>
|
|
2085
|
-
</button>
|
|
2086
|
-
</div>
|
|
2087
|
-
</div>
|
|
2088
|
-
} @else {
|
|
2089
|
-
<button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
|
|
2090
|
-
@if (uploading()) {
|
|
2091
|
-
<span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
|
|
2092
|
-
<span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
|
|
2093
|
-
} @else {
|
|
2094
|
-
<span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
|
|
2095
|
-
<span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
|
|
2096
|
-
<span class="fly-image-upload__hint">{{ sizeHint() }}</span>
|
|
2097
|
-
}
|
|
2098
|
-
</button>
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
@if (error()) {
|
|
2102
|
-
<div class="fly-image-upload__error">{{ error() }}</div>
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
<input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
|
|
2106
|
-
|
|
2107
|
-
@if (showCropper()) {
|
|
2108
|
-
<div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
|
|
2109
|
-
<div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
|
|
2110
|
-
<div class="fly-image-upload__crop-header">
|
|
2111
|
-
<h3>{{ 'files.imageUpload.crop' | translate }}</h3>
|
|
2112
|
-
</div>
|
|
2113
|
-
<div class="fly-image-upload__crop-body">
|
|
2114
|
-
<img #cropImage [src]="rawImageUrl()" alt="" />
|
|
2115
|
-
</div>
|
|
2116
|
-
<div class="fly-image-upload__crop-footer">
|
|
2117
|
-
<button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
|
|
2118
|
-
<button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
|
|
2119
|
-
</div>
|
|
2120
|
-
</div>
|
|
2121
|
-
</div>
|
|
2122
|
-
}
|
|
2123
|
-
</div>
|
|
2273
|
+
args: [{ selector: 'fly-image-upload', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
2274
|
+
<div class="fly-image-upload">
|
|
2275
|
+
@if (previewUrl()) {
|
|
2276
|
+
<div class="fly-image-upload__preview">
|
|
2277
|
+
<img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
|
|
2278
|
+
<div class="fly-image-upload__overlay">
|
|
2279
|
+
<button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
|
|
2280
|
+
<span class="pi pi-pencil" aria-hidden="true"></span>
|
|
2281
|
+
</button>
|
|
2282
|
+
<button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
|
|
2283
|
+
<span class="pi pi-trash" aria-hidden="true"></span>
|
|
2284
|
+
</button>
|
|
2285
|
+
</div>
|
|
2286
|
+
</div>
|
|
2287
|
+
} @else {
|
|
2288
|
+
<button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
|
|
2289
|
+
@if (uploading()) {
|
|
2290
|
+
<span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
|
|
2291
|
+
<span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
|
|
2292
|
+
} @else {
|
|
2293
|
+
<span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
|
|
2294
|
+
<span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
|
|
2295
|
+
<span class="fly-image-upload__hint">{{ sizeHint() }}</span>
|
|
2296
|
+
}
|
|
2297
|
+
</button>
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
@if (error()) {
|
|
2301
|
+
<div class="fly-image-upload__error">{{ error() }}</div>
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
<input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
|
|
2305
|
+
|
|
2306
|
+
@if (showCropper()) {
|
|
2307
|
+
<div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
|
|
2308
|
+
<div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
|
|
2309
|
+
<div class="fly-image-upload__crop-header">
|
|
2310
|
+
<h3>{{ 'files.imageUpload.crop' | translate }}</h3>
|
|
2311
|
+
</div>
|
|
2312
|
+
<div class="fly-image-upload__crop-body">
|
|
2313
|
+
<img #cropImage [src]="rawImageUrl()" alt="" />
|
|
2314
|
+
</div>
|
|
2315
|
+
<div class="fly-image-upload__crop-footer">
|
|
2316
|
+
<button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
|
|
2317
|
+
<button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
|
|
2318
|
+
</div>
|
|
2319
|
+
</div>
|
|
2320
|
+
</div>
|
|
2321
|
+
}
|
|
2322
|
+
</div>
|
|
2124
2323
|
`, styles: [".cropper-container{-webkit-touch-callout:none;direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;user-select:none}.cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{inset:0;position:absolute}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:\" \";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}\n", ".fly-image-upload{display:flex;flex-direction:column;gap:6px}.fly-image-upload__hidden{display:none}.fly-image-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:24px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:12px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s;min-height:120px}.fly-image-upload__dropzone:hover{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__icon{font-size:28px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-image-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__preview{position:relative;border-radius:12px;overflow:hidden}.fly-image-upload__img{display:block;width:100%;max-height:280px;object-fit:cover;border-radius:12px}.fly-image-upload__overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:12px;background:#00000059;opacity:0;transition:opacity .15s}.fly-image-upload__preview:hover .fly-image-upload__overlay{opacity:1}.fly-image-upload__action-btn{width:36px;height:36px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;background:#ffffffe6;color:var(--label-primary, #1f2937);font-size:14px;transition:background .15s}.fly-image-upload__action-btn:hover{background:#fff}.fly-image-upload__action-btn--danger:hover{background:#fee2e2;color:#dc2626}.fly-image-upload__error{font-size:12px;color:var(--system-red, #ef4444)}.fly-image-upload__crop-backdrop{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:#0009;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.fly-image-upload__crop-modal{background:var(--bg-primary, #fff);color:var(--label-primary, #1f2937);border-radius:16px;overflow:hidden;max-width:700px;width:90vw;box-shadow:0 20px 60px #0006;border:1px solid var(--separator-primary, rgba(0,0,0,.1))}.fly-image-upload__crop-header{padding:16px 20px;border-bottom:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}.fly-image-upload__crop-header h3{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1f2937)}.fly-image-upload__crop-body{max-height:60vh;overflow:hidden;background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__crop-body img{display:block;max-width:100%}.fly-image-upload__crop-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}html.spatial-theme .fly-image-upload__crop-modal{background:#ffffffa6;backdrop-filter:blur(40px) saturate(1.8);-webkit-backdrop-filter:blur(40px) saturate(1.8);border:1px solid rgba(255,255,255,.4)}html.spatial-theme .fly-image-upload__crop-header,html.spatial-theme .fly-image-upload__crop-footer{background:transparent}html.spatial-theme .fly-image-upload__crop-body{background:#0000000d}html.dark-theme .fly-image-upload__crop-modal{background:var(--bg-primary, #1c1c1e);border:1px solid var(--separator-primary, rgba(255,255,255,.1))}html.dark-theme .fly-image-upload__crop-body{background:var(--fill-tertiary, #2c2c2e)}\n"] }]
|
|
2125
2324
|
}], ctorParameters: () => [], propDecorators: { aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], maxSizeBytes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSizeBytes", required: false }] }], currentImageId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentImageId", required: false }] }], sourceApp: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceApp", required: false }] }], sourceEntityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityType", required: false }] }], sourceEntityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityId", required: false }] }], uploaded: [{ type: i0.Output, args: ["uploaded"] }], removed: [{ type: i0.Output, args: ["removed"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], cropImage: [{ type: i0.ViewChild, args: ['cropImage', { isSignal: true }] }] } });
|
|
2126
2325
|
|
|
@@ -2456,6 +2655,1113 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
2456
2655
|
`, styles: [".fly-file-upload{display:flex;flex-direction:column;gap:8px}.fly-file-upload__hidden{display:none}.fly-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:20px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:10px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s}.fly-file-upload__dropzone:hover,.fly-file-upload__dropzone--drag{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-file-upload__icon{font-size:24px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-file-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__error{font-size:12px;color:var(--system-red, #ef4444);padding:4px 0}.fly-file-upload__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}.fly-file-upload__item{display:flex;align-items:center;gap:10px;padding:8px 12px;border:1px solid var(--separator-primary, #e5e7eb);border-radius:8px;background:var(--fill-quaternary, #f9fafb);font-size:13px}.fly-file-upload__file-icon{font-size:18px;color:var(--label-tertiary, #9ca3af);flex-shrink:0}.fly-file-upload__file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.fly-file-upload__file-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--label-primary, #1f2937)}.fly-file-upload__file-size{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__progress-track{width:60px;height:4px;border-radius:2px;background:var(--fill-tertiary, #e5e7eb);overflow:hidden}.fly-file-upload__progress-fill{height:100%;background:var(--accent-primary, #0071e3);border-radius:2px;transition:width .2s}.fly-file-upload__file-ok{color:var(--system-green, #22c55e);font-size:14px}.fly-file-upload__file-error{color:var(--system-red, #ef4444);font-size:14px}.fly-file-upload__remove-btn{background:none;border:none;cursor:pointer;color:var(--label-tertiary, #9ca3af);width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:color .15s,background .15s;flex-shrink:0}.fly-file-upload__remove-btn:hover{color:var(--system-red, #ef4444);background:var(--fill-tertiary, #f3f4f6)}\n"] }]
|
|
2457
2656
|
}], ctorParameters: () => [], propDecorators: { maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], maxFileSizeBytes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSizeBytes", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], sourceApp: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceApp", required: false }] }], sourceEntityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityType", required: false }] }], sourceEntityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityId", required: false }] }], existingFileIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFileIds", required: false }] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }, { type: i0.Output, args: ["filesChange"] }], filesChanged: [{ type: i0.Output, args: ["filesChanged"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
|
|
2458
2657
|
|
|
2658
|
+
/**
|
|
2659
|
+
* Singleton registry of slash commands offered by the agent input palette.
|
|
2660
|
+
*
|
|
2661
|
+
* Lives in the DS so it crosses the federation boundary as a single instance via
|
|
2662
|
+
* `sharedMappings: ['@mohamedatia/fly-design-system']`. Federated remotes register
|
|
2663
|
+
* their commands at boot (and dispose on window close) without forking the shell.
|
|
2664
|
+
*
|
|
2665
|
+
* Storage is a signal store. `register` is O(1) for the unique-id case and O(n) when
|
|
2666
|
+
* replacing an existing id (filter then push). The append-then-replace strategy is
|
|
2667
|
+
* deliberate: registrations are rare (each one corresponds to a remote's app-init
|
|
2668
|
+
* effect), and the tradeoff buys us a stable id-collision contract — the *latest*
|
|
2669
|
+
* registration wins, and the previous handle's `dispose()` becomes a no-op rather
|
|
2670
|
+
* than removing the new entry.
|
|
2671
|
+
*/
|
|
2672
|
+
class AgentCommandRegistry {
|
|
2673
|
+
_commands = signal([], ...(ngDevMode ? [{ debugName: "_commands" }] : /* istanbul ignore next */ []));
|
|
2674
|
+
/** All currently-registered commands, in insertion order. */
|
|
2675
|
+
all = this._commands.asReadonly();
|
|
2676
|
+
/**
|
|
2677
|
+
* Returns a signal of commands whose scope is `'global'` OR whose `scope.appId` is in
|
|
2678
|
+
* the live app set. The signal recomputes when either the registry or `liveAppIds`
|
|
2679
|
+
* changes — pass a `Signal<ReadonlySet<string>>` from the host's app-registry service
|
|
2680
|
+
* for reactive filtering.
|
|
2681
|
+
*/
|
|
2682
|
+
visible(liveAppIds) {
|
|
2683
|
+
const liveSignal = isSignal(liveAppIds) ? liveAppIds : signal(liveAppIds).asReadonly();
|
|
2684
|
+
return computed(() => {
|
|
2685
|
+
const live = liveSignal();
|
|
2686
|
+
return this._commands().filter((cmd) => {
|
|
2687
|
+
if (cmd.scope === 'global')
|
|
2688
|
+
return true;
|
|
2689
|
+
return live.has(cmd.scope.appId);
|
|
2690
|
+
});
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
/**
|
|
2694
|
+
* Register a single command. Returns a handle whose `dispose()` removes the row by
|
|
2695
|
+
* id. If the same id is later re-registered, the original handle's `dispose()`
|
|
2696
|
+
* becomes a no-op (the newer registration owns the row). Idempotent disposal.
|
|
2697
|
+
*/
|
|
2698
|
+
register(cmd) {
|
|
2699
|
+
const generation = ++this._generation;
|
|
2700
|
+
this._commands.update((rows) => [...rows.filter((r) => r.id !== cmd.id), cmd]);
|
|
2701
|
+
this._owners.set(cmd.id, generation);
|
|
2702
|
+
return {
|
|
2703
|
+
dispose: () => {
|
|
2704
|
+
if (this._owners.get(cmd.id) === generation) {
|
|
2705
|
+
this._owners.delete(cmd.id);
|
|
2706
|
+
this._commands.update((rows) => rows.filter((r) => r.id !== cmd.id));
|
|
2707
|
+
}
|
|
2708
|
+
},
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Bulk register. Rolls back on duplicate id within the input batch (throws before any
|
|
2713
|
+
* row lands). Cross-batch duplicates against existing rows follow the standard
|
|
2714
|
+
* "latest wins" rule and do NOT trigger rollback.
|
|
2715
|
+
*
|
|
2716
|
+
* Returns a handle whose `dispose()` tears down every row registered by this call.
|
|
2717
|
+
*/
|
|
2718
|
+
registerAll(cmds) {
|
|
2719
|
+
const seen = new Set();
|
|
2720
|
+
for (const c of cmds) {
|
|
2721
|
+
if (seen.has(c.id)) {
|
|
2722
|
+
throw new Error(`AgentCommandRegistry.registerAll: duplicate id "${c.id}" in batch`);
|
|
2723
|
+
}
|
|
2724
|
+
seen.add(c.id);
|
|
2725
|
+
}
|
|
2726
|
+
const handles = cmds.map((c) => this.register(c));
|
|
2727
|
+
let disposed = false;
|
|
2728
|
+
return {
|
|
2729
|
+
dispose: () => {
|
|
2730
|
+
if (disposed)
|
|
2731
|
+
return;
|
|
2732
|
+
disposed = true;
|
|
2733
|
+
for (const h of handles)
|
|
2734
|
+
h.dispose();
|
|
2735
|
+
},
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2738
|
+
/** Tear down by id. Idempotent. */
|
|
2739
|
+
unregister(id) {
|
|
2740
|
+
if (this._owners.delete(id)) {
|
|
2741
|
+
this._commands.update((rows) => rows.filter((r) => r.id !== id));
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
/** Monotonic counter; identifies which registration call currently owns each id. */
|
|
2745
|
+
_generation = 0;
|
|
2746
|
+
/** id → generation. Used so a stale handle's `dispose()` is a no-op after replacement. */
|
|
2747
|
+
_owners = new Map();
|
|
2748
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentCommandRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2749
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentCommandRegistry, providedIn: 'root' });
|
|
2750
|
+
}
|
|
2751
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentCommandRegistry, decorators: [{
|
|
2752
|
+
type: Injectable,
|
|
2753
|
+
args: [{ providedIn: 'root' }]
|
|
2754
|
+
}] });
|
|
2755
|
+
/** `isSignal` shim — narrows to either `Signal<T>` or a plain value. */
|
|
2756
|
+
function isSignal(v) {
|
|
2757
|
+
return typeof v === 'function';
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
/**
|
|
2761
|
+
* Singleton registry of chip-renderer components and keyboard-alternative draggable
|
|
2762
|
+
* items, keyed by `kind` and `appId`.
|
|
2763
|
+
*
|
|
2764
|
+
* Like {@link AgentCommandRegistry}, this lives in the DS so it crosses the federation
|
|
2765
|
+
* boundary as a single instance. Hosts (the shell `<fly-agent-input>`) lookup
|
|
2766
|
+
* renderers; remotes register them.
|
|
2767
|
+
*
|
|
2768
|
+
* Renderer lookup is `O(n)` over the registered list — the registry is small (one or
|
|
2769
|
+
* two entries per app) and lookups happen on drop, not per frame.
|
|
2770
|
+
*
|
|
2771
|
+
* Draggable storage is per-`appId` writable signal cached in a Map, so each `appId`
|
|
2772
|
+
* gets a stable {@link Signal} reference across reads (callers can `===`-compare).
|
|
2773
|
+
*/
|
|
2774
|
+
class AgentDropRegistry {
|
|
2775
|
+
_renderers = signal([], ...(ngDevMode ? [{ debugName: "_renderers" }] : /* istanbul ignore next */ []));
|
|
2776
|
+
/** Per-appId writable store for draggables. Read-only mirror returned to callers. */
|
|
2777
|
+
_draggablesByApp = new Map();
|
|
2778
|
+
/**
|
|
2779
|
+
* Look up the chip-renderer component class for a `kind`. The newest registration for
|
|
2780
|
+
* a given `kind` wins, regardless of `appId` — we scan the list in reverse so a later
|
|
2781
|
+
* `register` call shadows an earlier one for the same `kind`.
|
|
2782
|
+
*
|
|
2783
|
+
* Returns `null` when no renderer is registered; the host falls back to a generic
|
|
2784
|
+
* `plainTextFallback` chip.
|
|
2785
|
+
*/
|
|
2786
|
+
rendererFor(kind) {
|
|
2787
|
+
const list = this._renderers();
|
|
2788
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
2789
|
+
if (list[i].kind === kind)
|
|
2790
|
+
return list[i].component;
|
|
2791
|
+
}
|
|
2792
|
+
return null;
|
|
2793
|
+
}
|
|
2794
|
+
/**
|
|
2795
|
+
* Register a renderer for a `(kind, appId)` pair. Re-registering the same pair
|
|
2796
|
+
* replaces the prior entry; the disposal handle for the prior registration becomes
|
|
2797
|
+
* a no-op.
|
|
2798
|
+
*
|
|
2799
|
+
* Returns a {@link AgentCommandHandle} (re-used to keep the disposable shape uniform
|
|
2800
|
+
* across registries) whose `dispose()` removes this exact registration.
|
|
2801
|
+
*/
|
|
2802
|
+
register(reg) {
|
|
2803
|
+
const generation = ++this._generation;
|
|
2804
|
+
const key = compositeKey(reg.kind, reg.appId);
|
|
2805
|
+
this._renderers.update((rows) => [
|
|
2806
|
+
...rows.filter((r) => compositeKey(r.kind, r.appId) !== key),
|
|
2807
|
+
reg,
|
|
2808
|
+
]);
|
|
2809
|
+
this._owners.set(key, generation);
|
|
2810
|
+
return {
|
|
2811
|
+
dispose: () => {
|
|
2812
|
+
if (this._owners.get(key) === generation) {
|
|
2813
|
+
this._owners.delete(key);
|
|
2814
|
+
this._renderers.update((rows) => rows.filter((r) => compositeKey(r.kind, r.appId) !== key));
|
|
2815
|
+
}
|
|
2816
|
+
},
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
/**
|
|
2820
|
+
* Apps publish their live "draggable from focused window" set so the keyboard
|
|
2821
|
+
* "Attach from app…" menu can offer them. Hosts call this each time the user-visible
|
|
2822
|
+
* draggable list changes; passing an empty array clears the entry for this `appId`.
|
|
2823
|
+
*/
|
|
2824
|
+
publishDraggables(appId, items) {
|
|
2825
|
+
if (items.length === 0) {
|
|
2826
|
+
const existing = this._draggablesByApp.get(appId);
|
|
2827
|
+
if (existing)
|
|
2828
|
+
existing.set([]);
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
this.bucketFor(appId).set(items);
|
|
2832
|
+
}
|
|
2833
|
+
/**
|
|
2834
|
+
* Reactive read of the draggable set published for `appId`. Empty when none. The
|
|
2835
|
+
* returned signal is stable across calls (cached by `appId`), so consumers can use
|
|
2836
|
+
* it as a stable input to `computed()`.
|
|
2837
|
+
*/
|
|
2838
|
+
draggablesFor(appId) {
|
|
2839
|
+
return this.bucketFor(appId).asReadonly();
|
|
2840
|
+
}
|
|
2841
|
+
/** Lazy-init the per-appId writable bucket. Returns the writable handle for internal use. */
|
|
2842
|
+
bucketFor(appId) {
|
|
2843
|
+
let bucket = this._draggablesByApp.get(appId);
|
|
2844
|
+
if (!bucket) {
|
|
2845
|
+
bucket = signal([]);
|
|
2846
|
+
this._draggablesByApp.set(appId, bucket);
|
|
2847
|
+
}
|
|
2848
|
+
return bucket;
|
|
2849
|
+
}
|
|
2850
|
+
_generation = 0;
|
|
2851
|
+
_owners = new Map();
|
|
2852
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentDropRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2853
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentDropRegistry, providedIn: 'root' });
|
|
2854
|
+
}
|
|
2855
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentDropRegistry, decorators: [{
|
|
2856
|
+
type: Injectable,
|
|
2857
|
+
args: [{ providedIn: 'root' }]
|
|
2858
|
+
}] });
|
|
2859
|
+
function compositeKey(kind, appId) {
|
|
2860
|
+
return `${kind}::${appId}`;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
/**
|
|
2864
|
+
* Agent input contracts.
|
|
2865
|
+
*
|
|
2866
|
+
* These types define the wire and DI surface for the new `<fly-agent-input>` orchestrator
|
|
2867
|
+
* (Phase 2+). Phase 1 ships only the contracts: payload envelope, command/drop registry
|
|
2868
|
+
* shapes, and chip-host inputs. The concrete components consume them later.
|
|
2869
|
+
*
|
|
2870
|
+
* Design rules:
|
|
2871
|
+
* - All fields are `readonly` — the registries store these in signal stores and we never
|
|
2872
|
+
* want a host mutating after registration. Hosts construct fresh objects to update.
|
|
2873
|
+
* - The discriminated kinds (`scope`, `mode`, `kind`) carry the entire variance — no
|
|
2874
|
+
* boolean flags. New variants extend the union without breaking existing consumers.
|
|
2875
|
+
* - The `Type<AgentChipHostInputs<T>>` shape is intentional: chip components declare
|
|
2876
|
+
* `payload`, `mode`, optional `onRemove` as Angular `input()` signals; the registry
|
|
2877
|
+
* wires them via `NgComponentOutlet` inputs in the consumer. The component reference
|
|
2878
|
+
* itself never leaks across the federation boundary by value — only by class identity.
|
|
2879
|
+
*/
|
|
2880
|
+
/** Frozen MIME used by `flyAgentDraggable` and the drop-zone reader. Never change without a DS major. */
|
|
2881
|
+
const AGENT_DRAG_MIME = 'application/x-fly-agent-payload+json';
|
|
2882
|
+
/** Frozen payload envelope version. Bump only with a published DS major. */
|
|
2883
|
+
const AGENT_PAYLOAD_VERSION = 1;
|
|
2884
|
+
const DEFAULT_AGENT_PAYLOAD_LIMITS = Object.freeze({
|
|
2885
|
+
maxJsonBytes: 32 * 1024,
|
|
2886
|
+
maxPlainTextFallbackBytes: 8 * 1024,
|
|
2887
|
+
maxStringFieldBytes: 4 * 1024,
|
|
2888
|
+
});
|
|
2889
|
+
|
|
2890
|
+
/**
|
|
2891
|
+
* Pure utility helpers for agent drag payload size guarding.
|
|
2892
|
+
*
|
|
2893
|
+
* Used by `flyAgentDraggable` at `dragstart` (Phase 2) and by app pre-flight code that
|
|
2894
|
+
* wants a clean payload before constructing the directive input. No Angular DI, no
|
|
2895
|
+
* runtime side effects — safe to import from anywhere.
|
|
2896
|
+
*
|
|
2897
|
+
* Performance notes:
|
|
2898
|
+
* - The `TextEncoder` is lazily allocated and cached. The constructor runs once per
|
|
2899
|
+
* module instance even under federation (singleton DS).
|
|
2900
|
+
* - String trimming uses `Intl.Segmenter` when available (all evergreen browsers
|
|
2901
|
+
* including Safari 14.1+) so we cut on grapheme boundaries — emoji ZWJ sequences,
|
|
2902
|
+
* combining marks, regional indicator pairs all stay intact. Fallback is
|
|
2903
|
+
* UTF-16 code-unit slice, which is a graceful degradation: it can split a
|
|
2904
|
+
* surrogate pair, but the resulting JSON is still syntactically valid and
|
|
2905
|
+
* downstream renderers handle the replacement char.
|
|
2906
|
+
*/
|
|
2907
|
+
/**
|
|
2908
|
+
* Thrown by {@link trimAgentPayload} when even a fully string-trimmed envelope still
|
|
2909
|
+
* exceeds `maxJsonBytes` (e.g. an envelope with thousands of nested empty objects /
|
|
2910
|
+
* array entries / property keys). Subclasses `RangeError` so callers using the standard
|
|
2911
|
+
* `instanceof RangeError` check still match, while exposing the structured fields
|
|
2912
|
+
* (`actualBytes`, `limitBytes`, `reason: 'json_too_large'`) needed for telemetry and
|
|
2913
|
+
* symmetric handling with {@link AgentPayloadValidationResult} failures.
|
|
2914
|
+
*/
|
|
2915
|
+
class AgentPayloadOversizeError extends RangeError {
|
|
2916
|
+
actualBytes;
|
|
2917
|
+
limitBytes;
|
|
2918
|
+
reason = 'json_too_large';
|
|
2919
|
+
constructor(message, actualBytes, limitBytes) {
|
|
2920
|
+
super(message);
|
|
2921
|
+
this.actualBytes = actualBytes;
|
|
2922
|
+
this.limitBytes = limitBytes;
|
|
2923
|
+
this.name = 'AgentPayloadOversizeError';
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
/** Cap on the dotted `fieldPath` string so a 10 MB property-key payload can't lock dev tools when `console.warn`d. */
|
|
2927
|
+
const FIELD_PATH_MAX_CHARS = 256;
|
|
2928
|
+
/** Append a path segment, capping the result at {@link FIELD_PATH_MAX_CHARS} chars (with `…` marker). */
|
|
2929
|
+
function appendPath(base, segment) {
|
|
2930
|
+
const next = base + segment;
|
|
2931
|
+
if (next.length <= FIELD_PATH_MAX_CHARS)
|
|
2932
|
+
return next;
|
|
2933
|
+
return next.slice(0, FIELD_PATH_MAX_CHARS - 1) + '…';
|
|
2934
|
+
}
|
|
2935
|
+
let _cachedEncoder = null;
|
|
2936
|
+
function getEncoder() {
|
|
2937
|
+
return (_cachedEncoder ??= new TextEncoder());
|
|
2938
|
+
}
|
|
2939
|
+
/** UTF-8 byte length of `s`. Cheap; cached encoder. */
|
|
2940
|
+
function utf8ByteLength(s) {
|
|
2941
|
+
return getEncoder().encode(s).length;
|
|
2942
|
+
}
|
|
2943
|
+
/**
|
|
2944
|
+
* Trim `input` so the UTF-8 byte length of the result is `<= maxBytes`. When trimming
|
|
2945
|
+
* occurs, append `ellipsis` (default `…`, U+2026) — its bytes are included in the cap.
|
|
2946
|
+
*
|
|
2947
|
+
* Idempotent: `trimAgentString(trimAgentString(s, n), n)` equals `trimAgentString(s, n)`.
|
|
2948
|
+
*
|
|
2949
|
+
* Edge cases:
|
|
2950
|
+
* - `maxBytes` shorter than the ellipsis itself returns an empty string.
|
|
2951
|
+
* - `Intl.Segmenter` (when present) ensures we never split a grapheme cluster.
|
|
2952
|
+
* - When the encoded length is already within the cap, returns `input` unchanged.
|
|
2953
|
+
*/
|
|
2954
|
+
function trimAgentString(input, maxBytes, ellipsis = '…') {
|
|
2955
|
+
if (maxBytes <= 0)
|
|
2956
|
+
return '';
|
|
2957
|
+
const enc = getEncoder();
|
|
2958
|
+
const inputLen = enc.encode(input).length;
|
|
2959
|
+
if (inputLen <= maxBytes)
|
|
2960
|
+
return input;
|
|
2961
|
+
const ellipsisLen = enc.encode(ellipsis).length;
|
|
2962
|
+
if (maxBytes < ellipsisLen)
|
|
2963
|
+
return '';
|
|
2964
|
+
const budget = maxBytes - ellipsisLen;
|
|
2965
|
+
// Segmenter path: walk graphemes, accumulate until next would overflow.
|
|
2966
|
+
const segmenterCtor = typeof Intl !== 'undefined' && Intl.Segmenter;
|
|
2967
|
+
if (segmenterCtor) {
|
|
2968
|
+
const segmenter = new segmenterCtor(undefined, { granularity: 'grapheme' });
|
|
2969
|
+
let used = 0;
|
|
2970
|
+
let acc = '';
|
|
2971
|
+
for (const seg of segmenter.segment(input)) {
|
|
2972
|
+
const segLen = enc.encode(seg.segment).length;
|
|
2973
|
+
if (used + segLen > budget)
|
|
2974
|
+
break;
|
|
2975
|
+
acc += seg.segment;
|
|
2976
|
+
used += segLen;
|
|
2977
|
+
}
|
|
2978
|
+
return acc + ellipsis;
|
|
2979
|
+
}
|
|
2980
|
+
// Fallback: code-unit slice; verify byte length and shrink until under budget.
|
|
2981
|
+
let cut = input.length;
|
|
2982
|
+
while (cut > 0 && enc.encode(input.slice(0, cut)).length > budget) {
|
|
2983
|
+
cut--;
|
|
2984
|
+
}
|
|
2985
|
+
return input.slice(0, cut) + ellipsis;
|
|
2986
|
+
}
|
|
2987
|
+
/**
|
|
2988
|
+
* Validate an {@link AgentDragPayload} against the supplied limits (or
|
|
2989
|
+
* {@link DEFAULT_AGENT_PAYLOAD_LIMITS} when omitted). Returns `{ ok: true }` or a
|
|
2990
|
+
* structured failure with the offending field path and byte counts.
|
|
2991
|
+
*
|
|
2992
|
+
* Order of checks (cheap → expensive):
|
|
2993
|
+
* 1. `version` matches the frozen {@link AGENT_PAYLOAD_VERSION}.
|
|
2994
|
+
* 2. `kind` is a non-empty string.
|
|
2995
|
+
* 3. `plainTextFallback` fits its cap.
|
|
2996
|
+
* 4. Every string in `payload` (recursively) fits the per-field cap.
|
|
2997
|
+
* 5. The full `JSON.stringify(envelope)` fits the JSON cap.
|
|
2998
|
+
*
|
|
2999
|
+
* The walker stops on the first oversize string field — the intent is to surface
|
|
3000
|
+
* actionable feedback, not enumerate every offender.
|
|
3001
|
+
*/
|
|
3002
|
+
function validateAgentPayload(payload, limits) {
|
|
3003
|
+
const eff = { ...DEFAULT_AGENT_PAYLOAD_LIMITS, ...(limits ?? {}) };
|
|
3004
|
+
if (payload.version !== AGENT_PAYLOAD_VERSION) {
|
|
3005
|
+
return { ok: false, reason: 'invalid_version' };
|
|
3006
|
+
}
|
|
3007
|
+
if (typeof payload.kind !== 'string' || payload.kind.length === 0) {
|
|
3008
|
+
return { ok: false, reason: 'invalid_kind' };
|
|
3009
|
+
}
|
|
3010
|
+
const fbBytes = utf8ByteLength(payload.plainTextFallback ?? '');
|
|
3011
|
+
if (fbBytes > eff.maxPlainTextFallbackBytes) {
|
|
3012
|
+
return {
|
|
3013
|
+
ok: false,
|
|
3014
|
+
reason: 'fallback_too_large',
|
|
3015
|
+
fieldPath: 'plainTextFallback',
|
|
3016
|
+
actualBytes: fbBytes,
|
|
3017
|
+
limitBytes: eff.maxPlainTextFallbackBytes,
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
const fieldFailure = walkStringsForOverage(payload.payload, 'payload', eff.maxStringFieldBytes);
|
|
3021
|
+
if (fieldFailure)
|
|
3022
|
+
return fieldFailure;
|
|
3023
|
+
const json = safeStringify(payload);
|
|
3024
|
+
const jsonBytes = utf8ByteLength(json);
|
|
3025
|
+
if (jsonBytes > eff.maxJsonBytes) {
|
|
3026
|
+
return {
|
|
3027
|
+
ok: false,
|
|
3028
|
+
reason: 'json_too_large',
|
|
3029
|
+
actualBytes: jsonBytes,
|
|
3030
|
+
limitBytes: eff.maxJsonBytes,
|
|
3031
|
+
};
|
|
3032
|
+
}
|
|
3033
|
+
return { ok: true };
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* Returns a NEW payload with all string fields trimmed to fit the per-field cap, the
|
|
3037
|
+
* `plainTextFallback` trimmed to its cap, and the full envelope re-checked against the
|
|
3038
|
+
* JSON cap. Idempotent — a second call against the trimmed result yields equal output.
|
|
3039
|
+
*
|
|
3040
|
+
* Throws {@link AgentPayloadOversizeError} only if even an empty trim couldn't shrink it
|
|
3041
|
+
* under the cap (e.g. an envelope with 1000+ nested empty objects). The error subclasses
|
|
3042
|
+
* `RangeError`, so legacy `instanceof RangeError` checks still match. Apps that hit this
|
|
3043
|
+
* should re-shape `payload` rather than call again.
|
|
3044
|
+
*/
|
|
3045
|
+
function trimAgentPayload(payload, limits) {
|
|
3046
|
+
const eff = { ...DEFAULT_AGENT_PAYLOAD_LIMITS, ...(limits ?? {}) };
|
|
3047
|
+
const trimmedFallback = trimAgentString(payload.plainTextFallback ?? '', eff.maxPlainTextFallbackBytes);
|
|
3048
|
+
const trimmedPayload = mapStrings(payload.payload, (s) => trimAgentString(s, eff.maxStringFieldBytes));
|
|
3049
|
+
const next = {
|
|
3050
|
+
...payload,
|
|
3051
|
+
plainTextFallback: trimmedFallback,
|
|
3052
|
+
payload: trimmedPayload,
|
|
3053
|
+
};
|
|
3054
|
+
const jsonBytes = utf8ByteLength(safeStringify(next));
|
|
3055
|
+
if (jsonBytes > eff.maxJsonBytes) {
|
|
3056
|
+
throw new AgentPayloadOversizeError(`AgentDragPayload exceeds maxJsonBytes (${jsonBytes} > ${eff.maxJsonBytes}) after string trimming. Re-shape the payload.`, jsonBytes, eff.maxJsonBytes);
|
|
3057
|
+
}
|
|
3058
|
+
return next;
|
|
3059
|
+
}
|
|
3060
|
+
// ─── Internal helpers ───────────────────────────────────────────────────────
|
|
3061
|
+
/**
|
|
3062
|
+
* Recursively walks `value`, returning the first string field that exceeds `maxBytes`
|
|
3063
|
+
* (as a structured failure). Returns `null` when every string fits.
|
|
3064
|
+
*
|
|
3065
|
+
* Path uses dotted property-access notation: `payload.body`, `payload.tags[2]`. Cycles
|
|
3066
|
+
* are detected via a WeakSet so attacker-crafted self-references don't loop forever.
|
|
3067
|
+
* Path strings are capped at {@link FIELD_PATH_MAX_CHARS} characters via {@link appendPath}
|
|
3068
|
+
* to prevent a malicious payload with multi-MB property keys from producing a multi-MB
|
|
3069
|
+
* `fieldPath` that locks up dev tools when logged.
|
|
3070
|
+
*/
|
|
3071
|
+
function walkStringsForOverage(value, path, maxBytes, seen = new WeakSet()) {
|
|
3072
|
+
if (typeof value === 'string') {
|
|
3073
|
+
const bytes = utf8ByteLength(value);
|
|
3074
|
+
if (bytes > maxBytes) {
|
|
3075
|
+
return {
|
|
3076
|
+
ok: false,
|
|
3077
|
+
reason: 'field_too_large',
|
|
3078
|
+
fieldPath: path,
|
|
3079
|
+
actualBytes: bytes,
|
|
3080
|
+
limitBytes: maxBytes,
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
return null;
|
|
3084
|
+
}
|
|
3085
|
+
if (value === null || typeof value !== 'object')
|
|
3086
|
+
return null;
|
|
3087
|
+
if (seen.has(value))
|
|
3088
|
+
return null;
|
|
3089
|
+
seen.add(value);
|
|
3090
|
+
if (Array.isArray(value)) {
|
|
3091
|
+
for (let i = 0; i < value.length; i++) {
|
|
3092
|
+
const r = walkStringsForOverage(value[i], appendPath(path, `[${i}]`), maxBytes, seen);
|
|
3093
|
+
if (r)
|
|
3094
|
+
return r;
|
|
3095
|
+
}
|
|
3096
|
+
return null;
|
|
3097
|
+
}
|
|
3098
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3099
|
+
const r = walkStringsForOverage(v, appendPath(path, `.${k}`), maxBytes, seen);
|
|
3100
|
+
if (r)
|
|
3101
|
+
return r;
|
|
3102
|
+
}
|
|
3103
|
+
return null;
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* Recursively maps every string leaf via `fn`, preserving object/array structure.
|
|
3107
|
+
* Cycles are short-circuited to `null` rather than throwing — the caller already
|
|
3108
|
+
* has a JSON.stringify cap that will catch any pathological input.
|
|
3109
|
+
*/
|
|
3110
|
+
function mapStrings(value, fn, seen = new WeakSet()) {
|
|
3111
|
+
if (typeof value === 'string')
|
|
3112
|
+
return fn(value);
|
|
3113
|
+
if (value === null || typeof value !== 'object')
|
|
3114
|
+
return value;
|
|
3115
|
+
if (seen.has(value))
|
|
3116
|
+
return null;
|
|
3117
|
+
seen.add(value);
|
|
3118
|
+
if (Array.isArray(value)) {
|
|
3119
|
+
return value.map((v) => mapStrings(v, fn, seen));
|
|
3120
|
+
}
|
|
3121
|
+
const out = {};
|
|
3122
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3123
|
+
out[k] = mapStrings(v, fn, seen);
|
|
3124
|
+
}
|
|
3125
|
+
return out;
|
|
3126
|
+
}
|
|
3127
|
+
/** `JSON.stringify` with a cycle-safe replacer — defends against attacker-crafted graphs. */
|
|
3128
|
+
function safeStringify(value) {
|
|
3129
|
+
const seen = new WeakSet();
|
|
3130
|
+
return JSON.stringify(value, (_k, v) => {
|
|
3131
|
+
if (v !== null && typeof v === 'object') {
|
|
3132
|
+
if (seen.has(v))
|
|
3133
|
+
return '[Circular]';
|
|
3134
|
+
seen.add(v);
|
|
3135
|
+
}
|
|
3136
|
+
return v;
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
/**
|
|
3141
|
+
* Strict whitelist for the ghost element id. Letter-led ASCII, allows word/hyphen/colon/dot,
|
|
3142
|
+
* capped at 128 chars. Anything else is rejected before reaching `getElementById` so a
|
|
3143
|
+
* future maintainer can't swap to `querySelector` and quietly reintroduce a CSS-injection
|
|
3144
|
+
* vector. (`getElementById` itself is string-equality-safe; the whitelist is defence-in-depth.)
|
|
3145
|
+
*/
|
|
3146
|
+
const GHOST_ID_PATTERN = /^[A-Za-z][\w\-:.]{0,127}$/;
|
|
3147
|
+
/**
|
|
3148
|
+
* Single canonical way to declare a drag source for the agent input.
|
|
3149
|
+
*
|
|
3150
|
+
* Apply on any element a remote wants to make draggable into the `<fly-agent-input>`:
|
|
3151
|
+
*
|
|
3152
|
+
* ```html
|
|
3153
|
+
* <article [flyAgentDraggable]="() => buildPayload(trend)" flyAgentDraggableGhostId="trend-ghost">…</article>
|
|
3154
|
+
* ```
|
|
3155
|
+
*
|
|
3156
|
+
* On `dragstart` the directive:
|
|
3157
|
+
* 1. Resolves the payload (calls the builder fn if supplied; otherwise reads the
|
|
3158
|
+
* static value).
|
|
3159
|
+
* 2. Validates against {@link DEFAULT_AGENT_PAYLOAD_LIMITS} via
|
|
3160
|
+
* {@link validateAgentPayload}. On failure it `console.warn`s with the structured
|
|
3161
|
+
* reason and writes ONLY the `text/plain` fallback — the receiver still sees a
|
|
3162
|
+
* legible drop, but the typed envelope is skipped so a misconfigured app can't
|
|
3163
|
+
* corrupt downstream consumers with an oversize payload.
|
|
3164
|
+
* 3. Writes the typed MIME (`application/x-fly-agent-payload+json`) plus the
|
|
3165
|
+
* `text/plain` fallback so non-Fly drop targets still get useful text.
|
|
3166
|
+
* 4. Sets `effectAllowed = 'copy'` (we never *move* domain objects across windows).
|
|
3167
|
+
* 5. When `flyAgentDraggableGhostId` resolves to an element in the DOM, calls
|
|
3168
|
+
* `setDragImage` to use that element as the drag preview; otherwise the browser
|
|
3169
|
+
* default applies.
|
|
3170
|
+
*
|
|
3171
|
+
* Notes:
|
|
3172
|
+
* - Builder form is preferred over static — it captures fresh state at `dragstart`,
|
|
3173
|
+
* not at directive init. A static `AgentDragPayload` is fine for items whose
|
|
3174
|
+
* domain object never changes after the row mounts.
|
|
3175
|
+
* - The directive sets `[attr.draggable]="true"` so callers don't have to.
|
|
3176
|
+
*/
|
|
3177
|
+
class FlyAgentDraggableDirective {
|
|
3178
|
+
/**
|
|
3179
|
+
* Either a static payload or a builder evaluated at `dragstart` (preferred — captures
|
|
3180
|
+
* fresh state). `input.required` so misuse fails at template compile time.
|
|
3181
|
+
*/
|
|
3182
|
+
flyAgentDraggable = input.required(...(ngDevMode ? [{ debugName: "flyAgentDraggable" }] : /* istanbul ignore next */ []));
|
|
3183
|
+
/**
|
|
3184
|
+
* Optional ghost element id (an existing element in the DOM whose snapshot becomes
|
|
3185
|
+
* the drag image). Falls back to the default browser drag image when absent or the
|
|
3186
|
+
* element can't be resolved.
|
|
3187
|
+
*
|
|
3188
|
+
* Must be a valid HTML id (ASCII, letter-led). `getElementById` is intentional —
|
|
3189
|
+
* do NOT swap to `querySelector` without re-introducing this whitelist.
|
|
3190
|
+
*/
|
|
3191
|
+
flyAgentDraggableGhostId = input(undefined, ...(ngDevMode ? [{ debugName: "flyAgentDraggableGhostId" }] : /* istanbul ignore next */ []));
|
|
3192
|
+
doc = inject(DOCUMENT$1);
|
|
3193
|
+
onDragStart(ev) {
|
|
3194
|
+
const dt = ev.dataTransfer;
|
|
3195
|
+
if (!dt)
|
|
3196
|
+
return;
|
|
3197
|
+
const payload = this.resolvePayload();
|
|
3198
|
+
const validation = validateAgentPayload(payload);
|
|
3199
|
+
if (!validation.ok) {
|
|
3200
|
+
// eslint-disable-next-line no-console
|
|
3201
|
+
console.warn('[flyAgentDraggable] payload validation failed; degrading to text/plain', validation);
|
|
3202
|
+
dt.setData('text/plain', payload.plainTextFallback ?? '');
|
|
3203
|
+
dt.effectAllowed = 'copy';
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
dt.setData(AGENT_DRAG_MIME, JSON.stringify(payload));
|
|
3207
|
+
dt.setData('text/plain', payload.plainTextFallback ?? '');
|
|
3208
|
+
dt.effectAllowed = 'copy';
|
|
3209
|
+
const ghostId = this.flyAgentDraggableGhostId();
|
|
3210
|
+
if (ghostId) {
|
|
3211
|
+
if (!GHOST_ID_PATTERN.test(ghostId)) {
|
|
3212
|
+
// Don't echo the rejected id (caller-controlled, could be huge).
|
|
3213
|
+
// eslint-disable-next-line no-console
|
|
3214
|
+
console.warn('[flyAgentDraggable] ignoring ghost id', { reason: 'invalid_ghost_id' });
|
|
3215
|
+
}
|
|
3216
|
+
else {
|
|
3217
|
+
const ghost = this.doc.getElementById(ghostId);
|
|
3218
|
+
if (ghost) {
|
|
3219
|
+
// Center the drag image; ResizeObserver-aware sizing is left to the host element.
|
|
3220
|
+
dt.setDragImage(ghost, ghost.clientWidth / 2, ghost.clientHeight / 2);
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
resolvePayload() {
|
|
3226
|
+
const raw = this.flyAgentDraggable();
|
|
3227
|
+
return typeof raw === 'function' ? raw() : raw;
|
|
3228
|
+
}
|
|
3229
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyAgentDraggableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3230
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: FlyAgentDraggableDirective, isStandalone: true, selector: "[flyAgentDraggable]", inputs: { flyAgentDraggable: { classPropertyName: "flyAgentDraggable", publicName: "flyAgentDraggable", isSignal: true, isRequired: true, transformFunction: null }, flyAgentDraggableGhostId: { classPropertyName: "flyAgentDraggableGhostId", publicName: "flyAgentDraggableGhostId", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "dragstart": "onDragStart($event)" }, properties: { "attr.draggable": "\"true\"" } }, ngImport: i0 });
|
|
3231
|
+
}
|
|
3232
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyAgentDraggableDirective, decorators: [{
|
|
3233
|
+
type: Directive,
|
|
3234
|
+
args: [{
|
|
3235
|
+
selector: '[flyAgentDraggable]',
|
|
3236
|
+
standalone: true,
|
|
3237
|
+
host: {
|
|
3238
|
+
'[attr.draggable]': '"true"',
|
|
3239
|
+
},
|
|
3240
|
+
}]
|
|
3241
|
+
}], propDecorators: { flyAgentDraggable: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyAgentDraggable", required: true }] }], flyAgentDraggableGhostId: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyAgentDraggableGhostId", required: false }] }], onDragStart: [{
|
|
3242
|
+
type: HostListener,
|
|
3243
|
+
args: ['dragstart', ['$event']]
|
|
3244
|
+
}] } });
|
|
3245
|
+
|
|
3246
|
+
/**
|
|
3247
|
+
* Verbatim TypeScript port of the C# `LanguageDTO[] _AllLanguages` array supplied by
|
|
3248
|
+
* product. **31 entries**, in the canonical order. Three RTL flagged: `he-IL`, `ur-IN`,
|
|
3249
|
+
* `ar-AE`. Includes `cmn-Hant-TW` for Chinese and `ka-GE` for Georgian.
|
|
3250
|
+
*
|
|
3251
|
+
* The order matches the C# source byte-for-byte — do not re-sort. Downstream tests
|
|
3252
|
+
* (`fly-locale-catalog.spec.ts`) pin the count, the unique-key constraints, and the
|
|
3253
|
+
* RTL set; if you reorder or add an entry, those tests will guide the fix.
|
|
3254
|
+
*
|
|
3255
|
+
* The strings carry Cyrillic, Hebrew, Vietnamese, Thai, Indonesian, Bengali, Devanagari,
|
|
3256
|
+
* Urdu, Catalan, Polish, Korean, Russian, Japanese, Han, Turkish, Romanian, Norwegian,
|
|
3257
|
+
* Hungarian, Greek, Finnish, Danish, Dutch, Swedish, Portuguese, Italian, German,
|
|
3258
|
+
* Spanish, French, English, Arabic, and Georgian. The file is saved as UTF-8 without
|
|
3259
|
+
* BOM (matches every other locale file in the repo). The `…` (U+2026) ellipsis is
|
|
3260
|
+
* preserved verbatim.
|
|
3261
|
+
*/
|
|
3262
|
+
const FLY_LOCALE_CATALOG = Object.freeze([
|
|
3263
|
+
{
|
|
3264
|
+
prefix: 'UK',
|
|
3265
|
+
dialect: 'uk-UA',
|
|
3266
|
+
englishName: 'Ukrainian',
|
|
3267
|
+
arabicName: 'الأوكرانية',
|
|
3268
|
+
nativeName: 'Українська',
|
|
3269
|
+
detected: 'Виявлена мова',
|
|
3270
|
+
searching: 'Шукаючи',
|
|
3271
|
+
elevating: 'Підняття до',
|
|
3272
|
+
translating: 'Переклад на',
|
|
3273
|
+
generatingAnswersForYou: 'Створення відповідей для вас...',
|
|
3274
|
+
learnMore: 'Дізнайтеся більше',
|
|
3275
|
+
waitingForResponseMsg: 'Будь ласка, наберіться терпіння, ми ще думаємо',
|
|
3276
|
+
isRtl: false,
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
prefix: 'HE',
|
|
3280
|
+
dialect: 'he-IL',
|
|
3281
|
+
englishName: 'Hebrew',
|
|
3282
|
+
arabicName: 'العبرية',
|
|
3283
|
+
nativeName: 'עברית',
|
|
3284
|
+
detected: 'שפה מזוהה',
|
|
3285
|
+
searching: 'מחפש אחר',
|
|
3286
|
+
elevating: 'מרומם ל',
|
|
3287
|
+
translating: 'תרגום ל',
|
|
3288
|
+
generatingAnswersForYou: 'מייצר עבורך תשובות...',
|
|
3289
|
+
learnMore: 'למידע נוסף',
|
|
3290
|
+
waitingForResponseMsg: 'אנא התאזרו בסבלנות, אנחנו עדיין חושבים',
|
|
3291
|
+
isRtl: true,
|
|
3292
|
+
},
|
|
3293
|
+
{
|
|
3294
|
+
prefix: 'VI',
|
|
3295
|
+
dialect: 'vi-VN',
|
|
3296
|
+
englishName: 'Vietnamese',
|
|
3297
|
+
arabicName: 'الفيتنامية',
|
|
3298
|
+
nativeName: 'Tiếng Việt',
|
|
3299
|
+
detected: 'Ngôn ngữ được phát hiện',
|
|
3300
|
+
searching: 'Tìm kiếm',
|
|
3301
|
+
elevating: 'Nâng lên',
|
|
3302
|
+
translating: 'Dịch sang',
|
|
3303
|
+
generatingAnswersForYou: 'Đang tạo câu trả lời cho bạn...',
|
|
3304
|
+
learnMore: 'Tìm hiểu thêm',
|
|
3305
|
+
waitingForResponseMsg: 'Vui lòng kiên nhẫn, chúng tôi vẫn đang suy nghĩ',
|
|
3306
|
+
isRtl: false,
|
|
3307
|
+
},
|
|
3308
|
+
{
|
|
3309
|
+
prefix: 'TH',
|
|
3310
|
+
dialect: 'th-TH',
|
|
3311
|
+
englishName: 'Thai',
|
|
3312
|
+
arabicName: 'التايلاندية',
|
|
3313
|
+
nativeName: 'ไทย',
|
|
3314
|
+
detected: 'ภาษาที่ตรวจพบ',
|
|
3315
|
+
searching: 'ค้นหา',
|
|
3316
|
+
elevating: 'ยกระดับเป็น',
|
|
3317
|
+
translating: 'การแปล',
|
|
3318
|
+
generatingAnswersForYou: 'กำลังสร้างคำตอบให้กับคุณ...',
|
|
3319
|
+
learnMore: 'เรียนรู้เพิ่มเติม',
|
|
3320
|
+
waitingForResponseMsg: 'โปรดอดใจรอ, พวกเรายังคงกำลังคิดอยู่',
|
|
3321
|
+
isRtl: false,
|
|
3322
|
+
},
|
|
3323
|
+
{
|
|
3324
|
+
prefix: 'ID',
|
|
3325
|
+
dialect: 'id-ID',
|
|
3326
|
+
englishName: 'Indonesian',
|
|
3327
|
+
arabicName: 'الإندونيسية',
|
|
3328
|
+
nativeName: 'Bahasa Indonesia',
|
|
3329
|
+
detected: 'Bahasa yang terdeteksi',
|
|
3330
|
+
searching: 'Mencari',
|
|
3331
|
+
elevating: 'Meninggalkan ke',
|
|
3332
|
+
translating: 'Menerjemahkan ke',
|
|
3333
|
+
generatingAnswersForYou: 'Menghasilkan jawaban untuk Anda...',
|
|
3334
|
+
learnMore: 'Cari tahu lebih lanjut',
|
|
3335
|
+
waitingForResponseMsg: 'Harap bersabar, kami masih sedang berpikir',
|
|
3336
|
+
isRtl: false,
|
|
3337
|
+
},
|
|
3338
|
+
{
|
|
3339
|
+
prefix: 'BN',
|
|
3340
|
+
dialect: 'bn-BD',
|
|
3341
|
+
englishName: 'Bengali',
|
|
3342
|
+
arabicName: 'البنغالية',
|
|
3343
|
+
nativeName: 'বাংলা',
|
|
3344
|
+
detected: 'সনাক্ত করা ভাষা',
|
|
3345
|
+
searching: 'খুঁজছি',
|
|
3346
|
+
elevating: 'উন্নত',
|
|
3347
|
+
translating: 'অনুবাদ',
|
|
3348
|
+
generatingAnswersForYou: 'আপনার জন্য উত্তর তৈরি করা হচ্ছে...',
|
|
3349
|
+
learnMore: 'আরো জানুন',
|
|
3350
|
+
waitingForResponseMsg: 'অনুগ্রহ করে ধৈর্য ধরুন, আমরা এখনও চিন্তা করছি',
|
|
3351
|
+
isRtl: false,
|
|
3352
|
+
},
|
|
3353
|
+
{
|
|
3354
|
+
prefix: 'HI',
|
|
3355
|
+
dialect: 'hi-IN',
|
|
3356
|
+
englishName: 'Hindi',
|
|
3357
|
+
arabicName: 'الهندية',
|
|
3358
|
+
nativeName: 'हिन्दी',
|
|
3359
|
+
detected: 'पता चला भाषा',
|
|
3360
|
+
searching: 'के लिए खोज रहे हैं',
|
|
3361
|
+
elevating: 'के लिए ऊंचा',
|
|
3362
|
+
translating: 'अनुवाद करना',
|
|
3363
|
+
generatingAnswersForYou: 'आपके लिए उत्तर तैयार किया जा रहा है...',
|
|
3364
|
+
learnMore: 'और जानें',
|
|
3365
|
+
waitingForResponseMsg: 'कृपया धैर्य रखें, हम अभी सोच रहे हैं',
|
|
3366
|
+
isRtl: false,
|
|
3367
|
+
},
|
|
3368
|
+
{
|
|
3369
|
+
prefix: 'UR',
|
|
3370
|
+
dialect: 'ur-IN',
|
|
3371
|
+
englishName: 'Urdu',
|
|
3372
|
+
arabicName: 'الأردية',
|
|
3373
|
+
nativeName: 'اردو',
|
|
3374
|
+
detected: 'پتہ چلا زبان',
|
|
3375
|
+
searching: 'کے لیے تلاش',
|
|
3376
|
+
elevating: 'بلند کرنا',
|
|
3377
|
+
translating: 'ترجمہ کرنا',
|
|
3378
|
+
generatingAnswersForYou: 'آپ کے لیے جوابات تیار کیے جا رہے ہیں...',
|
|
3379
|
+
learnMore: 'مزید جانیں',
|
|
3380
|
+
waitingForResponseMsg: 'براہ کرم صبر کریں، ہم ابھی سوچ رہے ہیں۔',
|
|
3381
|
+
isRtl: true,
|
|
3382
|
+
},
|
|
3383
|
+
{
|
|
3384
|
+
prefix: 'CA',
|
|
3385
|
+
dialect: 'ca-ES',
|
|
3386
|
+
englishName: 'Catalan',
|
|
3387
|
+
arabicName: 'الكتالانية',
|
|
3388
|
+
nativeName: 'Català',
|
|
3389
|
+
detected: 'Idioma detectat',
|
|
3390
|
+
searching: 'Buscant',
|
|
3391
|
+
elevating: 'Elevant a',
|
|
3392
|
+
translating: 'Traduir a',
|
|
3393
|
+
generatingAnswersForYou: 'Generant respostes per a tu...',
|
|
3394
|
+
learnMore: 'Aprèn més',
|
|
3395
|
+
waitingForResponseMsg: 'Si us plau, tingueu paciència, encara estem pensant',
|
|
3396
|
+
isRtl: false,
|
|
3397
|
+
},
|
|
3398
|
+
{
|
|
3399
|
+
prefix: 'PL',
|
|
3400
|
+
dialect: 'pl-PL',
|
|
3401
|
+
englishName: 'Polish',
|
|
3402
|
+
arabicName: 'البولندية',
|
|
3403
|
+
nativeName: 'Polski',
|
|
3404
|
+
detected: 'Wykryty język',
|
|
3405
|
+
searching: 'Szukać',
|
|
3406
|
+
elevating: 'Podniesienie do',
|
|
3407
|
+
translating: 'Tłumaczenie na',
|
|
3408
|
+
generatingAnswersForYou: 'Generowanie odpowiedzi dla Ciebie...',
|
|
3409
|
+
learnMore: 'Dowiedz się więcej',
|
|
3410
|
+
waitingForResponseMsg: 'Proszę o cierpliwość, nadal myślimy',
|
|
3411
|
+
isRtl: false,
|
|
3412
|
+
},
|
|
3413
|
+
{
|
|
3414
|
+
prefix: 'KO',
|
|
3415
|
+
dialect: 'ko-KR',
|
|
3416
|
+
englishName: 'Korean',
|
|
3417
|
+
arabicName: 'الكورية',
|
|
3418
|
+
nativeName: '한국어',
|
|
3419
|
+
detected: '탐지 언어',
|
|
3420
|
+
searching: '검색',
|
|
3421
|
+
elevating: '상승',
|
|
3422
|
+
translating: '번역',
|
|
3423
|
+
generatingAnswersForYou: '답변을 생성하는 중...',
|
|
3424
|
+
learnMore: '더 알아보기',
|
|
3425
|
+
waitingForResponseMsg: '잠시만 기다려 주세요, 저희는 아직 생각 중입니다',
|
|
3426
|
+
isRtl: false,
|
|
3427
|
+
},
|
|
3428
|
+
{
|
|
3429
|
+
prefix: 'RU',
|
|
3430
|
+
dialect: 'ru-RU',
|
|
3431
|
+
englishName: 'Russian',
|
|
3432
|
+
arabicName: 'الروسية',
|
|
3433
|
+
nativeName: 'Русский',
|
|
3434
|
+
detected: 'Обнаруженный язык',
|
|
3435
|
+
searching: 'В поисках',
|
|
3436
|
+
elevating: 'Поднимаясь до',
|
|
3437
|
+
translating: 'Перевод',
|
|
3438
|
+
generatingAnswersForYou: 'Генерация ответов для вас...',
|
|
3439
|
+
learnMore: 'Узнать больше',
|
|
3440
|
+
waitingForResponseMsg: 'Пожалуйста, подождите, мы всё ещё думаем',
|
|
3441
|
+
isRtl: false,
|
|
3442
|
+
},
|
|
3443
|
+
{
|
|
3444
|
+
prefix: 'JA',
|
|
3445
|
+
dialect: 'ja-JP',
|
|
3446
|
+
englishName: 'Japanese',
|
|
3447
|
+
arabicName: 'اليابانية',
|
|
3448
|
+
nativeName: '日本語',
|
|
3449
|
+
detected: '検出言語',
|
|
3450
|
+
searching: '探している',
|
|
3451
|
+
elevating: '昇格',
|
|
3452
|
+
translating: '翻訳',
|
|
3453
|
+
generatingAnswersForYou: 'あなたのための答えを生成中...',
|
|
3454
|
+
learnMore: 'もっと詳しく',
|
|
3455
|
+
waitingForResponseMsg: '少々お待ちください、ただいま考え中です',
|
|
3456
|
+
isRtl: false,
|
|
3457
|
+
},
|
|
3458
|
+
{
|
|
3459
|
+
prefix: 'ZH',
|
|
3460
|
+
dialect: 'cmn-Hant-TW',
|
|
3461
|
+
englishName: 'Chinese',
|
|
3462
|
+
arabicName: 'الصينية',
|
|
3463
|
+
nativeName: '中文',
|
|
3464
|
+
detected: '检测语言',
|
|
3465
|
+
searching: '搜索',
|
|
3466
|
+
elevating: '提升到',
|
|
3467
|
+
translating: '翻译成',
|
|
3468
|
+
generatingAnswersForYou: '为您生成答案...',
|
|
3469
|
+
learnMore: '了解更多',
|
|
3470
|
+
waitingForResponseMsg: '请耐心等待,我们还在思考',
|
|
3471
|
+
isRtl: false,
|
|
3472
|
+
},
|
|
3473
|
+
{
|
|
3474
|
+
prefix: 'TR',
|
|
3475
|
+
dialect: 'tr-TR',
|
|
3476
|
+
englishName: 'Turkish',
|
|
3477
|
+
arabicName: 'التركية',
|
|
3478
|
+
nativeName: 'Türkçe',
|
|
3479
|
+
detected: 'Tespit edilen dil',
|
|
3480
|
+
searching: 'Arama',
|
|
3481
|
+
elevating: 'Yükseltme',
|
|
3482
|
+
translating: 'Çevirme',
|
|
3483
|
+
generatingAnswersForYou: 'Sizin için yanıtlar üretiliyor...',
|
|
3484
|
+
learnMore: 'Daha fazla bilgi edin',
|
|
3485
|
+
waitingForResponseMsg: 'Lütfen sabırlı olun, hâlâ düşünüyoruz',
|
|
3486
|
+
isRtl: false,
|
|
3487
|
+
},
|
|
3488
|
+
{
|
|
3489
|
+
prefix: 'RO',
|
|
3490
|
+
dialect: 'ro-RO',
|
|
3491
|
+
englishName: 'Romanian',
|
|
3492
|
+
arabicName: 'الرومانية',
|
|
3493
|
+
nativeName: 'Română',
|
|
3494
|
+
detected: 'Limba detectată',
|
|
3495
|
+
searching: 'Căutare de',
|
|
3496
|
+
elevating: 'Elevator la',
|
|
3497
|
+
translating: 'Traducerea în',
|
|
3498
|
+
generatingAnswersForYou: 'Generarea de răspunsuri pentru tine...',
|
|
3499
|
+
learnMore: 'Află mai multe',
|
|
3500
|
+
waitingForResponseMsg: 'Vă rugăm să aveți răbdare, încă ne gândim',
|
|
3501
|
+
isRtl: false,
|
|
3502
|
+
},
|
|
3503
|
+
{
|
|
3504
|
+
prefix: 'NO',
|
|
3505
|
+
dialect: 'nb-NO',
|
|
3506
|
+
englishName: 'Norwegian',
|
|
3507
|
+
arabicName: 'النرويجية',
|
|
3508
|
+
nativeName: 'Norsk',
|
|
3509
|
+
detected: 'Oppdaget språk',
|
|
3510
|
+
searching: 'Leter etter',
|
|
3511
|
+
elevating: 'Løfter til',
|
|
3512
|
+
translating: 'Oversette til',
|
|
3513
|
+
generatingAnswersForYou: 'Genererer svar for deg...',
|
|
3514
|
+
learnMore: 'Lær mer',
|
|
3515
|
+
waitingForResponseMsg: 'Vennligst vær tålmodig, vi tenker fortsatt',
|
|
3516
|
+
isRtl: false,
|
|
3517
|
+
},
|
|
3518
|
+
{
|
|
3519
|
+
prefix: 'HU',
|
|
3520
|
+
dialect: 'hu-HU',
|
|
3521
|
+
englishName: 'Hungarian',
|
|
3522
|
+
arabicName: 'الهنغارية',
|
|
3523
|
+
nativeName: 'Magyar',
|
|
3524
|
+
detected: 'Detektált nyelv',
|
|
3525
|
+
searching: 'Keresés-ra,-re',
|
|
3526
|
+
elevating: 'Felemelve',
|
|
3527
|
+
translating: 'Fordítás',
|
|
3528
|
+
generatingAnswersForYou: 'Válaszok generálása az Ön számára...',
|
|
3529
|
+
learnMore: 'Tudj meg többet',
|
|
3530
|
+
waitingForResponseMsg: 'Kérjük, legyen türelemmel, még gondolkodunk',
|
|
3531
|
+
isRtl: false,
|
|
3532
|
+
},
|
|
3533
|
+
{
|
|
3534
|
+
prefix: 'EL',
|
|
3535
|
+
dialect: 'el-GR',
|
|
3536
|
+
englishName: 'Greek',
|
|
3537
|
+
arabicName: 'اليونانية',
|
|
3538
|
+
nativeName: 'Ελληνικά',
|
|
3539
|
+
detected: 'Εντοπισμένη γλώσσα',
|
|
3540
|
+
searching: 'Ψάχνοντας για',
|
|
3541
|
+
elevating: 'Ανυψώ',
|
|
3542
|
+
translating: 'Μεταφράζοντας σε',
|
|
3543
|
+
generatingAnswersForYou: 'Δημιουργία απαντήσεων για εσάς...',
|
|
3544
|
+
learnMore: 'Μάθε περισσότερα',
|
|
3545
|
+
waitingForResponseMsg: 'Παρακαλώ περιμένετε με υπομονή, σκεφτόμαστε ακόμα',
|
|
3546
|
+
isRtl: false,
|
|
3547
|
+
},
|
|
3548
|
+
{
|
|
3549
|
+
prefix: 'FI',
|
|
3550
|
+
dialect: 'fi-FI',
|
|
3551
|
+
englishName: 'Finnish',
|
|
3552
|
+
arabicName: 'الفنلندية',
|
|
3553
|
+
nativeName: 'Suomi',
|
|
3554
|
+
detected: 'Havaittu kieli',
|
|
3555
|
+
searching: 'Etsimässä',
|
|
3556
|
+
elevating: 'Nousee jhk',
|
|
3557
|
+
translating: 'Kääntäen',
|
|
3558
|
+
generatingAnswersForYou: 'Luodaan vastauksia sinulle...',
|
|
3559
|
+
learnMore: 'Lue lisää',
|
|
3560
|
+
waitingForResponseMsg: 'Ole hyvä ja ole kärsivällinen, ajattelemme yhä',
|
|
3561
|
+
isRtl: false,
|
|
3562
|
+
},
|
|
3563
|
+
{
|
|
3564
|
+
prefix: 'DA',
|
|
3565
|
+
dialect: 'da-DK',
|
|
3566
|
+
englishName: 'Danish',
|
|
3567
|
+
arabicName: 'الدانماركية',
|
|
3568
|
+
nativeName: 'Dansk',
|
|
3569
|
+
detected: 'Opdaget sprog',
|
|
3570
|
+
searching: 'Leder efter',
|
|
3571
|
+
elevating: 'Hæve til',
|
|
3572
|
+
translating: 'Oversættelse til',
|
|
3573
|
+
generatingAnswersForYou: 'Generer svar til dig...',
|
|
3574
|
+
learnMore: 'Lær mere',
|
|
3575
|
+
waitingForResponseMsg: 'Vent venligst tålmodigt, vi tænker stadig',
|
|
3576
|
+
isRtl: false,
|
|
3577
|
+
},
|
|
3578
|
+
{
|
|
3579
|
+
prefix: 'NL',
|
|
3580
|
+
dialect: 'nl-NL',
|
|
3581
|
+
englishName: 'Dutch',
|
|
3582
|
+
arabicName: 'الهولندية',
|
|
3583
|
+
nativeName: 'Nederlands',
|
|
3584
|
+
detected: 'Gedetecteerde taal',
|
|
3585
|
+
searching: 'Op zoek naar',
|
|
3586
|
+
elevating: 'Verheffen tot',
|
|
3587
|
+
translating: 'Vertalen naar',
|
|
3588
|
+
generatingAnswersForYou: 'Antwoorden voor u genereren...',
|
|
3589
|
+
learnMore: 'Lees meer',
|
|
3590
|
+
waitingForResponseMsg: 'Gelieve geduld te hebben, we zijn nog aan het nadenken',
|
|
3591
|
+
isRtl: false,
|
|
3592
|
+
},
|
|
3593
|
+
{
|
|
3594
|
+
prefix: 'SV',
|
|
3595
|
+
dialect: 'sv-SE',
|
|
3596
|
+
englishName: 'Swedish',
|
|
3597
|
+
arabicName: 'السويدية',
|
|
3598
|
+
nativeName: 'Svenska',
|
|
3599
|
+
detected: 'Upptäckt språk',
|
|
3600
|
+
searching: 'Söker efter',
|
|
3601
|
+
elevating: 'Upphöjande till',
|
|
3602
|
+
translating: 'Översätta till',
|
|
3603
|
+
generatingAnswersForYou: 'Genererar svar åt dig...',
|
|
3604
|
+
learnMore: 'Läs mer',
|
|
3605
|
+
waitingForResponseMsg: 'Vänligen ha tålamod, vi tänker fortfarande',
|
|
3606
|
+
isRtl: false,
|
|
3607
|
+
},
|
|
3608
|
+
{
|
|
3609
|
+
prefix: 'PT',
|
|
3610
|
+
dialect: 'pt-PT',
|
|
3611
|
+
englishName: 'Portuguese',
|
|
3612
|
+
arabicName: 'البرتغالية',
|
|
3613
|
+
nativeName: 'Português',
|
|
3614
|
+
detected: 'Idioma detectado',
|
|
3615
|
+
searching: 'Procurando por',
|
|
3616
|
+
elevating: 'Elevando para',
|
|
3617
|
+
translating: 'Traduzindo para',
|
|
3618
|
+
generatingAnswersForYou: 'Gerando respostas para você...',
|
|
3619
|
+
learnMore: 'Saiba mais',
|
|
3620
|
+
waitingForResponseMsg: 'Por favor, tenha paciência, ainda estamos a pensar',
|
|
3621
|
+
isRtl: false,
|
|
3622
|
+
},
|
|
3623
|
+
{
|
|
3624
|
+
prefix: 'IT',
|
|
3625
|
+
dialect: 'it-IT',
|
|
3626
|
+
englishName: 'Italian',
|
|
3627
|
+
arabicName: 'الإيطالية',
|
|
3628
|
+
nativeName: 'Italiano',
|
|
3629
|
+
detected: 'Lingua rilevata',
|
|
3630
|
+
searching: 'Alla ricerca di',
|
|
3631
|
+
elevating: 'Elevare a',
|
|
3632
|
+
translating: 'Tradurre in',
|
|
3633
|
+
generatingAnswersForYou: 'Generazione di risposte per te...',
|
|
3634
|
+
learnMore: 'Scopri di più',
|
|
3635
|
+
waitingForResponseMsg: 'Per favore, abbi pazienza, stiamo ancora pensando',
|
|
3636
|
+
isRtl: false,
|
|
3637
|
+
},
|
|
3638
|
+
{
|
|
3639
|
+
prefix: 'DE',
|
|
3640
|
+
dialect: 'de-DE',
|
|
3641
|
+
englishName: 'German',
|
|
3642
|
+
arabicName: 'الألمانية',
|
|
3643
|
+
nativeName: 'Deutsch',
|
|
3644
|
+
detected: 'Erkannte Sprache',
|
|
3645
|
+
searching: 'Auf der Suche nach',
|
|
3646
|
+
elevating: 'Erhöhung zu',
|
|
3647
|
+
translating: 'Übersetzung auf',
|
|
3648
|
+
generatingAnswersForYou: 'Wir generieren Antworten für Sie...',
|
|
3649
|
+
learnMore: 'Erfahre mehr',
|
|
3650
|
+
waitingForResponseMsg: 'Bitte haben Sie Geduld, wir denken noch nach',
|
|
3651
|
+
isRtl: false,
|
|
3652
|
+
},
|
|
3653
|
+
{
|
|
3654
|
+
prefix: 'ES',
|
|
3655
|
+
dialect: 'es-AR',
|
|
3656
|
+
englishName: 'Spanish',
|
|
3657
|
+
arabicName: 'الأسبانية',
|
|
3658
|
+
nativeName: 'Español',
|
|
3659
|
+
detected: 'Idioma detectado',
|
|
3660
|
+
searching: 'Buscando',
|
|
3661
|
+
elevating: 'Elevando a',
|
|
3662
|
+
translating: 'Traduciendo a',
|
|
3663
|
+
generatingAnswersForYou: 'Generando respuestas para ti...',
|
|
3664
|
+
learnMore: 'Aprende más',
|
|
3665
|
+
waitingForResponseMsg: 'Por favor, ten paciencia, todavía estamos pensando',
|
|
3666
|
+
isRtl: false,
|
|
3667
|
+
},
|
|
3668
|
+
{
|
|
3669
|
+
prefix: 'FR',
|
|
3670
|
+
dialect: 'fr-FR',
|
|
3671
|
+
englishName: 'French',
|
|
3672
|
+
arabicName: 'الفرنسية',
|
|
3673
|
+
nativeName: 'Français',
|
|
3674
|
+
detected: 'Langue détectée',
|
|
3675
|
+
searching: 'À la recherche de',
|
|
3676
|
+
elevating: 'Élever à',
|
|
3677
|
+
translating: 'Traduire par',
|
|
3678
|
+
generatingAnswersForYou: 'Générer des réponses pour vous...',
|
|
3679
|
+
learnMore: 'En savoir plus',
|
|
3680
|
+
waitingForResponseMsg: 'Veuillez patienter, nous réfléchissons encore',
|
|
3681
|
+
isRtl: false,
|
|
3682
|
+
},
|
|
3683
|
+
{
|
|
3684
|
+
prefix: 'EN',
|
|
3685
|
+
dialect: 'en-US',
|
|
3686
|
+
englishName: 'English',
|
|
3687
|
+
arabicName: 'الإنجليزية',
|
|
3688
|
+
nativeName: 'English',
|
|
3689
|
+
detected: 'Detected language',
|
|
3690
|
+
searching: 'Searching for',
|
|
3691
|
+
elevating: 'Elevating to',
|
|
3692
|
+
translating: 'Translating to',
|
|
3693
|
+
generatingAnswersForYou: 'Generating answers for you...',
|
|
3694
|
+
learnMore: 'Learn more',
|
|
3695
|
+
waitingForResponseMsg: 'Please bear with us, we are still thinking',
|
|
3696
|
+
isRtl: false,
|
|
3697
|
+
},
|
|
3698
|
+
{
|
|
3699
|
+
prefix: 'AR',
|
|
3700
|
+
dialect: 'ar-AE',
|
|
3701
|
+
englishName: 'Arabic',
|
|
3702
|
+
arabicName: 'العربية',
|
|
3703
|
+
nativeName: 'العربية',
|
|
3704
|
+
detected: 'اللغة المكتشفة',
|
|
3705
|
+
searching: 'البحث عن',
|
|
3706
|
+
elevating: 'رفع إلى',
|
|
3707
|
+
translating: 'ترجمة إلى',
|
|
3708
|
+
generatingAnswersForYou: 'جارٍ إنشاء إجابات لك...',
|
|
3709
|
+
learnMore: 'تعرف على المزيد',
|
|
3710
|
+
waitingForResponseMsg: 'نرجو منك التحلّي بالصبر، نحن ما زلنا نفكر',
|
|
3711
|
+
isRtl: true,
|
|
3712
|
+
},
|
|
3713
|
+
{
|
|
3714
|
+
prefix: 'KA',
|
|
3715
|
+
dialect: 'ka-GE',
|
|
3716
|
+
englishName: 'Georgian',
|
|
3717
|
+
arabicName: 'الجورجية',
|
|
3718
|
+
nativeName: 'ქართული',
|
|
3719
|
+
detected: 'გამოვლენილი ენა',
|
|
3720
|
+
searching: 'ძიება',
|
|
3721
|
+
elevating: 'ამაღლება',
|
|
3722
|
+
translating: 'თარგმნა',
|
|
3723
|
+
generatingAnswersForYou: 'პასუხების გენერირება თქვენთვის...',
|
|
3724
|
+
learnMore: 'გაიგე მეტი',
|
|
3725
|
+
waitingForResponseMsg: 'გთხოვთ მოითმინოთ, ჯერ კიდევ ვფიქრობთ',
|
|
3726
|
+
isRtl: false,
|
|
3727
|
+
},
|
|
3728
|
+
]);
|
|
3729
|
+
/**
|
|
3730
|
+
* Lookup by BCP-47 dialect (e.g. `'ar-AE'`). Case-insensitive per BCP-47 §2.1.1
|
|
3731
|
+
* (subtags are case-insensitive on the wire) — accepts `'en-US'`, `'en-us'`,
|
|
3732
|
+
* `'EN-US'`, etc. all as the canonical EN entry. The catalog itself keeps its
|
|
3733
|
+
* canonical casing for display.
|
|
3734
|
+
*
|
|
3735
|
+
* Returns `undefined` when not found; callers decide whether to fall back to `'en-US'`.
|
|
3736
|
+
*/
|
|
3737
|
+
function findLocaleByDialect(dialect) {
|
|
3738
|
+
if (typeof dialect !== 'string' || dialect.length === 0)
|
|
3739
|
+
return undefined;
|
|
3740
|
+
const lower = dialect.toLowerCase();
|
|
3741
|
+
return FLY_LOCALE_CATALOG.find((e) => e.dialect.toLowerCase() === lower);
|
|
3742
|
+
}
|
|
3743
|
+
/**
|
|
3744
|
+
* Lookup by short prefix (e.g. `'AR'`). Prefixes are unique in the catalog (asserted by tests).
|
|
3745
|
+
* Case-insensitive — accepts `'AR'`, `'ar'`, `'Ar'`. Catalog stores prefixes uppercase
|
|
3746
|
+
* for display.
|
|
3747
|
+
*/
|
|
3748
|
+
function findLocaleByPrefix(prefix) {
|
|
3749
|
+
if (typeof prefix !== 'string' || prefix.length === 0)
|
|
3750
|
+
return undefined;
|
|
3751
|
+
const lower = prefix.toLowerCase();
|
|
3752
|
+
return FLY_LOCALE_CATALOG.find((e) => e.prefix.toLowerCase() === lower);
|
|
3753
|
+
}
|
|
3754
|
+
/**
|
|
3755
|
+
* Type-narrowing helper for entries flagged RTL.
|
|
3756
|
+
*
|
|
3757
|
+
* Named with the `Entry` suffix to avoid colliding with the existing
|
|
3758
|
+
* `isRtlLocale(lang: string)` from `i18n.service.ts`, which answers a different
|
|
3759
|
+
* question (is the *UI* locale code RTL?). Both helpers coexist in `public-api`.
|
|
3760
|
+
*/
|
|
3761
|
+
function isRtlLocaleEntry(entry) {
|
|
3762
|
+
return entry.isRtl === true;
|
|
3763
|
+
}
|
|
3764
|
+
|
|
2459
3765
|
/*
|
|
2460
3766
|
* @mohamedatia/fly-design-system — Public API
|
|
2461
3767
|
* https://www.npmjs.com/package/@mohamedatia/fly-design-system
|
|
@@ -2526,6 +3832,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
2526
3832
|
* `systemKey` field so platform-managed charts (`'apps'`, `'default-company'`) are
|
|
2527
3833
|
* identifiable across tenants. New `SHARE_ORG_CHART_SYSTEM_KEY_APPS` /
|
|
2528
3834
|
* `SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT` constants exported.
|
|
3835
|
+
* v2.5.0: Agent input contracts (Phase 1 of the `<fly-agent-input>` plan) — additive only.
|
|
3836
|
+
* `AgentCommandRegistry` + `AgentDropRegistry` (singletons via `providedIn: 'root'`,
|
|
3837
|
+
* shared across federation), `flyAgentDraggable` directive, payload-guard helpers
|
|
3838
|
+
* (`validateAgentPayload`, `trimAgentPayload`, `trimAgentString`, `utf8ByteLength`),
|
|
3839
|
+
* and the cross-cutting `FLY_LOCALE_CATALOG` (31 entries, frozen — verbatim port of
|
|
3840
|
+
* `Fly.Shared.Core.LanguageDTO[]`). Three RTL flagged: `he-IL`, `ur-IN`, `ar-AE`.
|
|
3841
|
+
* The catalog's RTL helper is exported as `isRtlLocaleEntry` to avoid colliding with
|
|
3842
|
+
* the existing `isRtlLocale(lang: string)` from `i18n.service.ts`.
|
|
3843
|
+
* No UI consumers yet — Phase 2 wires the registries into the shell input component.
|
|
3844
|
+
* v2.5.1: New `MessageBoxService.showAcknowledged()` entry point returning
|
|
3845
|
+
* `DialogResultWithAcknowledgement` for "Don't ask again"-style flows.
|
|
3846
|
+
* The `dontAskAgain` config (`MessageBoxDontAskAgainConfig`) lives ONLY on
|
|
3847
|
+
* `MessageBoxOptionsWithAcknowledgement` — the base `MessageBoxOptions`
|
|
3848
|
+
* no longer carries it, so the type system enforces "checkbox ⇒ caller
|
|
3849
|
+
* MUST use `showAcknowledged()`" and eliminates the silent-discard path
|
|
3850
|
+
* on `show()`. Existing `show()` callers are unaffected. Case-insensitive
|
|
3851
|
+
* `findLocaleByDialect` / `findLocaleByPrefix` (BCP-47 §2.1.1). See
|
|
3852
|
+
* `.workflow/plans/agent-input-component.md` Phase 2b-1.
|
|
2529
3853
|
* See docs/ExternalAppsGuide/03-frontend-app.md.
|
|
2530
3854
|
*/
|
|
2531
3855
|
/**
|
|
@@ -2544,5 +3868,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
2544
3868
|
* Generated bundle index. Do not edit.
|
|
2545
3869
|
*/
|
|
2546
3870
|
|
|
2547
|
-
export { AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AudienceBuilderComponent, AuthService, ContextMenuComponent, DEFAULT_FLY_THEME_MODE, DialogResult, FLY_THEME_MODE_IDS, FlyBlockUiComponent, FlyFileUploadComponent, FlyImageUploadComponent, FlyThemeService, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, SharePanelComponent, StandaloneWindowManagerService, TranslatePipe, WINDOW_DATA, WindowManagerService, isRtlLocale, normalizeFlyTheme };
|
|
3871
|
+
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentCommandRegistry, AgentDropRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DialogResult, FLY_LOCALE_CATALOG, FLY_THEME_MODE_IDS, FlyAgentDraggableDirective, FlyBlockUiComponent, FlyFileUploadComponent, FlyImageUploadComponent, FlyRemoteRouter, FlyThemeService, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, SharePanelComponent, StandaloneWindowManagerService, TranslatePipe, WINDOW_DATA, WindowManagerService, findLocaleByDialect, findLocaleByPrefix, isRtlLocale, isRtlLocaleEntry, normalizeFlyTheme, trimAgentPayload, trimAgentString, utf8ByteLength, validateAgentPayload };
|
|
2548
3872
|
//# sourceMappingURL=mohamedatia-fly-design-system.mjs.map
|