en:outskirts:mages-peaks:detach-windows
Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| en:outskirts:mages-peaks:detach-windows [2024/04/09 15:56] – created Philippe M | en:outskirts:mages-peaks:detach-windows [2024/10/28 08:00] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Detachable Windows and Intercom ====== | ||
| + | |||
| + | |||
| ===== Goals ===== | ===== Goals ===== | ||
| As demonstrated in the laelith-adventure project, for the Editor, the goal is to enable a (Svelte) component sitting in a page to be // | As demonstrated in the laelith-adventure project, for the Editor, the goal is to enable a (Svelte) component sitting in a page to be // | ||
| Line 4: | Line 7: | ||
| We introduce here the **intercoms** utility, that helps managing the communication with detachable components. The main design goal here is to have the same way to communicate with the component, be it local or in a detached window. | We introduce here the **intercoms** utility, that helps managing the communication with detachable components. The main design goal here is to have the same way to communicate with the component, be it local or in a detached window. | ||
| - | ===== Using the Intercoms | + | ===== Attaching/ |
| + | === Properties of the detacheable component === | ||
| A detachable Svelte component must have a '' | A detachable Svelte component must have a '' | ||
| Line 17: | Line 21: | ||
| A container page for the detached version will pass '' | A container page for the detached version will pass '' | ||
| + | |||
| + | === Attach/ | ||
| + | |||
| + | Within the Svelte component, the following example code manages the operation: | ||
| + | |||
| + | < | ||
| + | const intercom = detached | ||
| + | ? intercoms.newDetachedIntercom(" | ||
| + | : intercoms.newIntercom(" | ||
| + | let fullyDetached = false; | ||
| + | | ||
| + | // The Embedded Component requests its container to destroy it. | ||
| + | const closeButton = () => { | ||
| + | onClose(); | ||
| + | }; | ||
| + | | ||
| + | // Closing the detached popup window reattaches the component | ||
| + | const reattach = () => { | ||
| + | window.close(); | ||
| + | }; | ||
| + | |||
| + | const detach = () => { | ||
| + | const ghostId = intercoms.detachMe(" | ||
| + | const detachedWindow = window.open( | ||
| + | "/? | ||
| + | " | ||
| + | " | ||
| + | ); | ||
| + | if (detachedWindow) { | ||
| + | intercoms.declareChild(ghostId, | ||
| + | fullyDetached = true; | ||
| + | onDetach(true); | ||
| + | }); | ||
| + | } else { | ||
| + | intercoms.undetachMe(" | ||
| + | } | ||
| + | }; | ||
| + | | ||
| + | onMount(() => { | ||
| + | intercom.on(Intercom.RX_MESSAGE, | ||
| + | (msg: IntercomMessage) => { | ||
| + | switch (msg.msgType) { | ||
| + | case " | ||
| + | fullyDetached = false; | ||
| + | onDetach(false); | ||
| + | // If needed, re-init code for the reattached component | ||
| + | break; | ||
| + | } | ||
| + | }, | ||
| + | " | ||
| + | ); | ||
| + | intercom.whenReady().then(() => { | ||
| + | // If needed, init code for the component | ||
| + | }); | ||
| + | }); | ||
| + | onDestroy(() => { | ||
| + | intercoms.destroyIntercom(" | ||
| + | }); | ||
| + | | ||
| + | </ | ||
| + | |||
| + | {#if !fullyDetached} | ||
| + | // Here the Svelte code of the component | ||
| + | {/if} | ||
| + | </ | ||
| + | |||
| + | The **const intercom = detached ? newDetachedIntercom...** code declares this Component to the // | ||
| + | |||
| + | The **const ghostId = intercoms.detachMe(" | ||
| + | |||
| + | Then the **const detachedWindow = window.open(...)** really creates the detached popup window. The page opened at this URL should embedd our component, with the '' | ||
| + | |||
| + | |||
| + | The **intercoms.declareChild(ghostId, | ||
| + | |||
| + | When the view is mounted, it listens to // | ||
| + | |||
| + | The " | ||
| + | ===== Using the Intercoms to communicate ===== | ||
| + | === Readiness signals === | ||
| + | Attaching / Detaching views involves a number of non-instantaneous, | ||
| + | The // | ||
| + | |||
| + | This is signalled using the '' | ||
| + | When in the embedded view, the returned Promise will immediately resolved.\\ | ||
| + | When in the detached view, it will resolve when the handshake with the parent window is complete, and, most importantly, | ||
| + | |||
| + | Typically, this is where you will have the code to fetch the data of the component, using // | ||
| + | |||
| + | < | ||
| + | intercom.whenReady().then(() => { | ||
| + | intercom.sendMessage(" | ||
| + | }); | ||
| + | </ | ||
| + | |||
| + | === Communication between components === | ||
| + | The // | ||
| + | |||
| + | The key property of this communication system is that a message addressed to a '' | ||
| + | |||
| + | Sending a message to the view identified by **target-view-id** is done using: | ||
| + | < | ||
| + | intercom.sendMessage(" | ||
| + | </ | ||
| + | |||
| + | The '' | ||
| + | |||
| + | For message reception, attach a Listener to the '' | ||
| + | < | ||
| + | intercom.on(Intercom.RX_MESSAGE, | ||
| + | (msg: IntercomMessage) => { | ||
| + | switch (msg.msgType) { | ||
| + | case ' | ||
| + | [...] | ||
| + | } | ||
| + | }," | ||
| + | ); | ||
| + | | ||
| + | intercom.whenReady().then(() => { | ||
| + | intercom.sendMessage(' | ||
| + | }); | ||
| + | </ | ||
| + | |||
| + | The code above illustrates a common pattern: the View registers a message listener on the '' | ||
| + | |||
| + | === The Network Prefix === | ||
| + | All the '' | ||
| + | The special value **prefix-all** broadcasts a message to all Views with the same Prefix.\\ | ||
| + | |||
| + | //Intercom messages// can only be exchanged between Views of the same '' | ||
| + | * This disposition allows to define several independent " | ||
| + | * It allows to scope the Broadcasts to the Views of the same // | ||
| + | * Other unrelated applications communicate using also postMessage between windows and // | ||
| + | |||
| + | ===== Ensuring consistency between definitions ===== | ||
| + | |||
| + | To prevent misspelling bugs and provide a central place for the common definitions of communicating entities, it is suggested to create a specific '' | ||
| + | |||
| + | * All the ViewIDs (same network prefix) in an //Enum//. | ||
| + | * The '' | ||
| + | * The type definition of the Payloads. | ||
| + | |||
| + | Create one such file for each " | ||
| + | |||
| + | < | ||
| + | /** | ||
| + | * Participants to the Adventure Editor group. Used as Source and Target of messages, with the NetworkPrefix. | ||
| + | */ | ||
| + | export enum AdvEditViews { | ||
| + | /** For broadcasting messages, to all except the emitter. */ | ||
| + | All = " | ||
| + | |||
| + | /** The Adventure Editor Store. */ | ||
| + | EditStore = " | ||
| + | |||
| + | /** The Main Editor View */ | ||
| + | MainEditor = " | ||
| + | [...] | ||
| + | } | ||
| + | |||
| + | /** Messages of these msgTypes are requests adressed to a View. */ | ||
| + | export enum AdvEditRequests { | ||
| + | /** | ||
| + | * The message requests the Adventure currently being edited. | ||
| + | * The Owner of the Adventure Edit Store should respond with a CurrentAdventure indication to the requester. | ||
| + | * Target: AdvEditViews.EditStore | ||
| + | * No Payload. | ||
| + | */ | ||
| + | RequestCurrentAdventure = " | ||
| + | [...] | ||
| + | } | ||
| + | |||
| + | /** Messages of these msgTypes are event-like messages that can interest different Views. */ | ||
| + | export enum AdvEditIndications { | ||
| + | |||
| + | /** The message payload contains the Adventure newly selected. */ | ||
| + | CurrentAdventure = " | ||
| + | [...] | ||
| + | } | ||
| + | |||
| + | /** | ||
| + | * Payload of AdventureAssetCount and UpdateAssetCount | ||
| + | */ | ||
| + | export interface AdventureAssetCountUpdate { | ||
| + | assetId: string, | ||
| + | value: number | ||
| + | } | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | </ | ||
en/outskirts/mages-peaks/detach-windows.1712678169.txt.gz · Last modified: 2024/10/28 08:00 (external edit)
