Skip to content

add foyer notification #108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Jun 25, 2025
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e571614
WIP:add foyer notification
zippy Jan 6, 2025
a9a5aee
fix from review
zippy Jan 7, 2025
b17b06b
Merge tag 'v0.13.0-rc.1' into foyer-notifications
zippy Jan 8, 2025
ddef8a6
add group name to foyer notification
zippy Jan 8, 2025
49b9f45
merge main
zippy May 20, 2025
423151a
Merge remote-tracking branch 'origin/main' into foyer-notifications
zippy May 20, 2025
f33aaff
Merge remote-tracking branch 'origin/main' into foyer-notifications
zippy May 20, 2025
c3df351
fix notebooks in example config
zippy May 20, 2025
7fc94a5
Merge branch 'main' into foyer-notifications
zippy Jun 18, 2025
bfe170f
update foyer message colors
zippy Jun 18, 2025
985d472
fix enable-disable of foyer send button
zippy Jun 18, 2025
8396f17
adds switching to group when clicking on foyer notification
zippy Jun 18, 2025
6ba883c
unify notification to WeaveLocation
zippy Jun 18, 2025
f81c35d
Merge branch 'main' into foyer-notifications
matthme Jun 19, 2025
ac3b520
Merge branch 'main' into foyer-notifications
matthme Jun 19, 2025
b8fa931
add mention logic, button tweaks
matthme Jun 19, 2025
1bab66f
Merge branch 'main' into foyer-notifications
matthme Jun 19, 2025
b14dae0
Merge branch 'foyer-notifications' of github.com:lightningrodlabs/mos…
matthme Jun 19, 2025
5f5219e
Merge branch 'foyer-notifications' into feat/foyer-tweaks
matthme Jun 19, 2025
0dace7d
Update src/renderer/src/groups/foyer.ts
matthme Jun 25, 2025
ae1d4bd
Update src/renderer/src/groups/foyer.ts
matthme Jun 25, 2025
60f6254
Merge pull request #127 from lightningrodlabs/feat/foyer-tweaks
matthme Jun 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,309 changes: 1,264 additions & 1,045 deletions Cargo.lock

Large diffs are not rendered by default.

803 changes: 424 additions & 379 deletions example/Cargo.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions src/main/index.ts
Original file line number Diff line number Diff line change
@@ -79,6 +79,7 @@ import {
GroupProfile,
ParentToAppletMessage,
WAL,
WeaveLocation,
} from '@theweave/api';
import { readLocalServices, startLocalServices } from './cli/devSetup';
import { autoUpdater, UpdateCheckResult } from '@matthme/electron-updater';
@@ -926,7 +927,7 @@ if (!RUNNING_WITH_COMMAND) {
notification: FrameNotification,
showInSystray: boolean,
notifyOS: boolean,
appletId: AppletId | undefined,
weaveLocation: WeaveLocation | undefined,
appletName: string | undefined,
): void => {
if (showInSystray && notification.urgency === 'high') {
@@ -949,7 +950,8 @@ if (!RUNNING_WITH_COMMAND) {
.on('click', () => {
console.log('Clicked on OS notification');
createOrShowMainWindow();
emitToWindow(MAIN_WINDOW!, 'switch-to-applet', appletId);
if (weaveLocation)
emitToWindow(MAIN_WINDOW!, 'switch-to-weave-location', weaveLocation);
SYSTRAY_ICON_STATE = undefined;
if (SYSTRAY) SYSTRAY.setImage(SYSTRAY_ICON_DEFAULT);
})
17 changes: 13 additions & 4 deletions src/preload/admin.ts
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import {
GroupProfile,
ParentToAppletMessage,
WAL,
WeaveLocation,
} from '@theweave/api';
import {
AppHashes,
@@ -53,8 +54,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
) => ipcRenderer.on('applet-to-parent-message', callback),
onDeepLinkReceived: (callback: (e: Electron.IpcRendererEvent, payload: string) => any) =>
ipcRenderer.on('deep-link-received', callback),
onSwitchToApplet: (callback: (e: Electron.IpcRendererEvent, payload: AppletId) => any) =>
ipcRenderer.on('switch-to-applet', callback),
onSwitchToWeaveLocation: (
callback: (e: Electron.IpcRendererEvent, payload: WeaveLocation) => any,
) => ipcRenderer.on('switch-to-weave-location', callback),
onWindowClosing: (callback: (e: Electron.IpcRendererEvent) => any) =>
ipcRenderer.on('window-closing', callback),
onWillNavigateExternal: (callback: (e: Electron.IpcRendererEvent) => any) =>
@@ -112,10 +114,17 @@ contextBridge.exposeInMainWorld('electronAPI', {
notification: FrameNotification,
showInSystray: boolean,
notifyOS: boolean,
appletId: AppletId | undefined,
weaveLocation: WeaveLocation | undefined,
appletName: string | undefined,
) =>
ipcRenderer.invoke('notification', notification, showInSystray, notifyOS, appletId, appletName),
ipcRenderer.invoke(
'notification',
notification,
showInSystray,
notifyOS,
weaveLocation,
appletName,
),
enableDevMode: () => ipcRenderer.invoke('enable-dev-mode'),
disableDevMode: () => ipcRenderer.invoke('disable-dev-mode'),
fetchIcon: (appActionHashB64: ActionHashB64) =>
2 changes: 1 addition & 1 deletion src/renderer/src/applets/applet-host.ts
Original file line number Diff line number Diff line change
@@ -490,7 +490,7 @@ export async function handleAppletIframeMessage(
notification,
notificationTypeSettings.showInSystray,
notificationTypeSettings.allowOSNotification && notification.urgency === 'high',
appletStore ? encodeHashToBase64(appletStore.appletHash) : undefined,
appletStore ? { type: 'applet', appletHash: appletStore.appletHash } : undefined,
appletStore ? appletStore.applet.custom_name : undefined,
);
}
5 changes: 3 additions & 2 deletions src/renderer/src/electron-api.ts
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ import {
GroupProfile as GroupProfilePartial,
ParentToAppletMessage,
WAL,
WeaveLocation,
} from '@theweave/api';

import {
@@ -57,7 +58,7 @@ declare global {
callback: (e: any, payload: { message: AppletToParentMessage; id: string }) => void,
) => void;
onDeepLinkReceived: (callback: (e: any, payload: string) => any) => void;
onSwitchToApplet: (callback: (e: any, payload: AppletId) => any) => void;
onSwitchToWeaveLocation: (callback: (e: any, payload: WeaveLocation) => any) => void;
onMossUpdateProgress: (callback: (e: any, payload: ProgressInfo) => any) => void;
onRequestFactoryReset: (callback: (e: any) => any) => void;
onWillNavigateExternal: (callback: (e: any) => any) => void;
@@ -135,7 +136,7 @@ declare global {
notification: FrameNotification,
showInSystray: boolean,
notifyOS: boolean,
appletId: AppletId | undefined,
weaveLocation: WeaveLocation | undefined,
appletName: string | undefined,
) => Promise<void>;
enableDevMode: () => Promise<void>;
15 changes: 15 additions & 0 deletions src/renderer/src/elements/_new_design/icons.ts
Original file line number Diff line number Diff line change
@@ -295,3 +295,18 @@ export const chevronDoubleRightIcon = (size = 16) => html`
/>
</svg>
`;

export const sendIcon = (size = 16) => html`
<svg
xmlns="http://www.w3.org/2000/svg"
width=${size}
height=${size}
fill="currentColor"
class="bi bi-send"
viewBox="0 0 16 16"
>
<path
d="M15.854.146a.5.5 0 0 1 .11.54l-5.819 14.547a.75.75 0 0 1-1.329.124l-3.178-4.995L.643 7.184a.75.75 0 0 1 .124-1.33L15.314.037a.5.5 0 0 1 .54.11ZM6.636 10.07l2.761 4.338L14.13 2.576zm6.787-8.201L1.591 6.602l4.339 2.76z"
/>
</svg>
`;
8 changes: 5 additions & 3 deletions src/renderer/src/elements/main-dashboard.ts
Original file line number Diff line number Diff line change
@@ -618,9 +618,11 @@ export class MainDashboard extends LitElement {
});
});

window.electronAPI.onSwitchToApplet((_, appletId) => {
if (appletId) {
this.openViews.openAppletMain(decodeHashFromBase64(appletId));
window.electronAPI.onSwitchToWeaveLocation((_, weaveLocation) => {
if (weaveLocation) {
if (weaveLocation.type === 'applet')
this.openViews.openAppletMain(weaveLocation.appletHash);
else if (weaveLocation.type === 'group') this.openGroup(weaveLocation.dnaHash);
}
});

36 changes: 14 additions & 22 deletions src/renderer/src/groups/elements/foyer-stream.ts
Original file line number Diff line number Diff line change
@@ -22,6 +22,8 @@ import { get, StoreSubscriber } from '@holochain-open-dev/stores';
import { AgentPubKey, decodeHashFromBase64, encodeHashToBase64 } from '@holochain/client';
import { mdiChat, mdiSofa } from '@mdi/js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { mossStyles } from '../../shared-styles.js';
import { sendIcon } from '../../elements/_new_design/icons.js';

@localized()
@customElement('foyer-stream')
@@ -325,7 +327,7 @@ export class FoyerStream extends LitElement {
<div class="send-controls">
<sl-input
id="msg-input"
style="width:100%"
style="width:100%;"
@sl-input=${(e) => {
this.disabled = !e.target.value || !this._msgInput.value;
}}
@@ -337,32 +339,22 @@ export class FoyerStream extends LitElement {
}}
placeholder="Message"
></sl-input>
<sl-button
style="margin-left:10px;"
circle
${this.disabled ? 'disabled' : ''}
<button
class="moss-button"
style="margin-left: 10px; padding: 0 11px; border-radius: 9px;"
?disabled=${this.disabled}
@click=${() => this.sendMessage()}
>
<svg
style="margin-top:10px"
xmlns="http://www.w3.org/2000/svg"
height="16"
width="16"
viewBox="0 0 512 512"
>
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.-->
<path
d="M16.1 260.2c-22.6 12.9-20.5 47.3 3.6 57.3L160 376V479.3c0 18.1 14.6 32.7 32.7 32.7c9.7 0 18.9-4.3 25.1-11.8l62-74.3 123.9 51.6c18.9 7.9 40.8-4.5 43.9-24.7l64-416c1.9-12.1-3.4-24.3-13.5-31.2s-23.3-7.5-34-1.4l-448 256zm52.1 25.5L409.7 90.6 190.1 336l1.2 1L68.2 285.7zM403.3 425.4L236.7 355.9 450.8 116.6 403.3 425.4z"
/>
</svg>
</sl-button>
<div class="column center-content" style="padding-top: 2px;">${sendIcon(18)}</div>
</button>
</div>
</div>
`;
}

static styles = [
sharedStyles,
mossStyles,
css`
.info:hover {
opacity: 0.7;
@@ -397,7 +389,7 @@ export class FoyerStream extends LitElement {
padding: 3px 10px;
flex-shrink: 1;
align-self: flex-start;
background-color: #305f19;
background-color: #323c1c;
}
.msg-content {
display: flex;
@@ -407,7 +399,7 @@ export class FoyerStream extends LitElement {
}
.my-msg {
align-self: flex-end;
background-color: #72b51b;
background-color: #4b461b;
}
.send-controls {
display: flex;
@@ -424,7 +416,7 @@ export class FoyerStream extends LitElement {
min-width: 16px;
height: 16px;
margin-left: 5px;
background-color: #f3ff3b;
background-color: #bac9af;
color: black;
font-size: 80%;
border-radius: 8px;
@@ -453,7 +445,7 @@ export class FoyerStream extends LitElement {
position: absolute;
top: 2px;
right: 14px;
background-color: #f3ff3b;
background-color: #bac9af;
margin-top: 5px;
padding: 5px;
color: white;
83 changes: 69 additions & 14 deletions src/renderer/src/groups/foyer.ts
Original file line number Diff line number Diff line change
@@ -6,13 +6,23 @@ import {
type AgentPubKey,
InstalledAppId,
RoleNameCallZomeRequest,
AppAuthenticationToken,
} from '@holochain/client';
import TimeAgo from 'javascript-time-ago';
import type { ProfilesStore } from '@holochain-open-dev/profiles';
import { type Writable, writable, get, type Readable, readable } from '@holochain-open-dev/stores';
import {
type Writable,
writable,
get,
type Readable,
readable,
toPromise,
} from '@holochain-open-dev/stores';
import { HoloHashMap } from '@holochain-open-dev/utils/dist/holo-hash-map';
import { type Message, Stream, type Payload } from './stream';
import { derived } from 'svelte/store';
import { FrameNotification, GroupProfile, WeaveLocation } from '@theweave/api';
import { GroupStore } from './group-store';

export const time = readable(Date.now(), function start(set) {
const interval = setInterval(() => {
@@ -130,29 +140,74 @@ export class FoyerStore {

stream.addMessage(message);
if (message.payload.type == 'Msg') {
// if (isWeaveContext()) {
// this.weaveClient.notifyFrame([
// {
// title: `message from ${encodeHashToBase64(message.from)}`,
// body: message.payload.text,
// notification_type: 'message',
// icon_src: undefined,
// urgency: 'high',
// timestamp: message.payload.created,
// },
// ]);
// }
if (encodeHashToBase64(message.from) != this.myPubKeyB64) {
const mainWindowFocused = await window.electronAPI.isMainWindowFocused();
let b64From = encodeHashToBase64(message.from);

if (b64From != this.myPubKeyB64) {
if (!mainWindowFocused) {
const senderProfile = await toPromise(this.profilesStore.profiles.get(message.from));
const senderNickname = senderProfile ? senderProfile.entry.nickname : b64From;
const myProfile = await toPromise(this.profilesStore.myProfile);
const myNickName = myProfile ? myProfile.entry.nickname.toLowerCase() : undefined;

const amIMentioned = message.payload.text.toLowerCase().includes(`@${myNickName}`);
const urgency = amIMentioned ? 'high' : 'medium';
const notification: FrameNotification = {
title: `from ${senderNickname}`,
body: message.payload.text,
notification_type: 'message',
icon_src: undefined,
urgency,
fromAgent: message.from,
timestamp: message.payload.created,
};
const weaveLocation: WeaveLocation = {
type: 'group',
dnaHash: this.groupStore.groupDnaHash,
};
await window.electronAPI.notification(
notification,
true,
amIMentioned,
weaveLocation,
`${this.groupProfile ? this.groupProfile.name : ''} foyer `,
);
}

await this.client.sendMessage(streamId, { type: 'Ack', created: message.payload.created }, [
message.from,
]);
}
}
}

static async create(
groupStore: GroupStore,
profilesStore: ProfilesStore,
clientIn: AppClient,
authenticationToken: AppAuthenticationToken,
roleName: RoleName,
zomeName: string = ZOME_NAME,
) {
let groupProfile: undefined | GroupProfile = undefined;
groupProfile = await toPromise(groupStore.groupProfile);
return new FoyerStore(
groupStore,
groupProfile,
profilesStore,
clientIn,
authenticationToken,
roleName,
zomeName,
);
}

constructor(
protected groupStore: GroupStore,
protected groupProfile: GroupProfile | undefined,
public profilesStore: ProfilesStore,
protected clientIn: AppClient,
protected authenticationToken: AppAuthenticationToken,
protected roleName: RoleName,
protected zomeName: string = ZOME_NAME,
) {
7 changes: 6 additions & 1 deletion src/renderer/src/groups/group-store.ts
Original file line number Diff line number Diff line change
@@ -144,7 +144,12 @@ export class GroupStore {
this.profilesStore = new ProfilesStore(new ProfilesClient(appWebsocket, 'group'));
this.customViewsStore = new CustomViewsStore(new CustomViewsClient(appWebsocket, 'group'));

this.foyerStore = new FoyerStore(this.profilesStore, appWebsocket, 'foyer');
FoyerStore.create(this, this.profilesStore, appWebsocket, authenticationToken, 'foyer').then(
(instance) => {
this.foyerStore = instance;
// Use the instance
},
);

this._peerStatuses = writable(undefined);

2 changes: 1 addition & 1 deletion weave.dev.config.example.ts
Original file line number Diff line number Diff line change
@@ -97,7 +97,7 @@ export default defineConfig({
},
source: {
type: 'https',
url: 'https://github.com/lightningrodlabs/notebooks/releases/download/v0.2.4/notebooks.webhapp',
url: 'https://github.com/lightningrodlabs/notebooks/releases/download/v0.5.0-rc.0/notebooks.webhapp',
},
},
],