Skip to content

Commit

Permalink
Merge branch 'main' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
matthme committed Oct 19, 2024
2 parents 3c1cf44 + 72488a7 commit 75f49fe
Show file tree
Hide file tree
Showing 23 changed files with 713 additions and 98 deletions.
40 changes: 40 additions & 0 deletions example/ui/src/applet-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
weaveUrlToLocation,
ReadonlyPeerStatusStore,
GroupPermissionType,
UnsubscribeFunction,
} from '@theweave/api';
import { AgentPubKey, AppClient } from '@holochain/client';
import '@theweave/elements/dist/elements/wal-embed.js';
Expand Down Expand Up @@ -78,11 +79,28 @@ export class AppletMain extends LitElement {
// @state()
// unsubscribe: undefined | (() => void);

@query('#failUnbeforeUnloadCheckmark')
failUnbeforeUnloadCheckmark!: HTMLInputElement;

onBeforeUnloadUnsubscribe: UnsubscribeFunction | undefined;

async firstUpdated() {
this.onBeforeUnloadUnsubscribe = this.weaveClient.onBeforeUnload(() => {
if (this.failUnbeforeUnloadCheckmark.checked)
throw new Error(
'The onbeforeunload callback failed (intentionally for testing purposes) in the example applet :(.'
);
console.log('@example-applet: Running second unbeforeunload callback.');
});

this.groupPermissionType = await this.weaveClient.myGroupPermissionType();
this.appletParticipants = await this.weaveClient.appletParticipants();
}

disconnectedCallback(): void {
if (this.onBeforeUnloadUnsubscribe) this.onBeforeUnloadUnsubscribe();
}

// disconnectedCallback(): void {
// if (this.unsubscribe) this.unsubscribe();
// }
Expand Down Expand Up @@ -247,6 +265,7 @@ export class AppletMain extends LitElement {
<div class="row">
<div class="column">
<create-post style="margin: 16px;"></create-post>
<h2>Notifications</h2>
<button @click=${() => this.sendLowNotification(5000)}>
Send Low Urgency Notification with 5 seconds delay
</button>
Expand All @@ -256,6 +275,17 @@ export class AppletMain extends LitElement {
<button @click=${() => this.sendUrgentNotification(5000)}>
Send High Urgency Notification with 5 seconds delay
</button>
<h2>on-before-unload behavior</h2>
<div class="row">
<input type="checkbox" id="failUnbeforeUnloadCheckmark"/>
Make the on-before-unload callback fail when reloading the applet to test how Moss handles this case.
</div>
<h2>Activity Notification</h2>
<search-agent
@agent-selected=${this.handleAgentSelected}
></search-agent>
Expand All @@ -265,6 +295,9 @@ export class AppletMain extends LitElement {
}}>
Send Activity Notification
</button>
<h2>Links</h2>
<div>Enter WAL:</div>
<textarea
id="wal-input-field"
Expand All @@ -282,6 +315,9 @@ export class AppletMain extends LitElement {
>
<a href="https://duckduckgo.com">duckduckgo.com</a>
<a href="https://duckduckgo.com" traget="_blank">duckduckgo.com</a>
<h2>Clipboard</h2>
<button
@click=${() => {
navigator.clipboard.writeText('Easter Egg.');
Expand All @@ -290,6 +326,8 @@ export class AppletMain extends LitElement {
Copy Something To Clipboard
</button>
<h2>Bindings</h2>
<div style="border: 1px solid black; padding: 5px; border-radius: 5px; margin: 10px 0;">
<div><b>Create Binding:</b></div>
<div class="row">
Expand Down Expand Up @@ -319,6 +357,8 @@ export class AppletMain extends LitElement {
</button>
</div>
<h2>WAL Embeds</h2>
<div style="border: 1px solid black; padding: 5px; border-radius: 5px; margin: 10px 0;">
<div><b>Embed WAL:</b></div>
<div class="row" style="margin-bottom: 10px;">
Expand Down
11 changes: 10 additions & 1 deletion example/ui/src/elements/post-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import './edit-post.js';
import { PostsStore } from '../posts-store.js';
import { postsStoreContext } from '../context.js';
import { Post } from '../types.js';
import { WAL, weaveUrlFromWal } from '@theweave/api';
import { WAL, WeaveClient, weaveUrlFromWal } from '@theweave/api';

/**
* @element post-detail
Expand All @@ -41,6 +41,9 @@ export class PostDetail extends LitElement {
@property(hashProperty('post-hash'))
postHash!: ActionHash;

@property()
weaveClient!: WeaveClient;

/**
* @internal
*/
Expand Down Expand Up @@ -126,6 +129,12 @@ export class PostDetail extends LitElement {
</sl-card>
<attachments-card .wal=${weaveUrlFromWal(this.WAL!, false)}></attachments-card>
</div>
<sl-button
style="margin-top: 20px;"
variant="danger"
@click=${() => this.weaveClient.requestClose()}
>Close Window (only works if open in separate Window)</sl-button
>
`;
}

Expand Down
8 changes: 8 additions & 0 deletions example/ui/src/example-applet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,14 @@ export class ExampleApplet extends LitElement {

peerStatusUnsubscribe: UnsubscribeFunction | undefined;

onBeforeUnloadUnsubscribe: UnsubscribeFunction | undefined;

firstUpdated() {
this.onBeforeUnloadUnsubscribe = this.weaveClient.onBeforeUnload(async () => {
console.log('Unloading in 10 seconds');
await new Promise((resolve) => setTimeout(resolve, 10000));
console.log('Unloading now.');
});
// To test whether applet iframe properly gets removed after disabling applet.
// setInterval(() => {
// console.log('Hello from the example applet iframe.');
Expand Down Expand Up @@ -117,6 +124,7 @@ export class ExampleApplet extends LitElement {
<attachments-context .store=${this.attachmentsStore}>
<post-detail
.postHash=${this.weaveClient.renderInfo.view.wal.hrl[1]}
.weaveClient=${this.weaveClient}
></post-detail>
</attachments-context>
</posts-context>
Expand Down
70 changes: 69 additions & 1 deletion iframes/applet-iframe/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ import {
import { readable } from '@holochain-open-dev/stores';
import { toOriginalCaseB64 } from '@theweave/utils';

type CallbackWithId = {
id: number;
callback: () => any;
};

declare global {
interface Window {
__WEAVE_API__: WeaveServices;
Expand All @@ -49,6 +54,7 @@ declare global {
__WEAVE_APPLET_ID__: AppletId;
__WEAVE_PROTOCOL_VERSION__: string;
__MOSS_VERSION__: string;
__WEAVE_ON_BEFORE_UNLOAD_CALLBACKS__: Array<CallbackWithId> | undefined;
}

interface WindowEventMap {
Expand All @@ -60,11 +66,40 @@ const weaveApi: WeaveServices = {
mossVersion: () => {
return window.__MOSS_VERSION__;
},

onPeerStatusUpdate: (callback: (payload: PeerStatusUpdate) => any) => {
const listener = (e: CustomEvent<PeerStatusUpdate>) => callback(e.detail);
window.addEventListener('peer-status-update', listener);
return () => window.removeEventListener('peer-status-update', listener);
},

onBeforeUnload: (callback: () => void) => {
// registers a callback on the window object that will be called before
// the iframe gets unloaded
const existingCallbacks = window.__WEAVE_ON_BEFORE_UNLOAD_CALLBACKS__ || [];
let newCallbackId = 0;
const existingCallbackIds = existingCallbacks.map((callbackWithId) => callbackWithId.id);
if (existingCallbackIds && existingCallbackIds.length > 0) {
// every new callback gets a new id in increasing manner
const highestId = existingCallbackIds.sort((a, b) => b - a)[0];
newCallbackId = highestId + 1;
}

existingCallbacks.push({ id: newCallbackId, callback });

window.__WEAVE_ON_BEFORE_UNLOAD_CALLBACKS__ = existingCallbacks;

const unlisten = () => {
const allCallbacks = window.__WEAVE_ON_BEFORE_UNLOAD_CALLBACKS__ || [];
window.__WEAVE_ON_BEFORE_UNLOAD_CALLBACKS__ = allCallbacks.filter(
(callbackWithId) => callbackWithId.id !== newCallbackId,
);
};

// We return an unlistener function which removes the callback from the list of callbacks
return unlisten;
},

openAppletMain: async (appletHash: EntryHash): Promise<void> =>
postMessage({
type: 'open-view',
Expand Down Expand Up @@ -190,6 +225,22 @@ const weaveApi: WeaveServices = {
window.__WEAVE_API__ = weaveApi;
window.__WEAVE_APPLET_SERVICES__ = new AppletServices();

// message handler for ParentToApplet messages
// This one is registered early here for any type of iframe
// to be able to respond also in case of page refreshes in short time
// intervals. Otherwise the message handler may not be registered in time
// when the on-before-unload message is sent to the iframe and Moss
// is waiting for a response and will never get one.
window.addEventListener('message', async (m: MessageEvent<any>) => {
try {
const result = await handleEventMessage(m.data);
m.ports[0].postMessage({ type: 'success', result });
} catch (e) {
console.error('Failed to send postMessage to cross-group-view', e);
m.ports[0]?.postMessage({ type: 'error', error: (e as any).message });
}
});

const [_, view] = await Promise.all([fetchLocalStorage(), getRenderView()]);

if (!view) {
Expand Down Expand Up @@ -233,7 +284,7 @@ const weaveApi: WeaveServices = {

const appletHash = window.__WEAVE_APPLET_HASH__;

// message handler for ParentToApplet messages - Only added for applet main-view
// message handler for ParentToApplet messages
window.addEventListener('message', async (m: MessageEvent<any>) => {
try {
const result = await handleMessage(appletClient, appletHash, m.data);
Expand Down Expand Up @@ -329,6 +380,19 @@ async function fetchLocalStorage() {
);
}

const handleEventMessage = async (message: ParentToAppletMessage) => {
switch (message.type) {
case 'on-before-unload':
const allCallbacks = window.__WEAVE_ON_BEFORE_UNLOAD_CALLBACKS__ || [];
await Promise.all(
allCallbacks.map(async (callbackWithId) => await callbackWithId.callback()),
);
return;
default:
return;
}
};

const handleMessage = async (
appletClient: AppClient,
appletHash: AppletHash,
Expand Down Expand Up @@ -364,6 +428,10 @@ const handleMessage = async (
}),
);
break;
case 'on-before-unload': {
// This case is handled in handleEventMessage
return;
}
default:
throw new Error(
`Unknown ParentToAppletMessage type: '${(message as any).type}'. Message: ${message}`,
Expand Down
16 changes: 16 additions & 0 deletions libs/api/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,18 @@ export interface WeaveServices {
* @returns
*/
onPeerStatusUpdate: (callback: (payload: PeerStatusUpdate) => any) => UnsubscribeFunction;
/**
* Event listener allowing to register a callback that will get executed before the
* applet gets reloaded, for example to save intermediate user input (e.g. commit
* the most recent changes of a document to the source chain).
*
* If this callback takes too long, users may be offered to force reload, thereby
* ignoring/cancelling the pending callback.
*
* @param callback Callback that gets called before the Applet gets reloaded
* @returns
*/
onBeforeUnload: (callback: () => void) => UnsubscribeFunction;
/**
* Open the main view of the specified Applet
* @param appletHash
Expand Down Expand Up @@ -369,6 +381,10 @@ export class WeaveClient implements WeaveServices {
return window.__WEAVE_API__.onPeerStatusUpdate(callback);
};

onBeforeUnload = (callback: () => any): UnsubscribeFunction => {
return window.__WEAVE_API__.onBeforeUnload(callback);
};

openAppletMain = async (appletHash: EntryHash): Promise<void> =>
window.__WEAVE_API__.openAppletMain(appletHash);

Expand Down
3 changes: 3 additions & 0 deletions libs/api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ export type ParentToAppletMessage =
| {
type: 'peer-status-update';
payload: PeerStatusUpdate;
}
| {
type: 'on-before-unload';
};

export type AppletToParentMessage = {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "org.lightningrodlabs.moss-0.13",
"version": "0.13.0-gamma.2",
"version": "0.13.0-gamma.3",
"private": true,
"description": "Moss (0.13)",
"main": "./out/main/index.js",
Expand All @@ -22,6 +22,7 @@
"applet-dev": "yarn check:binaries && concurrently \"yarn workspace @theweave/api build:watch\" \"yarn workspace @theweave/elements build:watch\" \"UI_PORT=8888 yarn workspace example-applet start\" \"electron-vite dev -- --dev-config weave.dev.config.ts --agent-idx 1\" \"sleep 10 && electron-vite dev -- --dev-config weave.dev.config.ts --agent-idx 2 --sync-time 10000\"",
"applet-dev-1": "yarn check:binaries && concurrently \"yarn workspace @theweave/api build:watch\" \"yarn workspace @theweave/elements build:watch\" \"yarn workspace @theweave/attachments build:watch\" \"UI_PORT=8888 yarn workspace example-applet start\" \"electron-vite dev -- --dev-config weave.dev.config.ts --agent-idx 1\"",
"applet-dev-3": "yarn check:binaries && concurrently \"yarn workspace @theweave/api build:watch\" \"yarn workspace @theweave/elements build:watch\" \"UI_PORT=8888 yarn workspace example-applet start\" \"electron-vite dev -- --dev-config weave.dev.config.ts --agent-idx 1\" \"sleep 5 && electron-vite dev -- --dev-config weave.dev.config.ts --agent-idx 2 --sync-time 10000\" \"sleep 5 && electron-vite dev -- --dev-config weave.dev.config.ts --agent-idx 3 --sync-time 10000\"",
"applet-dev-example-1": "yarn check:binaries && concurrently \"yarn workspace @theweave/api build:watch\" \"yarn workspace @theweave/elements build:watch\" \"UI_PORT=8888 yarn workspace example-applet start\" \"electron-vite dev -- --dev-config weave.dev.config.example.ts --agent-idx 1\"",
"applet-dev-example": "yarn check:binaries && concurrently \"yarn workspace @theweave/api build:watch\" \"yarn workspace @theweave/elements build:watch\" \"UI_PORT=8888 yarn workspace example-applet start\" \"electron-vite dev -- --dev-config weave.dev.config.example.ts --agent-idx 1\" \"sleep 10 && electron-vite dev -- --dev-config weave.dev.config.example.ts --agent-idx 2\"",
"dev": "yarn check:binaries && concurrently \"yarn workspace @theweave/api build:watch\" \"yarn workspace @theweave/elements build:watch\" \"UI_PORT=8888 yarn workspace example-applet start\" \"electron-vite dev -- --dev-config weave.dev.config.ts --agent-idx 1\"",
"dev:electron": "electron-vite dev",
Expand Down
Loading

0 comments on commit 75f49fe

Please sign in to comment.