Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | 4x 4x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x | import { ApplicationRef, Injectable } from '@angular/core'; import { NavigationEnd, Router } from '@angular/router'; import { Store, select } from '@ngrx/store'; import { filter, first, fromEvent, map, switchMap, tap } from 'rxjs'; import { getCurrentLocale } from 'ish-core/store/core/configuration'; import { DomService } from 'ish-core/utils/dom/dom.service'; import { whenTruthy } from 'ish-core/utils/operators'; interface DesignViewMessage { type: | 'dv-clientAction' | 'dv-clientNavigation' | 'dv-clientReady' | 'dv-clientRefresh' | 'dv-clientLocale' | 'dv-clientStable' | 'dv-clientContentIds'; // eslint-disable-next-line @typescript-eslint/no-explicit-any payload?: any; } @Injectable({ providedIn: 'root' }) export class DesignViewService { private allowedHostMessageTypes = ['dv-clientRefresh']; constructor( private router: Router, private appRef: ApplicationRef, private domService: DomService, private store: Store ) { this.init(); } /** * Send a message to the host window. * Send the message to any host since the PWA is not supposed to know a fixed IAP URL (we are not sending secrets). * * @param message The message to send to the host (including type and payload) */ messageToHost(message: DesignViewMessage) { window.parent.postMessage(message, '*'); } /** * Start method that sets up Design View communication. * Needs to be called *once* for the whole application. */ private init() { if (!this.shouldInit()) { return; } this.listenToHostMessages(); this.listenToApplication(); // tell the host client is ready this.messageToHost({ type: 'dv-clientReady' }); } /** * Decides whether to init the Design View capabilities or not. * Is used by the init method, so it will only initialize when * (1) there is a window (i.e. the application does not run in SSR/Universal) * (2) application does not run on top level window (i.e. it runs in the Design View iframe) */ private shouldInit() { // eslint-disable-next-line @typescript-eslint/prefer-optional-chain return typeof window !== 'undefined' && window.parent && window.parent !== window; } /** * Subscribe to messages from the host window. * Incoming messages are filtered using `allowedHostMessageTypes` * Should only be called *once* during initialization. */ private listenToHostMessages() { fromEvent<MessageEvent>(window, 'message') .pipe( filter(e => e.data.hasOwnProperty('type') && this.allowedHostMessageTypes.includes(e.data.type)), map(message => message.data) ) .subscribe(message => this.handleHostMessage(message)); } /** * Listen to events throughout the application and send message to host when * (1) route has changed (`dv-clientNavigation`), * (2) application is stable, i.e. all async tasks have been completed (`dv-clientStable`) or * (3) content include has been reloaded (`dv-clientStable`). * * Should only be called *once* during initialization. */ private listenToApplication() { const navigation$ = this.router.events.pipe(filter<NavigationEnd>(e => e instanceof NavigationEnd)); const stable$ = this.appRef.isStable.pipe(whenTruthy(), first()); const navigationStable$ = navigation$.pipe(switchMap(() => stable$)); // send `dv-clientNavigation` event for each route change navigation$ .pipe( tap(e => this.messageToHost({ type: 'dv-clientNavigation', payload: { url: e.url } })), switchMap(() => this.appRef.isStable.pipe(whenTruthy(), first())) ) .subscribe(() => { this.sendContentIds(); }); stable$.subscribe(() => { this.applyHierarchyHighlighting(); this.sendContentIds(); }); // send `dv-clientStable` event when application is stable or loading of the content included finished navigationStable$.subscribe(() => { this.messageToHost({ type: 'dv-clientStable' }); this.applyHierarchyHighlighting(); }); // send `dv-clientLocale` event when application is stable and the current application locale was determined stable$ .pipe( switchMap(() => this.store.pipe(select(getCurrentLocale), whenTruthy()).pipe( first() // PWA reloads after each locale change, only one locale is active during runtime ) ) ) .subscribe(locale => this.messageToHost({ type: 'dv-clientLocale', payload: { locale } })); } /** * Handle incoming message from the host window. * Invoked by the event listener in `listenToHostMessages()` when a new message arrives. */ private handleHostMessage(message: DesignViewMessage) { switch (message.type) { case 'dv-clientRefresh': { location.reload(); return; } } } /** * Workaround for the missing Firefox CSS support for :has to highlight * only the last .design-view-wrapper in the .design-view-wrapper hierarchy. */ private applyHierarchyHighlighting() { const designViewWrapper: NodeListOf<HTMLElement> = document.querySelectorAll('.design-view-wrapper'); designViewWrapper.forEach(element => { Iif (!element.querySelector('.design-view-wrapper')) { this.domService.addClass(element, 'last-design-view-wrapper'); } }); } /** * Send IDs of the content artifacts (content includes, content pages, view contexts) to the Design View system. */ private sendContentIds() { const contentIds: { id: string; resource: 'includes' | 'pages' | 'viewcontexts' }[] = []; document.querySelectorAll('[includeid], [pageid], [viewcontextid]').forEach(element => { // transfer only view contexts that have call parameters if (element.getAttribute('viewcontextid')) { const callParams = element.getAttribute('callparametersstring'); Iif (callParams) { contentIds.push({ id: `${element.getAttribute('viewcontextid')}/entrypoint?${callParams}`, resource: 'viewcontexts', }); } } else { contentIds.push( element.getAttribute('includeid') ? { id: element.getAttribute('includeid'), resource: 'includes' } : { id: element.getAttribute('pageid'), resource: 'pages' } ); } }); this.messageToHost({ type: 'dv-clientContentIds', payload: { contentIds } }); } } |