@platform-mesh/portal-ui-lib 0.49.42 → 0.50.0
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/assets/646.js +1 -1
- package/assets/platform-mesh-portal-ui-wc.js +21 -21
- package/assets/ui5-webcomponents-fiori-sap-horizon-parameters-bundle.js +1 -1
- package/assets/ui5-webcomponents-fiori-sap-horizon_auto-parameters-bundle.js +1 -1
- package/fesm2022/platform-mesh-portal-ui-lib-portal-options.mjs +510 -2
- package/fesm2022/platform-mesh-portal-ui-lib-portal-options.mjs.map +1 -1
- package/package.json +1 -1
- package/types/platform-mesh-portal-ui-lib-portal-options.d.ts +15 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Injectable, DestroyRef } from '@angular/core';
|
|
2
|
+
import { inject, Injectable, DestroyRef, signal, ViewChild, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent } from '@angular/core';
|
|
3
3
|
import { I18nService, EntityType, AuthService, LuigiCoreService, ConfigService, EnvConfigService, LocalStorageKeys } from '@openmfp/portal-ui-lib';
|
|
4
4
|
import '@ui5/webcomponents/dist/Breadcrumbs.js';
|
|
5
5
|
import '@ui5/webcomponents/dist/BreadcrumbsItem.js';
|
|
@@ -10,6 +10,8 @@ import { isNamespacedResource, generateGraphQLFields, mergeListWithSubscriptionR
|
|
|
10
10
|
import '@ui5/webcomponents/dist/ComboBox.js';
|
|
11
11
|
import { Subject, defer, of, firstValueFrom } from 'rxjs';
|
|
12
12
|
import { shareReplay, retry, takeUntil, switchMap, startWith, scan, tap, catchError } from 'rxjs/operators';
|
|
13
|
+
import { CommonModule } from '@angular/common';
|
|
14
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
13
15
|
|
|
14
16
|
class CustomGlobalNodesServiceImpl {
|
|
15
17
|
constructor() {
|
|
@@ -334,6 +336,485 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
334
336
|
args: [{ providedIn: 'root' }]
|
|
335
337
|
}] });
|
|
336
338
|
|
|
339
|
+
const OPEN_PERSISTENT_PANEL_MESSAGE = 'portal.open-provider-panel';
|
|
340
|
+
const PROVIDER_PANEL_MESSAGE = {
|
|
341
|
+
close: 'portal.provider-panel.close',
|
|
342
|
+
closeFailed: 'portal.provider-panel.close-failed',
|
|
343
|
+
closed: 'portal.provider-panel.closed',
|
|
344
|
+
context: 'portal.provider-panel.context',
|
|
345
|
+
ready: 'portal.provider-panel.ready',
|
|
346
|
+
requestClose: 'portal.provider-panel.request-close',
|
|
347
|
+
};
|
|
348
|
+
function mergePersistentPanelTargets(current, update) {
|
|
349
|
+
const organizationChanged = current.organization !== undefined &&
|
|
350
|
+
update.organization !== undefined &&
|
|
351
|
+
current.organization !== update.organization;
|
|
352
|
+
const accountChanged = current.account !== undefined &&
|
|
353
|
+
update.account !== undefined &&
|
|
354
|
+
current.account !== update.account;
|
|
355
|
+
if (organizationChanged || accountChanged) {
|
|
356
|
+
return structuredClone(update);
|
|
357
|
+
}
|
|
358
|
+
// Provider nodes normally contain only panel capability metadata. Keep the
|
|
359
|
+
// target tracked by navigation and overlay any bounded scope that the
|
|
360
|
+
// requesting node does carry.
|
|
361
|
+
return structuredClone({ ...current, ...update });
|
|
362
|
+
}
|
|
363
|
+
const panelIdPattern = /^[a-z0-9](?:[a-z0-9.-]{0,62}[a-z0-9])?$/;
|
|
364
|
+
const workspaceSegmentPattern = /^[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?$/;
|
|
365
|
+
function parsePersistentPanelConfig(message, providerNode, portalOrigin) {
|
|
366
|
+
if (message.id !== OPEN_PERSISTENT_PANEL_MESSAGE) {
|
|
367
|
+
throw new Error('Persistent panel request is invalid');
|
|
368
|
+
}
|
|
369
|
+
const capability = recordValue(providerNode?.context?.persistentPanel);
|
|
370
|
+
const id = boundedString(capability['id'], 64);
|
|
371
|
+
const title = boundedString(capability['title'], 80);
|
|
372
|
+
if (!id || !panelIdPattern.test(id)) {
|
|
373
|
+
throw new Error('Registered persistent panel id is invalid');
|
|
374
|
+
}
|
|
375
|
+
if (!title) {
|
|
376
|
+
throw new Error('Registered persistent panel title is invalid');
|
|
377
|
+
}
|
|
378
|
+
const registeredViewUrl = boundedString(providerNode?.viewUrl, 2048);
|
|
379
|
+
if (!registeredViewUrl) {
|
|
380
|
+
throw new Error('Registered provider UI URL is missing');
|
|
381
|
+
}
|
|
382
|
+
const portalURL = new URL(portalOrigin);
|
|
383
|
+
const url = new URL(registeredViewUrl, portalURL);
|
|
384
|
+
if (url.origin === portalURL.origin ||
|
|
385
|
+
!allowedPanelURL(url, portalURL) ||
|
|
386
|
+
url.username ||
|
|
387
|
+
url.password) {
|
|
388
|
+
throw new Error('Registered provider UI URL is invalid');
|
|
389
|
+
}
|
|
390
|
+
return { id, title, url: url.toString(), origin: url.origin };
|
|
391
|
+
}
|
|
392
|
+
function allowedPanelURL(url, portalURL) {
|
|
393
|
+
if (url.protocol === 'https:') {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
return (portalURL.protocol === 'http:' &&
|
|
397
|
+
url.protocol === 'http:' &&
|
|
398
|
+
['localhost', '127.0.0.1', '[::1]'].includes(url.hostname));
|
|
399
|
+
}
|
|
400
|
+
function persistentPanelTarget(globalContext, nodeContext) {
|
|
401
|
+
const context = { ...globalContext, ...(nodeContext ?? {}) };
|
|
402
|
+
const entityContext = recordValue(context['entityContext']);
|
|
403
|
+
const accountContext = recordValue(entityContext['account']);
|
|
404
|
+
const resourceDefinition = recordValue(context['resourceDefinition']);
|
|
405
|
+
const organization = boundedString(context['organization'], 253);
|
|
406
|
+
const organizationId = boundedString(context['organizationId'], 512);
|
|
407
|
+
const rawAccountPath = boundedString(context['accountPath'], 1024);
|
|
408
|
+
const account = boundedString(accountContext['id'], 253) ||
|
|
409
|
+
boundedString(context['accountId'], 253) ||
|
|
410
|
+
boundedString(context['core_platform-mesh_io_accountId'], 253) ||
|
|
411
|
+
accountFromPath(rawAccountPath, organization) ||
|
|
412
|
+
accountFromEntity(context);
|
|
413
|
+
// Organization-only scope is valid, but descendants without an account are
|
|
414
|
+
// not. Organization context can carry a kcpPath, so fail closed before
|
|
415
|
+
// projecting workspace fields.
|
|
416
|
+
if (!account) {
|
|
417
|
+
return compact({ organization, organizationId });
|
|
418
|
+
}
|
|
419
|
+
const accountPath = validatedAccountPath(rawAccountPath, organization, account);
|
|
420
|
+
const workspacePath = validatedWorkspacePath(context['workspacePath'], organization, account) ||
|
|
421
|
+
validatedWorkspacePath(context['kcpPath'], organization, account) ||
|
|
422
|
+
validatedWorkspacePath(rawAccountPath, organization, account) ||
|
|
423
|
+
canonicalAccountPath(organization, account);
|
|
424
|
+
const namespace = boundedString(context['namespaceId'], 253) ||
|
|
425
|
+
boundedString(resourceDefinition['namespace'], 253);
|
|
426
|
+
const resource = compact({
|
|
427
|
+
group: boundedString(resourceDefinition['group'], 253) ||
|
|
428
|
+
boundedString(resourceDefinition['apiGroup'], 253),
|
|
429
|
+
version: boundedString(resourceDefinition['version'], 63),
|
|
430
|
+
kind: boundedString(resourceDefinition['kind'], 253) ||
|
|
431
|
+
boundedString(context['entityKind'], 253),
|
|
432
|
+
name: boundedString(context['entityName'], 253),
|
|
433
|
+
});
|
|
434
|
+
return compact({
|
|
435
|
+
organization,
|
|
436
|
+
organizationId,
|
|
437
|
+
account,
|
|
438
|
+
accountPath,
|
|
439
|
+
workspacePath,
|
|
440
|
+
namespace,
|
|
441
|
+
resource: Object.keys(resource).length > 0 ? resource : undefined,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function accountFromPath(accountPath, organization) {
|
|
445
|
+
if (!accountPath) {
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
if (workspaceSegmentPattern.test(accountPath)) {
|
|
449
|
+
return accountPath;
|
|
450
|
+
}
|
|
451
|
+
const segments = accountPath.split(':');
|
|
452
|
+
if (segments.length < 4 ||
|
|
453
|
+
segments[0] !== 'root' ||
|
|
454
|
+
segments[1] !== 'orgs' ||
|
|
455
|
+
segments.some((segment) => !workspaceSegmentPattern.test(segment)) ||
|
|
456
|
+
(organization !== undefined && segments[2] !== organization)) {
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
return boundedString(segments.at(-1), 253);
|
|
460
|
+
}
|
|
461
|
+
function accountFromEntity(context) {
|
|
462
|
+
if (boundedString(context['entityKind'], 253) !== 'Account') {
|
|
463
|
+
return undefined;
|
|
464
|
+
}
|
|
465
|
+
return boundedString(context['entityName'], 253);
|
|
466
|
+
}
|
|
467
|
+
function canonicalWorkspacePath(accountPath) {
|
|
468
|
+
if (!accountPath?.startsWith('root:')) {
|
|
469
|
+
return undefined;
|
|
470
|
+
}
|
|
471
|
+
const segments = accountPath.split(':');
|
|
472
|
+
if (segments.length < 3 ||
|
|
473
|
+
segments.some((segment) => !workspaceSegmentPattern.test(segment))) {
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|
|
476
|
+
return accountPath;
|
|
477
|
+
}
|
|
478
|
+
function validatedWorkspacePath(value, organization, account) {
|
|
479
|
+
if (!organization || !account) {
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
482
|
+
const path = canonicalWorkspacePath(boundedString(value, 1024));
|
|
483
|
+
const segments = path?.split(':');
|
|
484
|
+
if (!segments ||
|
|
485
|
+
segments.length < 4 ||
|
|
486
|
+
segments[0] !== 'root' ||
|
|
487
|
+
segments[1] !== 'orgs' ||
|
|
488
|
+
segments[2] !== organization ||
|
|
489
|
+
segments.at(-1) !== account) {
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
return path;
|
|
493
|
+
}
|
|
494
|
+
function validatedAccountPath(value, organization, account) {
|
|
495
|
+
if (!value) {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
if (workspaceSegmentPattern.test(value)) {
|
|
499
|
+
return value === account ? value : undefined;
|
|
500
|
+
}
|
|
501
|
+
return validatedWorkspacePath(value, organization, account);
|
|
502
|
+
}
|
|
503
|
+
function canonicalAccountPath(organization, account) {
|
|
504
|
+
if (!organization ||
|
|
505
|
+
!account ||
|
|
506
|
+
!workspaceSegmentPattern.test(organization) ||
|
|
507
|
+
!workspaceSegmentPattern.test(account)) {
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
return `root:orgs:${organization}:${account}`;
|
|
511
|
+
}
|
|
512
|
+
function compact(value) {
|
|
513
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
514
|
+
}
|
|
515
|
+
function boundedString(value, maxLength) {
|
|
516
|
+
if (typeof value !== 'string') {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
const trimmed = value.trim();
|
|
520
|
+
if (!trimmed || trimmed.length > maxLength || trimmed.includes('\u0000')) {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
return trimmed;
|
|
524
|
+
}
|
|
525
|
+
function isRecord$1(value) {
|
|
526
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
527
|
+
}
|
|
528
|
+
function recordValue(value) {
|
|
529
|
+
return isRecord$1(value) ? value : {};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
class PersistentPanelComponent {
|
|
533
|
+
constructor() {
|
|
534
|
+
this.state = signal('hidden', ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
535
|
+
this.title = signal('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
536
|
+
this.source = signal(null, ...(ngDevMode ? [{ debugName: "source" }] : /* istanbul ignore next */ []));
|
|
537
|
+
this.closing = signal(false, ...(ngDevMode ? [{ debugName: "closing" }] : /* istanbul ignore next */ []));
|
|
538
|
+
this.closeError = signal('', ...(ngDevMode ? [{ debugName: "closeError" }] : /* istanbul ignore next */ []));
|
|
539
|
+
this.sanitizer = inject(DomSanitizer);
|
|
540
|
+
this.config = null;
|
|
541
|
+
this.target = {};
|
|
542
|
+
this.sequence = 0;
|
|
543
|
+
this.closeSequence = 0;
|
|
544
|
+
this.closeTimeout = null;
|
|
545
|
+
this.closeRequestIdSent = null;
|
|
546
|
+
this.destroyed = false;
|
|
547
|
+
this.ready = false;
|
|
548
|
+
this.returnFocusElement = null;
|
|
549
|
+
this.onMessage = (event) => {
|
|
550
|
+
const frame = this.panelFrame?.nativeElement;
|
|
551
|
+
if (!frame ||
|
|
552
|
+
event.source !== frame.contentWindow ||
|
|
553
|
+
event.origin !== this.config?.origin ||
|
|
554
|
+
!isRecord(event.data)) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (event.data['type'] === PROVIDER_PANEL_MESSAGE.ready) {
|
|
558
|
+
this.ready = true;
|
|
559
|
+
if (this.closing()) {
|
|
560
|
+
this.requestCloseIfReady(this.closeSequence);
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
this.publishTarget();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (event.data['type'] === PROVIDER_PANEL_MESSAGE.requestClose) {
|
|
567
|
+
this.close();
|
|
568
|
+
}
|
|
569
|
+
if (event.data['type'] === PROVIDER_PANEL_MESSAGE.closeFailed &&
|
|
570
|
+
typeof event.data['requestId'] === 'number' &&
|
|
571
|
+
event.data['requestId'] === this.closeRequestIdSent) {
|
|
572
|
+
this.failClose(event.data['requestId']);
|
|
573
|
+
}
|
|
574
|
+
if (event.data['type'] === PROVIDER_PANEL_MESSAGE.closed &&
|
|
575
|
+
typeof event.data['requestId'] === 'number') {
|
|
576
|
+
this.finishClose(event.data['requestId']);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
window.addEventListener('message', this.onMessage);
|
|
580
|
+
}
|
|
581
|
+
ngOnDestroy() {
|
|
582
|
+
this.destroyed = true;
|
|
583
|
+
window.removeEventListener('message', this.onMessage);
|
|
584
|
+
this.clearCloseTimeout();
|
|
585
|
+
}
|
|
586
|
+
open(config, target) {
|
|
587
|
+
if (this.closing()) {
|
|
588
|
+
this.closeSequence += 1;
|
|
589
|
+
}
|
|
590
|
+
this.clearCloseTimeout();
|
|
591
|
+
this.closeRequestIdSent = null;
|
|
592
|
+
this.closing.set(false);
|
|
593
|
+
this.closeError.set('');
|
|
594
|
+
const sourceChanged = this.config?.url !== config.url;
|
|
595
|
+
if (!this.source()) {
|
|
596
|
+
this.returnFocusElement =
|
|
597
|
+
document.activeElement instanceof HTMLElement
|
|
598
|
+
? document.activeElement
|
|
599
|
+
: null;
|
|
600
|
+
}
|
|
601
|
+
if (sourceChanged) {
|
|
602
|
+
this.ready = false;
|
|
603
|
+
}
|
|
604
|
+
this.config = config;
|
|
605
|
+
this.target = structuredClone(target);
|
|
606
|
+
this.title.set(config.title);
|
|
607
|
+
if (sourceChanged || !this.source()) {
|
|
608
|
+
this.source.set(this.sanitizer.bypassSecurityTrustResourceUrl(config.url));
|
|
609
|
+
}
|
|
610
|
+
this.state.set('expanded');
|
|
611
|
+
queueMicrotask(() => {
|
|
612
|
+
if (this.destroyed) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
this.publishTarget();
|
|
616
|
+
this.focusPanel();
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
updateTarget(target) {
|
|
620
|
+
this.target = structuredClone(target);
|
|
621
|
+
this.publishTarget();
|
|
622
|
+
}
|
|
623
|
+
collapse() {
|
|
624
|
+
this.state.set('hidden');
|
|
625
|
+
queueMicrotask(() => {
|
|
626
|
+
if (this.destroyed) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
this.reopenButton?.nativeElement.focus();
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
expand() {
|
|
633
|
+
this.state.set('expanded');
|
|
634
|
+
queueMicrotask(() => {
|
|
635
|
+
if (this.destroyed) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
this.focusPanel();
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
toggleSize() {
|
|
642
|
+
this.state.update((state) => state === 'maximized' ? 'expanded' : 'maximized');
|
|
643
|
+
}
|
|
644
|
+
close() {
|
|
645
|
+
if (!this.source() || this.closing()) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
this.closeSequence += 1;
|
|
649
|
+
const requestId = this.closeSequence;
|
|
650
|
+
this.closeRequestIdSent = null;
|
|
651
|
+
this.closing.set(true);
|
|
652
|
+
this.closeError.set('');
|
|
653
|
+
this.state.set('hidden');
|
|
654
|
+
this.focusReturnTarget();
|
|
655
|
+
this.requestCloseIfReady(requestId);
|
|
656
|
+
this.closeTimeout = window.setTimeout(() => {
|
|
657
|
+
this.failClose(requestId);
|
|
658
|
+
}, 30_000);
|
|
659
|
+
}
|
|
660
|
+
failClose(requestId) {
|
|
661
|
+
if (!this.closing() || requestId !== this.closeSequence) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
this.clearCloseTimeout();
|
|
665
|
+
this.closeRequestIdSent = null;
|
|
666
|
+
this.closing.set(false);
|
|
667
|
+
this.closeError.set('Provider cleanup is incomplete. Try closing again.');
|
|
668
|
+
this.state.set('expanded');
|
|
669
|
+
queueMicrotask(() => {
|
|
670
|
+
if (this.destroyed) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
this.focusPanel();
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
finishClose(requestId) {
|
|
677
|
+
if (!this.closing() ||
|
|
678
|
+
requestId !== this.closeSequence ||
|
|
679
|
+
this.closeRequestIdSent !== requestId) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
this.clearCloseTimeout();
|
|
683
|
+
this.closeRequestIdSent = null;
|
|
684
|
+
this.closing.set(false);
|
|
685
|
+
this.closeError.set('');
|
|
686
|
+
this.config = null;
|
|
687
|
+
this.ready = false;
|
|
688
|
+
this.source.set(null);
|
|
689
|
+
this.title.set('');
|
|
690
|
+
this.state.set('hidden');
|
|
691
|
+
this.focusReturnTarget();
|
|
692
|
+
this.returnFocusElement = null;
|
|
693
|
+
}
|
|
694
|
+
clearCloseTimeout() {
|
|
695
|
+
if (this.closeTimeout !== null) {
|
|
696
|
+
window.clearTimeout(this.closeTimeout);
|
|
697
|
+
this.closeTimeout = null;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
frameLoaded() {
|
|
701
|
+
this.publishTarget();
|
|
702
|
+
}
|
|
703
|
+
publishTarget() {
|
|
704
|
+
if (!this.config) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
this.sequence += 1;
|
|
708
|
+
this.post({
|
|
709
|
+
type: PROVIDER_PANEL_MESSAGE.context,
|
|
710
|
+
panelId: this.config.id,
|
|
711
|
+
sequence: this.sequence,
|
|
712
|
+
target: this.target,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
requestCloseIfReady(requestId) {
|
|
716
|
+
if (!this.ready ||
|
|
717
|
+
!this.closing() ||
|
|
718
|
+
requestId !== this.closeSequence ||
|
|
719
|
+
this.closeRequestIdSent === requestId) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
this.closeRequestIdSent = requestId;
|
|
723
|
+
this.post({ type: PROVIDER_PANEL_MESSAGE.close, requestId });
|
|
724
|
+
}
|
|
725
|
+
focusPanel() {
|
|
726
|
+
this.panelFrame?.nativeElement.focus?.();
|
|
727
|
+
}
|
|
728
|
+
focusReturnTarget() {
|
|
729
|
+
if (this.returnFocusElement?.isConnected) {
|
|
730
|
+
this.returnFocusElement.focus();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
post(message) {
|
|
734
|
+
if (!this.config) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
this.panelFrame?.nativeElement.contentWindow?.postMessage(message, this.config.origin);
|
|
738
|
+
}
|
|
739
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PersistentPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
740
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: PersistentPanelComponent, isStandalone: true, selector: "pm-persistent-panel", viewQueries: [{ propertyName: "panelFrame", first: true, predicate: ["panelFrame"], descendants: true }, { propertyName: "reopenButton", first: true, predicate: ["reopenButton"], descendants: true }], ngImport: i0, template: "<section\n class=\"persistent-panel\"\n role=\"complementary\"\n aria-labelledby=\"persistent-panel-title\"\n [attr.aria-hidden]=\"state() === 'hidden'\"\n [class.maximized]=\"state() === 'maximized'\"\n [class.open]=\"state() !== 'hidden'\"\n>\n <header class=\"persistent-panel-header\">\n <div>\n <h2 id=\"persistent-panel-title\">{{ title() }}</h2>\n @if (closeError()) {\n <p role=\"alert\">{{ closeError() }}</p>\n }\n </div>\n <div class=\"persistent-panel-actions\">\n <button\n aria-label=\"Toggle panel size\"\n [attr.aria-pressed]=\"state() === 'maximized'\"\n title=\"Toggle panel size\"\n type=\"button\"\n (click)=\"toggleSize()\"\n >\n @if (state() === 'maximized') { \u25F2 } @else { \u25F1 }\n </button>\n <button\n aria-label=\"Collapse panel\"\n title=\"Collapse panel\"\n type=\"button\"\n (click)=\"collapse()\"\n >\n \u2014\n </button>\n <button\n aria-label=\"Close panel\"\n title=\"Close panel\"\n type=\"button\"\n (click)=\"close()\"\n >\n \u00D7\n </button>\n </div>\n </header>\n @if (source()) {\n <iframe\n #panelFrame\n class=\"persistent-panel-frame\"\n referrerpolicy=\"no-referrer\"\n sandbox=\"allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts\"\n [src]=\"source()\"\n [title]=\"title()\"\n (load)=\"frameLoaded()\"\n ></iframe>\n }\n</section>\n\n@if (state() === 'hidden' && source() && !closing()) {\n<button\n #reopenButton\n class=\"persistent-panel-reopen\"\n aria-label=\"Open provider panel\"\n type=\"button\"\n (click)=\"expand()\"\n>\n {{ title() }}\n</button>\n}\n", styles: [":host{--pm-panel-header-height: 3rem;font-family:var(--sapFontFamily, \"72\", Arial, sans-serif)}.persistent-panel{position:fixed;top:var(--luigi__shellbar--height, 3.25rem);right:0;bottom:0;z-index:1000;display:none;width:min(34rem,100vw);background:var(--sapBackgroundColor, #fff);border-left:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9);box-shadow:-.25rem 0 1rem #0000001f}.persistent-panel.open{display:grid;grid-template-rows:var(--pm-panel-header-height) 1fr}.persistent-panel.maximized{left:4.25rem;width:auto}.persistent-panel-header{display:flex;align-items:center;justify-content:space-between;padding:0 .75rem 0 1rem;color:var(--sapShell_TextColor, #1d2d3e);background:var(--sapShell_Background, #fff);border-bottom:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9)}.persistent-panel-header h2{overflow:hidden;margin:0;font-size:1rem;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.persistent-panel-header p{margin:0;color:var(--sapNegativeTextColor, #aa0808);font-size:.75rem}.persistent-panel-actions{display:flex;gap:.25rem}.persistent-panel-actions button,.persistent-panel-reopen{min-width:2rem;min-height:2rem;color:var(--sapButton_TextColor, #0064d9);background:transparent;border:0;border-radius:.25rem;cursor:pointer}.persistent-panel-actions button:hover,.persistent-panel-reopen:hover{background:var(--sapButton_Lite_Hover_Background, #eaecee)}.persistent-panel-frame{width:100%;height:100%;border:0}.persistent-panel-reopen{position:fixed;right:1rem;bottom:1rem;z-index:1000;padding:.5rem .75rem;color:var(--sapButton_Emphasized_TextColor, #fff);background:var(--sapButton_Emphasized_Background, #0070f2);box-shadow:0 .25rem .75rem #0003}@media(max-width:600px){.persistent-panel,.persistent-panel.maximized{left:0;width:100vw}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
741
|
+
}
|
|
742
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PersistentPanelComponent, decorators: [{
|
|
743
|
+
type: Component,
|
|
744
|
+
args: [{ selector: 'pm-persistent-panel', imports: [CommonModule], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<section\n class=\"persistent-panel\"\n role=\"complementary\"\n aria-labelledby=\"persistent-panel-title\"\n [attr.aria-hidden]=\"state() === 'hidden'\"\n [class.maximized]=\"state() === 'maximized'\"\n [class.open]=\"state() !== 'hidden'\"\n>\n <header class=\"persistent-panel-header\">\n <div>\n <h2 id=\"persistent-panel-title\">{{ title() }}</h2>\n @if (closeError()) {\n <p role=\"alert\">{{ closeError() }}</p>\n }\n </div>\n <div class=\"persistent-panel-actions\">\n <button\n aria-label=\"Toggle panel size\"\n [attr.aria-pressed]=\"state() === 'maximized'\"\n title=\"Toggle panel size\"\n type=\"button\"\n (click)=\"toggleSize()\"\n >\n @if (state() === 'maximized') { \u25F2 } @else { \u25F1 }\n </button>\n <button\n aria-label=\"Collapse panel\"\n title=\"Collapse panel\"\n type=\"button\"\n (click)=\"collapse()\"\n >\n \u2014\n </button>\n <button\n aria-label=\"Close panel\"\n title=\"Close panel\"\n type=\"button\"\n (click)=\"close()\"\n >\n \u00D7\n </button>\n </div>\n </header>\n @if (source()) {\n <iframe\n #panelFrame\n class=\"persistent-panel-frame\"\n referrerpolicy=\"no-referrer\"\n sandbox=\"allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts\"\n [src]=\"source()\"\n [title]=\"title()\"\n (load)=\"frameLoaded()\"\n ></iframe>\n }\n</section>\n\n@if (state() === 'hidden' && source() && !closing()) {\n<button\n #reopenButton\n class=\"persistent-panel-reopen\"\n aria-label=\"Open provider panel\"\n type=\"button\"\n (click)=\"expand()\"\n>\n {{ title() }}\n</button>\n}\n", styles: [":host{--pm-panel-header-height: 3rem;font-family:var(--sapFontFamily, \"72\", Arial, sans-serif)}.persistent-panel{position:fixed;top:var(--luigi__shellbar--height, 3.25rem);right:0;bottom:0;z-index:1000;display:none;width:min(34rem,100vw);background:var(--sapBackgroundColor, #fff);border-left:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9);box-shadow:-.25rem 0 1rem #0000001f}.persistent-panel.open{display:grid;grid-template-rows:var(--pm-panel-header-height) 1fr}.persistent-panel.maximized{left:4.25rem;width:auto}.persistent-panel-header{display:flex;align-items:center;justify-content:space-between;padding:0 .75rem 0 1rem;color:var(--sapShell_TextColor, #1d2d3e);background:var(--sapShell_Background, #fff);border-bottom:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9)}.persistent-panel-header h2{overflow:hidden;margin:0;font-size:1rem;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.persistent-panel-header p{margin:0;color:var(--sapNegativeTextColor, #aa0808);font-size:.75rem}.persistent-panel-actions{display:flex;gap:.25rem}.persistent-panel-actions button,.persistent-panel-reopen{min-width:2rem;min-height:2rem;color:var(--sapButton_TextColor, #0064d9);background:transparent;border:0;border-radius:.25rem;cursor:pointer}.persistent-panel-actions button:hover,.persistent-panel-reopen:hover{background:var(--sapButton_Lite_Hover_Background, #eaecee)}.persistent-panel-frame{width:100%;height:100%;border:0}.persistent-panel-reopen{position:fixed;right:1rem;bottom:1rem;z-index:1000;padding:.5rem .75rem;color:var(--sapButton_Emphasized_TextColor, #fff);background:var(--sapButton_Emphasized_Background, #0070f2);box-shadow:0 .25rem .75rem #0003}@media(max-width:600px){.persistent-panel,.persistent-panel.maximized{left:0;width:100vw}}\n"] }]
|
|
745
|
+
}], ctorParameters: () => [], propDecorators: { panelFrame: [{
|
|
746
|
+
type: ViewChild,
|
|
747
|
+
args: ['panelFrame']
|
|
748
|
+
}], reopenButton: [{
|
|
749
|
+
type: ViewChild,
|
|
750
|
+
args: ['reopenButton']
|
|
751
|
+
}] } });
|
|
752
|
+
function isRecord(value) {
|
|
753
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
class PersistentPanelService {
|
|
757
|
+
constructor() {
|
|
758
|
+
this.appRef = inject(ApplicationRef);
|
|
759
|
+
this.environmentInjector = inject(EnvironmentInjector);
|
|
760
|
+
this.panelRef = null;
|
|
761
|
+
this.target = {};
|
|
762
|
+
this.targetUpdateSequence = 0;
|
|
763
|
+
}
|
|
764
|
+
open(config, target) {
|
|
765
|
+
this.target = target;
|
|
766
|
+
if (!this.panelRef) {
|
|
767
|
+
this.panelRef = createComponent(PersistentPanelComponent, {
|
|
768
|
+
environmentInjector: this.environmentInjector,
|
|
769
|
+
});
|
|
770
|
+
this.appRef.attachView(this.panelRef.hostView);
|
|
771
|
+
document.body.appendChild(this.panelRef.location.nativeElement);
|
|
772
|
+
}
|
|
773
|
+
this.panelRef.instance.open(config, target);
|
|
774
|
+
// createComponent() is outside a template tree, so it has no initial
|
|
775
|
+
// render until change detection is run explicitly. Subsequent signal
|
|
776
|
+
// updates are scheduled normally once the view has been initialized.
|
|
777
|
+
this.panelRef.changeDetectorRef.detectChanges();
|
|
778
|
+
}
|
|
779
|
+
beginTargetUpdate() {
|
|
780
|
+
const targetUpdateSequence = ++this.targetUpdateSequence;
|
|
781
|
+
return (context) => {
|
|
782
|
+
if (targetUpdateSequence !== this.targetUpdateSequence) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
this.updateTarget(context);
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
updateTarget(context) {
|
|
789
|
+
const target = persistentPanelTarget({}, context);
|
|
790
|
+
this.target = target;
|
|
791
|
+
this.panelRef?.instance.updateTarget(target);
|
|
792
|
+
}
|
|
793
|
+
currentTarget() {
|
|
794
|
+
return structuredClone(this.target);
|
|
795
|
+
}
|
|
796
|
+
destroy() {
|
|
797
|
+
this.targetUpdateSequence += 1;
|
|
798
|
+
if (this.panelRef) {
|
|
799
|
+
const host = this.panelRef.location.nativeElement;
|
|
800
|
+
this.appRef.detachView(this.panelRef.hostView);
|
|
801
|
+
this.panelRef.destroy();
|
|
802
|
+
host.remove();
|
|
803
|
+
this.panelRef = null;
|
|
804
|
+
}
|
|
805
|
+
this.target = {};
|
|
806
|
+
}
|
|
807
|
+
ngOnDestroy() {
|
|
808
|
+
this.destroy();
|
|
809
|
+
}
|
|
810
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PersistentPanelService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
811
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PersistentPanelService, providedIn: 'root' }); }
|
|
812
|
+
}
|
|
813
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PersistentPanelService, decorators: [{
|
|
814
|
+
type: Injectable,
|
|
815
|
+
args: [{ providedIn: 'root' }]
|
|
816
|
+
}] });
|
|
817
|
+
|
|
337
818
|
function collectAccountNamesFromNodeHierarchy(node) {
|
|
338
819
|
const accountNames = [];
|
|
339
820
|
let currentNode = node;
|
|
@@ -400,8 +881,10 @@ class NodeChangeHookConfigServiceImpl {
|
|
|
400
881
|
constructor() {
|
|
401
882
|
this.luigiCoreService = inject(LuigiCoreService);
|
|
402
883
|
this.crdGatewayKcpPatchResolver = inject(CrdGatewayKcpPatchResolver);
|
|
884
|
+
this.persistentPanelService = inject(PersistentPanelService);
|
|
403
885
|
}
|
|
404
886
|
async nodeChangeHook(prevNode, nextNode, currentContext) {
|
|
887
|
+
const updatePersistentPanelTarget = this.persistentPanelService.beginTargetUpdate();
|
|
405
888
|
if (nextNode.initialRoute &&
|
|
406
889
|
nextNode.virtualTree &&
|
|
407
890
|
!nextNode._virtualTree) {
|
|
@@ -409,6 +892,10 @@ class NodeChangeHookConfigServiceImpl {
|
|
|
409
892
|
}
|
|
410
893
|
this.accumulatePortalPermissions(prevNode, nextNode, currentContext);
|
|
411
894
|
await this.crdGatewayKcpPatchResolver.resolveCrdGatewayKcpPath(nextNode);
|
|
895
|
+
updatePersistentPanelTarget({
|
|
896
|
+
...currentContext,
|
|
897
|
+
...(nextNode.context ?? {}),
|
|
898
|
+
});
|
|
412
899
|
}
|
|
413
900
|
accumulatePortalPermissions(prevNode, nextNode, currentContext) {
|
|
414
901
|
const portalPermissions = prevNode?.context?.portalPermissions ??
|
|
@@ -561,9 +1048,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
561
1048
|
args: [{ providedIn: 'root' }]
|
|
562
1049
|
}] });
|
|
563
1050
|
|
|
1051
|
+
class OpenPersistentPanelListener {
|
|
1052
|
+
constructor() {
|
|
1053
|
+
this.luigiCoreService = inject(LuigiCoreService);
|
|
1054
|
+
this.persistentPanelService = inject(PersistentPanelService);
|
|
1055
|
+
}
|
|
1056
|
+
messageId() {
|
|
1057
|
+
return OPEN_PERSISTENT_PANEL_MESSAGE;
|
|
1058
|
+
}
|
|
1059
|
+
onCustomMessageReceived(customMessage, _microFrontend, microFrontendNode) {
|
|
1060
|
+
const config = parsePersistentPanelConfig(customMessage, microFrontendNode, new URL(document.baseURI).origin);
|
|
1061
|
+
const target = mergePersistentPanelTargets(this.persistentPanelService.currentTarget(), persistentPanelTarget(this.luigiCoreService.getGlobalContext(), microFrontendNode?.context));
|
|
1062
|
+
this.persistentPanelService.open(config, target);
|
|
1063
|
+
}
|
|
1064
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: OpenPersistentPanelListener, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1065
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: OpenPersistentPanelListener, providedIn: 'root' }); }
|
|
1066
|
+
}
|
|
1067
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: OpenPersistentPanelListener, decorators: [{
|
|
1068
|
+
type: Injectable,
|
|
1069
|
+
args: [{ providedIn: 'root' }]
|
|
1070
|
+
}] });
|
|
1071
|
+
|
|
564
1072
|
/**
|
|
565
1073
|
* Generated bundle index. Do not edit.
|
|
566
1074
|
*/
|
|
567
1075
|
|
|
568
|
-
export { CustomGlobalNodesServiceImpl, CustomRoutingConfigServiceImpl, HeaderBarConfigServiceImpl, LuigiExtendedGlobalContextConfigServiceImpl, NavigationRedirectStrategyServiceImpl, NodeChangeHookConfigServiceImpl, NodeContextProcessingServiceImpl, UserProfileConfigServiceImpl };
|
|
1076
|
+
export { CustomGlobalNodesServiceImpl, CustomRoutingConfigServiceImpl, HeaderBarConfigServiceImpl, LuigiExtendedGlobalContextConfigServiceImpl, NavigationRedirectStrategyServiceImpl, NodeChangeHookConfigServiceImpl, NodeContextProcessingServiceImpl, OpenPersistentPanelListener, UserProfileConfigServiceImpl };
|
|
569
1077
|
//# sourceMappingURL=platform-mesh-portal-ui-lib-portal-options.mjs.map
|