From 294a6460385721cc448174c5b55672cd28e66986 Mon Sep 17 00:00:00 2001 From: "guillem.cordoba" Date: Tue, 22 Oct 2024 14:25:28 +0200 Subject: [PATCH] Fixed mocks and docs --- docs/elements/my-notifications-icon-button.md | 40 +- docs/elements/my-notifications-list.md | 40 +- docs/public/elements/custom-elements.json | 452 ++++++---------- tests/src/multi-device.test.ts | 1 - tests/src/notification-lifecycle.test.ts | 2 - ui/custom-elements.json | 486 ++++++------------ ui/src/elements/my-notifications-list.ts | 3 +- ui/src/mocks.ts | 301 +++-------- ui/src/notifications-store.ts | 236 +-------- ui/src/types.ts | 7 +- 10 files changed, 415 insertions(+), 1153 deletions(-) diff --git a/docs/elements/my-notifications-icon-button.md b/docs/elements/my-notifications-icon-button.md index 41ed2b84..bbd4369d 100644 --- a/docs/elements/my-notifications-icon-button.md +++ b/docs/elements/my-notifications-icon-button.md @@ -59,9 +59,9 @@ import { mdiBell } from '@mdi/js'; import { decode } from '@msgpack/msgpack'; import { decodeHashFromBase64 } from '@holochain/client'; import { render, html } from "lit"; -import { Signal } from '@holochain-open-dev/signals'; +import { Signal, toPromise } from '@holochain-open-dev/signals'; -import { NotificationsZomeMock, sampleNotification } from "../../ui/src/mocks.ts"; +import { NotificationsZomeMock } from "../../ui/src/mocks.ts"; import { NotificationsStore } from "../../ui/src/notifications-store.ts"; import { NotificationsClient } from "../../ui/src/notifications-client.ts"; @@ -82,38 +82,16 @@ onMounted(async () => { ); const profilesStore = new ProfilesStore(new ProfilesClient(profilesMock, "notifications_test")); + const myProfile = await toPromise(profilesStore.myProfile); + const mock = new NotificationsZomeMock(); const client = new NotificationsClient(mock, "notifications_test"); - const notification = await sampleNotification(client); - - const record = await mock.create_notification(notification); - - const store = new NotificationsStore(client, { - types: { - type1: { - name: 'Hello!', - description: 'something', - title(group) { - return new Signal.State({ - status: 'completed', - value: group, - }); - }, - onClick: group => alert(`clicked notification of group: ${group}`), - contents: n => { - const i = decode(n.entry.content); - return new Signal.State({ - status: 'completed', - value: { - iconSrc: wrapPathInSvg(mdiBell), - body: i.body, - }, - }); - }, - }, - }, - }); + const record = await client.sendNotification(myProfile.profileHash, 'example', 'type1', 'group1', { + Hello: 'world!' + }); + + const store = new NotificationsStore(client); render(html` diff --git a/docs/elements/my-notifications-list.md b/docs/elements/my-notifications-list.md index 5f7ca720..7fef3936 100644 --- a/docs/elements/my-notifications-list.md +++ b/docs/elements/my-notifications-list.md @@ -59,9 +59,9 @@ import { render, html } from "lit"; import { wrapPathInSvg } from '@holochain-open-dev/elements/dist/icon.js' import { mdiBell } from '@mdi/js'; import { decode } from '@msgpack/msgpack'; -import { Signal } from '@holochain-open-dev/signals'; +import { Signal, toPromise } from '@holochain-open-dev/signals'; -import { NotificationsZomeMock, sampleNotification } from "../../ui/src/mocks.ts"; +import { NotificationsZomeMock } from "../../ui/src/mocks.ts"; import { NotificationsStore } from "../../ui/src/notifications-store.ts"; import { NotificationsClient } from "../../ui/src/notifications-client.ts"; @@ -82,38 +82,16 @@ onMounted(async () => { ); const profilesStore = new ProfilesStore(new ProfilesClient(profilesMock, "notifications_test")); + const myProfile = await toPromise(profilesStore.myProfile); + const mock = new NotificationsZomeMock(); const client = new NotificationsClient(mock, "notifications_test"); - const notification = await sampleNotification(client); - - const record = await mock.create_notification(notification); - - const store = new NotificationsStore(client, { - types: { - type1: { - name: 'Hello!', - description: 'something', - title(group) { - return new Signal.State({ - status: 'completed', - value: group, - }); - }, - onClick: group => alert(`clicked notification of group: ${group}`), - contents: n => { - const i = decode(n.entry.content); - return new Signal.State({ - status: 'completed', - value: { - iconSrc: wrapPathInSvg(mdiBell), - body: i.body, - }, - }); - }, - }, - }, - }); + const record = await client.sendNotification(myProfile.profileHash, 'example', 'type1', 'group1', { + Hello: 'world!' + }); + + const store = new NotificationsStore(client); render(html` diff --git a/docs/public/elements/custom-elements.json b/docs/public/elements/custom-elements.json index 5717ff52..afc82956 100644 --- a/docs/public/elements/custom-elements.json +++ b/docs/public/elements/custom-elements.json @@ -2,62 +2,6 @@ "schemaVersion": "1.0.0", "readme": "", "modules": [ - { - "kind": "javascript-module", - "path": "locales/locales.js", - "declarations": [ - { - "kind": "variable", - "name": "sourceLocale", - "default": "`en`", - "description": "The locale code that templates in this source code are written in." - }, - { - "kind": "variable", - "name": "targetLocales", - "type": { - "text": "array" - }, - "default": "[\n ,\n]", - "description": "The other locale codes that this application is localized into. Sorted\nlexicographically." - }, - { - "kind": "variable", - "name": "allLocales", - "type": { - "text": "array" - }, - "default": "[\n `en`,\n]", - "description": "All valid project locale codes. Sorted lexicographically." - } - ], - "exports": [ - { - "kind": "js", - "name": "sourceLocale", - "declaration": { - "name": "sourceLocale", - "module": "locales/locales.js" - } - }, - { - "kind": "js", - "name": "targetLocales", - "declaration": { - "name": "targetLocales", - "module": "locales/locales.js" - } - }, - { - "kind": "js", - "name": "allLocales", - "declaration": { - "name": "allLocales", - "module": "locales/locales.js" - } - } - ] - }, { "kind": "javascript-module", "path": "src/context.ts", @@ -129,25 +73,20 @@ { "kind": "field", "name": "notifications", - "default": "new HoloHashMap<\n\t\tActionHash,\n\t\t{\n\t\t\tdeletes: Array>;\n\t\t\trevisions: Array;\n\t\t}\n\t>()", + "default": "new HoloHashMap()", "description": "Notification" }, { "kind": "field", - "name": "notificationsForRecipient", - "default": "new HoloHashMap()" - }, - { - "kind": "field", - "name": "readNotificationsByRecipient", - "default": "new HoloHashMap>()" + "name": "notificationsStatusChanges", + "default": "new HoloHashMap<\n\t\tEntryHash,\n\t\tNotificationsStatusChanges\n\t>()" }, { "kind": "method", - "name": "create_notification", + "name": "send_notification", "return": { "type": { - "text": "Promise" + "text": "Promise" } }, "parameters": [ @@ -161,169 +100,59 @@ }, { "kind": "method", - "name": "get_notification", - "return": { - "type": { - "text": "Promise" - } - }, - "parameters": [ - { - "name": "notificationHash", - "type": { - "text": "ActionHash" - } - } - ] - }, - { - "kind": "method", - "name": "get_all_deletes_for_notification", - "return": { - "type": { - "text": "Promise> | undefined>" - } - }, + "name": "change_notifications_status", "parameters": [ { - "name": "notificationHash", + "name": "status_changes", "type": { - "text": "ActionHash" + "text": "Record" } } ] }, { "kind": "method", - "name": "get_oldest_delete_for_notification", + "name": "query_notifications_and_status", "return": { "type": { - "text": "Promise | undefined>" + "text": "Promise<\n\t\tRecord<\n\t\t\tEntryHashB64,\n\t\t\t{ notification: Notification; status: NotificationStatus }\n\t\t>\n\t>" } - }, - "parameters": [ - { - "name": "notificationHash", - "type": { - "text": "ActionHash" - } - } - ] + } }, { "kind": "method", - "name": "delete_notification", - "return": { - "type": { - "text": "Promise" - } - }, + "name": "query_notifications_with_status", "parameters": [ { - "name": "original_notification_hash", + "name": "notificationStatus", "type": { - "text": "ActionHash" + "text": "NotificationStatus" } } ] }, { "kind": "method", - "name": "get_notifications_for_recipient", + "name": "get_notification_contents", "return": { "type": { - "text": "Promise>" + "text": "Promise" } }, "parameters": [ { - "name": "recipient", - "type": { - "text": "AgentPubKey" - } - } - ] - }, - { - "kind": "method", - "name": "mark_notifications_as_read", - "parameters": [ - { - "name": "notifications", - "type": { - "text": "ActionHash[]" - } - } - ] - }, - { - "kind": "method", - "name": "dismiss_notifications", - "parameters": [ - { - "name": "notifications", + "name": "notification", "type": { - "text": "ActionHash[]" + "text": "Notification" } } ] - }, - { - "kind": "method", - "name": "get_undismissed_notifications", - "return": { - "type": { - "text": "Promise>" - } - } - }, - { - "kind": "method", - "name": "get_dismissed_notifications", - "return": { - "type": { - "text": "Promise>" - } - } - }, - { - "kind": "method", - "name": "get_read_notifications", - "return": { - "type": { - "text": "Promise>" - } - } } ], "superclass": { "name": "ZomeMock", "package": "@holochain-open-dev/utils" } - }, - { - "kind": "function", - "name": "sampleNotification", - "return": { - "type": { - "text": "Promise" - } - }, - "parameters": [ - { - "name": "client", - "type": { - "text": "NotificationsClient" - } - }, - { - "name": "partialNotification", - "default": "{}", - "type": { - "text": "Partial" - } - } - ] } ], "exports": [ @@ -334,14 +163,6 @@ "name": "NotificationsZomeMock", "module": "src/mocks.ts" } - }, - { - "kind": "js", - "name": "sampleNotification", - "declaration": { - "name": "sampleNotification", - "module": "src/mocks.ts" - } } ] }, @@ -356,7 +177,7 @@ "members": [ { "kind": "method", - "name": "createNotification", + "name": "sendNotification", "return": { "type": { "text": "Promise" @@ -364,9 +185,33 @@ }, "parameters": [ { - "name": "notification", + "name": "recipientProfileHash", "type": { - "text": "Notification" + "text": "ActionHash" + } + }, + { + "name": "zomeName", + "type": { + "text": "string" + } + }, + { + "name": "notificationType", + "type": { + "text": "string" + } + }, + { + "name": "notificationGroup", + "type": { + "text": "string" + } + }, + { + "name": "content", + "type": { + "text": "any" } } ], @@ -374,98 +219,73 @@ }, { "kind": "method", - "name": "getNotification", - "return": { - "type": { - "text": "Promise | undefined>" - } - }, + "name": "markNotificationsAsRead", "parameters": [ { - "name": "notificationHash", + "name": "notificationsHashes", "type": { - "text": "ActionHash" + "text": "EntryHash[]" } } ] }, { "kind": "method", - "name": "markNotificationsAsRead", - "return": { - "type": { - "text": "Promise" - } - }, + "name": "dismissNotifications", "parameters": [ { "name": "notificationsHashes", "type": { - "text": "ActionHash[]" + "text": "EntryHash[]" } } ] }, { "kind": "method", - "name": "dismissNotifications", - "return": { - "type": { - "text": "Promise" - } - }, + "name": "changeNotificationsStatus", "parameters": [ { - "name": "notificationsHashes", + "name": "statusChanges", "type": { - "text": "ActionHash[]" + "text": "Record" } } ] }, { "kind": "method", - "name": "getAllDeletesForNotification", + "name": "queryNotificationsWithStatus", "return": { "type": { - "text": "Promise> | undefined>" + "text": "Promise>" } }, "parameters": [ { - "name": "originalNotificationHash", + "name": "statusFilter", "type": { - "text": "ActionHash" + "text": "NotificationStatus" } } ] }, { "kind": "method", - "name": "getUndismissedNotifications", + "name": "getNotificationContents", "return": { "type": { - "text": "Promise>" + "text": "Promise" } - } - }, - { - "kind": "method", - "name": "getReadNotifications", - "return": { - "type": { - "text": "Promise>" - } - } - }, - { - "kind": "method", - "name": "getDismissedNotifications", - "return": { - "type": { - "text": "Promise<\n\t\tArray<[SignedActionHashed, SignedActionHashed[]]>\n\t>" + }, + "parameters": [ + { + "name": "notification", + "type": { + "text": "Notification" + } } - } + ] } ], "superclass": { @@ -496,56 +316,53 @@ "members": [ { "kind": "method", - "name": "addTypes", + "name": "buildQueryNotificationsWithStatusSignal", + "privacy": "private", "parameters": [ { - "name": "notificationsType", + "name": "statusFilter", "type": { - "text": "Record" + "text": "NotificationStatus" } } - ] - }, - { - "kind": "field", - "name": "notifications", - "default": "new LazyHoloHashMap((notificationHash: ActionHash) => ({\n\t\tentry: immutableEntrySignal(() =>\n\t\t\tthis.client.getNotification(notificationHash),\n\t\t),\n\t\tdeletes: deletesForEntrySignal(this.client, notificationHash, () =>\n\t\t\tthis.client.getAllDeletesForNotification(notificationHash),\n\t\t),\n\t}))", - "description": "Notification" - }, - { - "kind": "field", - "name": "undismissedNotificationsLinks", - "privacy": "private" - }, - { - "kind": "field", - "name": "readNotificationsLinks", - "privacy": "private" + ], + "description": "Notifications" }, { "kind": "field", - "name": "readNotifications", - "default": "new AsyncComputed(() => {\n\t\tconst readNotificationsLinks = this.readNotificationsLinks.get();\n\t\tconst undismissedNotifications = this.undismissedNotificationsLinks.get();\n\t\tif (readNotificationsLinks.status !== 'completed')\n\t\t\treturn readNotificationsLinks;\n\t\tif (undismissedNotifications.status !== 'completed')\n\t\t\treturn undismissedNotifications;\n\n\t\t/** Aggregate the read notification hashes and filter them by whether they've been dismissed */\n\n\t\tconst allReadNotificationsHashes = uniquify(\n\t\t\tArray.from([] as ActionHash[]).concat(\n\t\t\t\t...readNotificationsLinks.value.map(\n\t\t\t\t\tlink => decode(link.tag) as ActionHash[],\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\n\t\tconst undismissedNotificationsHashes = undismissedNotifications.value.map(\n\t\t\tl => encodeHashToBase64(l.target),\n\t\t);\n\n\t\tconst notificationsHashes = allReadNotificationsHashes.filter(hash =>\n\t\t\tundismissedNotificationsHashes.includes(encodeHashToBase64(hash)),\n\t\t);\n\n\t\t/* If a notification was persistent and has been read but was deleted (usually by someone else performing the action that the notification required), then we dismiss the notification */\n\n\t\t// const deletes = joinAsync(\n\t\t// \tnotificationsHashes.map(hash =>\n\t\t// \t\tthis.notifications.get(hash).deletes.get(),\n\t\t// \t),\n\t\t// );\n\t\t// const entries = joinAsync(\n\t\t// \tnotificationsHashes.map(hash =>\n\t\t// \t\tthis.notifications.get(hash).entry.get(),\n\t\t// \t),\n\t\t// );\n\t\t// if (entries.status !== 'completed') return entries;\n\t\t// if (deletes.status !== 'completed') return deletes;\n\n\t\t// const nonDeletedNotificationHashes: ActionHash[] = [];\n\t\t// const notificationsToDismiss: ActionHash[] = [];\n\n\t\t// for (let i = 0; i < notificationsHashes.length; i++) {\n\t\t// \tif (!entries.value[i].entry.persistent || deletes.value[i].length === 0) {\n\t\t// \t\tnonDeletedNotificationHashes.push(notificationsHashes[i]);\n\t\t// \t} else {\n\t\t// \t\tnotificationsToDismiss.push(notificationsHashes[i]);\n\t\t// \t}\n\t\t// }\n\n\t\t// if (notificationsToDismiss.length > 0) {\n\t\t// \tthis.client.dismissNotifications(notificationsToDismiss);\n\t\t// }\n\n\t\tconst value = slice(this.notifications, notificationsHashes);\n\t\treturn {\n\t\t\tstatus: 'completed',\n\t\t\tvalue,\n\t\t};\n\t})" + "name": "_notificationContents", + "privacy": "private", + "default": "new HoloHashMap<\n\t\tEntryHash,\n\t\tAsyncSignal\n\t>()" }, { - "kind": "field", - "name": "unreadNotifications", - "default": "new AsyncComputed(() => {\n\t\tconst readNotifications = this.readNotifications.get();\n\t\tconst undismissedNotifications = this.undismissedNotificationsLinks.get();\n\n\t\tif (readNotifications.status !== 'completed') return readNotifications;\n\t\tif (undismissedNotifications.status !== 'completed')\n\t\t\treturn undismissedNotifications;\n\n\t\tconst readNotificationsHashes = Array.from(\n\t\t\treadNotifications.value.keys(),\n\t\t).map(h => encodeHashToBase64(h));\n\n\t\tconst links = undismissedNotifications.value.filter(\n\t\t\tlink =>\n\t\t\t\t!readNotificationsHashes.includes(encodeHashToBase64(link.target)),\n\t\t);\n\t\tconst value = slice(this.notifications, uniquify(links.map(l => l.target)));\n\t\treturn {\n\t\t\tstatus: 'completed',\n\t\t\tvalue,\n\t\t};\n\t})" + "kind": "method", + "name": "notificationContents", + "parameters": [ + { + "name": "notificationHash", + "type": { + "text": "EntryHash" + } + }, + { + "name": "notification", + "type": { + "text": "Notification" + } + } + ] }, { "kind": "field", - "name": "deletedNotificationsLinks" + "name": "unreadNotifications" }, { "kind": "field", - "name": "dismissedNotifications", - "default": "new AsyncComputed(() => {\n\t\tconst deletedLinks = this.deletedNotificationsLinks.get();\n\t\tif (deletedLinks.status !== 'completed') return deletedLinks;\n\n\t\tconst value = slice(\n\t\t\tthis.notifications,\n\t\t\tdeletedLinks.value.map(l => l[0].hashed.content.target_address),\n\t\t);\n\n\t\treturn {\n\t\t\tstatus: 'completed',\n\t\t\tvalue,\n\t\t};\n\t})" + "name": "readNotifications" }, { "kind": "field", - "name": "notificationsByTypeAndGroup", - "default": "new LazyMap(\n\t\t(notificationType: string) =>\n\t\t\tnew LazyMap((notificationGroup: string) => ({\n\t\t\t\tread: new AsyncComputed(() => {\n\t\t\t\t\tconst notifications = this.readNotifications.get();\n\t\t\t\t\tif (notifications.status !== 'completed') return notifications;\n\n\t\t\t\t\tconst entries = joinAsyncMap(\n\t\t\t\t\t\tmapValues(notifications.value, n => n.entry.get()),\n\t\t\t\t\t);\n\t\t\t\t\tif (entries.status !== 'completed') return entries;\n\n\t\t\t\t\tconst value = pickBy(\n\t\t\t\t\t\tentries.value,\n\t\t\t\t\t\tn =>\n\t\t\t\t\t\t\tn.entry.notification_type === notificationType &&\n\t\t\t\t\t\t\tn.entry.notification_group === notificationGroup,\n\t\t\t\t\t);\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t\tunread: new AsyncComputed(() => {\n\t\t\t\t\tconst notifications = this.unreadNotifications.get();\n\t\t\t\t\tif (notifications.status !== 'completed') return notifications;\n\n\t\t\t\t\tconst entries = joinAsyncMap(\n\t\t\t\t\t\tmapValues(notifications.value, n => n.entry.get()),\n\t\t\t\t\t);\n\t\t\t\t\tif (entries.status !== 'completed') return entries;\n\n\t\t\t\t\tconst value = pickBy(\n\t\t\t\t\t\tentries.value,\n\t\t\t\t\t\tn =>\n\t\t\t\t\t\t\tn.entry.notification_type === notificationType &&\n\t\t\t\t\t\t\tn.entry.notification_group === notificationGroup,\n\t\t\t\t\t);\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t})),\n\t)", - "description": "Helpers for consuming UIs" + "name": "dismissedNotifications" } ] } @@ -567,6 +384,62 @@ "declarations": [], "exports": [] }, + { + "kind": "javascript-module", + "path": "locales/locales.js", + "declarations": [ + { + "kind": "variable", + "name": "sourceLocale", + "default": "`en`", + "description": "The locale code that templates in this source code are written in." + }, + { + "kind": "variable", + "name": "targetLocales", + "type": { + "text": "array" + }, + "default": "[\n ,\n]", + "description": "The other locale codes that this application is localized into. Sorted\nlexicographically." + }, + { + "kind": "variable", + "name": "allLocales", + "type": { + "text": "array" + }, + "default": "[\n `en`,\n]", + "description": "All valid project locale codes. Sorted lexicographically." + } + ], + "exports": [ + { + "kind": "js", + "name": "sourceLocale", + "declaration": { + "name": "sourceLocale", + "module": "locales/locales.js" + } + }, + { + "kind": "js", + "name": "targetLocales", + "declaration": { + "name": "targetLocales", + "module": "locales/locales.js" + } + }, + { + "kind": "js", + "name": "allLocales", + "declaration": { + "name": "allLocales", + "module": "locales/locales.js" + } + } + ] + }, { "kind": "javascript-module", "path": "src/elements/my-notifications-icon-button.ts", @@ -633,12 +506,6 @@ "text": "boolean" } }, - { - "name": "persistent", - "type": { - "text": "boolean" - } - }, { "name": "notificationGroup", "type": { @@ -653,23 +520,6 @@ } ] }, - { - "kind": "method", - "name": "notificationInfo", - "return": { - "type": { - "text": "AsyncResult" - } - }, - "parameters": [ - { - "name": "notificationHash", - "type": { - "text": "ActionHash" - } - } - ] - }, { "kind": "method", "name": "getNotificationsGroups" diff --git a/tests/src/multi-device.test.ts b/tests/src/multi-device.test.ts index 564a5a28..4de3da1a 100644 --- a/tests/src/multi-device.test.ts +++ b/tests/src/multi-device.test.ts @@ -4,7 +4,6 @@ import { decodeHashFromBase64 } from '@holochain/client'; import { dhtSync, pause, runScenario } from '@holochain/tryorama'; import { assert, expect, test } from 'vitest'; -import { sampleNotification } from '../../ui/src/mocks.js'; import { Notification } from '../../ui/src/types.js'; import { setup } from './setup.js'; diff --git a/tests/src/notification-lifecycle.test.ts b/tests/src/notification-lifecycle.test.ts index e0a1aa06..526d067e 100644 --- a/tests/src/notification-lifecycle.test.ts +++ b/tests/src/notification-lifecycle.test.ts @@ -3,8 +3,6 @@ import { EntryRecord } from '@holochain-open-dev/utils'; import { dhtSync, pause, runScenario } from '@holochain/tryorama'; import { assert, expect, test } from 'vitest'; -import { sampleNotification } from '../../ui/src/mocks.js'; -import { Notification } from '../../ui/src/types.js'; import { setup } from './setup.js'; test('create notifications, read it, and dismiss it', async () => { diff --git a/ui/custom-elements.json b/ui/custom-elements.json index 4751ff26..afc82956 100644 --- a/ui/custom-elements.json +++ b/ui/custom-elements.json @@ -2,62 +2,6 @@ "schemaVersion": "1.0.0", "readme": "", "modules": [ - { - "kind": "javascript-module", - "path": "locales/locales.js", - "declarations": [ - { - "kind": "variable", - "name": "sourceLocale", - "default": "`en`", - "description": "The locale code that templates in this source code are written in." - }, - { - "kind": "variable", - "name": "targetLocales", - "type": { - "text": "array" - }, - "default": "[\n ,\n]", - "description": "The other locale codes that this application is localized into. Sorted\nlexicographically." - }, - { - "kind": "variable", - "name": "allLocales", - "type": { - "text": "array" - }, - "default": "[\n `en`,\n]", - "description": "All valid project locale codes. Sorted lexicographically." - } - ], - "exports": [ - { - "kind": "js", - "name": "sourceLocale", - "declaration": { - "name": "sourceLocale", - "module": "locales/locales.js" - } - }, - { - "kind": "js", - "name": "targetLocales", - "declaration": { - "name": "targetLocales", - "module": "locales/locales.js" - } - }, - { - "kind": "js", - "name": "allLocales", - "declaration": { - "name": "allLocales", - "module": "locales/locales.js" - } - } - ] - }, { "kind": "javascript-module", "path": "src/context.ts", @@ -129,25 +73,20 @@ { "kind": "field", "name": "notifications", - "default": "new HoloHashMap<\n\t\tActionHash,\n\t\t{\n\t\t\tdeletes: Array>;\n\t\t\trevisions: Array;\n\t\t}\n\t>()", + "default": "new HoloHashMap()", "description": "Notification" }, { "kind": "field", - "name": "notificationsForRecipient", - "default": "new HoloHashMap()" - }, - { - "kind": "field", - "name": "readNotificationsByRecipient", - "default": "new HoloHashMap>()" + "name": "notificationsStatusChanges", + "default": "new HoloHashMap<\n\t\tEntryHash,\n\t\tNotificationsStatusChanges\n\t>()" }, { "kind": "method", - "name": "create_notification", + "name": "send_notification", "return": { "type": { - "text": "Promise" + "text": "Promise" } }, "parameters": [ @@ -161,160 +100,50 @@ }, { "kind": "method", - "name": "get_notification", - "return": { - "type": { - "text": "Promise" - } - }, - "parameters": [ - { - "name": "notificationHash", - "type": { - "text": "ActionHash" - } - } - ] - }, - { - "kind": "method", - "name": "get_all_deletes_for_notification", - "return": { - "type": { - "text": "Promise> | undefined>" - } - }, + "name": "change_notifications_status", "parameters": [ { - "name": "notificationHash", + "name": "status_changes", "type": { - "text": "ActionHash" + "text": "Record" } } ] }, { "kind": "method", - "name": "get_oldest_delete_for_notification", + "name": "query_notifications_and_status", "return": { "type": { - "text": "Promise | undefined>" - } - }, - "parameters": [ - { - "name": "notificationHash", - "type": { - "text": "ActionHash" - } + "text": "Promise<\n\t\tRecord<\n\t\t\tEntryHashB64,\n\t\t\t{ notification: Notification; status: NotificationStatus }\n\t\t>\n\t>" } - ] - }, - { - "kind": "method", - "name": "delete_notification", - "return": { - "type": { - "text": "Promise" - } - }, - "parameters": [ - { - "name": "original_notification_hash", - "type": { - "text": "ActionHash" - } - } - ] - }, - { - "kind": "method", - "name": "get_notifications_for_recipient", - "return": { - "type": { - "text": "Promise>" - } - }, - "parameters": [ - { - "name": "recipient_prolife_hash", - "type": { - "text": "ActionHash" - } - } - ] - }, - { - "kind": "method", - "name": "mark_notifications_as_read", - "parameters": [ - { - "name": "input", - "type": { - "text": "{\n\t\tnotifications_hashes: ActionHash[];\n\t\tmy_profile_hash: ActionHash;\n\t}" - } - } - ] - }, - { - "kind": "method", - "name": "dismiss_notifications", - "parameters": [ - { - "name": "input", - "type": { - "text": "{\n\t\tnotifications_hashes: ActionHash[];\n\t\tmy_profile_hash: ActionHash;\n\t}" - } - } - ] - }, - { - "kind": "method", - "name": "get_undismissed_notifications", - "return": { - "type": { - "text": "Promise>" - } - }, - "parameters": [ - { - "name": "myProfileHash", - "type": { - "text": "ActionHash" - } - } - ] + } }, { "kind": "method", - "name": "get_dismissed_notifications", - "return": { - "type": { - "text": "Promise>" - } - }, + "name": "query_notifications_with_status", "parameters": [ { - "name": "myProfileHash", + "name": "notificationStatus", "type": { - "text": "ActionHash" + "text": "NotificationStatus" } } ] }, { "kind": "method", - "name": "get_read_notifications", + "name": "get_notification_contents", "return": { "type": { - "text": "Promise>" + "text": "Promise" } }, "parameters": [ { - "name": "myProfileHash", + "name": "notification", "type": { - "text": "ActionHash" + "text": "Notification" } } ] @@ -324,30 +153,6 @@ "name": "ZomeMock", "package": "@holochain-open-dev/utils" } - }, - { - "kind": "function", - "name": "sampleNotification", - "return": { - "type": { - "text": "Promise" - } - }, - "parameters": [ - { - "name": "client", - "type": { - "text": "NotificationsClient" - } - }, - { - "name": "partialNotification", - "default": "{}", - "type": { - "text": "Partial" - } - } - ] } ], "exports": [ @@ -358,14 +163,6 @@ "name": "NotificationsZomeMock", "module": "src/mocks.ts" } - }, - { - "kind": "js", - "name": "sampleNotification", - "declaration": { - "name": "sampleNotification", - "module": "src/mocks.ts" - } } ] }, @@ -380,7 +177,7 @@ "members": [ { "kind": "method", - "name": "createNotification", + "name": "sendNotification", "return": { "type": { "text": "Promise" @@ -388,9 +185,33 @@ }, "parameters": [ { - "name": "notification", + "name": "recipientProfileHash", "type": { - "text": "Notification" + "text": "ActionHash" + } + }, + { + "name": "zomeName", + "type": { + "text": "string" + } + }, + { + "name": "notificationType", + "type": { + "text": "string" + } + }, + { + "name": "notificationGroup", + "type": { + "text": "string" + } + }, + { + "name": "content", + "type": { + "text": "any" } } ], @@ -398,103 +219,73 @@ }, { "kind": "method", - "name": "getNotification", - "return": { - "type": { - "text": "Promise | undefined>" - } - }, + "name": "markNotificationsAsRead", "parameters": [ { - "name": "notificationHash", + "name": "notificationsHashes", "type": { - "text": "ActionHash" + "text": "EntryHash[]" } } ] }, { "kind": "method", - "name": "myProfileHash", - "privacy": "private" - }, - { - "kind": "method", - "name": "markNotificationsAsRead", - "return": { - "type": { - "text": "Promise" - } - }, + "name": "dismissNotifications", "parameters": [ { "name": "notificationsHashes", "type": { - "text": "ActionHash[]" + "text": "EntryHash[]" } } ] }, { "kind": "method", - "name": "dismissNotifications", - "return": { - "type": { - "text": "Promise" - } - }, + "name": "changeNotificationsStatus", "parameters": [ { - "name": "notificationsHashes", + "name": "statusChanges", "type": { - "text": "ActionHash[]" + "text": "Record" } } ] }, { "kind": "method", - "name": "getAllDeletesForNotification", + "name": "queryNotificationsWithStatus", "return": { "type": { - "text": "Promise> | undefined>" + "text": "Promise>" } }, "parameters": [ { - "name": "originalNotificationHash", + "name": "statusFilter", "type": { - "text": "ActionHash" + "text": "NotificationStatus" } } ] }, { "kind": "method", - "name": "getUndismissedNotifications", + "name": "getNotificationContents", "return": { "type": { - "text": "Promise>" + "text": "Promise" } - } - }, - { - "kind": "method", - "name": "getReadNotifications", - "return": { - "type": { - "text": "Promise>" - } - } - }, - { - "kind": "method", - "name": "getDismissedNotifications", - "return": { - "type": { - "text": "Promise<\n\t\tArray<[SignedActionHashed, SignedActionHashed[]]>\n\t>" + }, + "parameters": [ + { + "name": "notification", + "type": { + "text": "Notification" + } } - } + ] } ], "superclass": { @@ -525,61 +316,53 @@ "members": [ { "kind": "method", - "name": "addTypes", + "name": "buildQueryNotificationsWithStatusSignal", + "privacy": "private", "parameters": [ { - "name": "notificationsType", + "name": "statusFilter", "type": { - "text": "Record" + "text": "NotificationStatus" } } - ] - }, - { - "kind": "field", - "name": "notifications", - "default": "new LazyHoloHashMap((notificationHash: ActionHash) => ({\n\t\tentry: immutableEntrySignal(() =>\n\t\t\tthis.client.getNotification(notificationHash),\n\t\t),\n\t\tdeletes: deletesForEntrySignal(this.client, notificationHash, () =>\n\t\t\tthis.client.getAllDeletesForNotification(notificationHash),\n\t\t),\n\t}))", - "description": "Notification" - }, - { - "kind": "field", - "name": "myProfileExistsOrPending", - "privacy": "private" - }, - { - "kind": "field", - "name": "undismissedNotificationsLinks", - "privacy": "private" - }, - { - "kind": "field", - "name": "readNotificationsLinks", - "privacy": "private" + ], + "description": "Notifications" }, { "kind": "field", - "name": "readNotifications", - "default": "new AsyncComputed(() => {\n\t\tconst readNotificationsLinks = this.readNotificationsLinks.get();\n\t\tconst undismissedNotifications = this.undismissedNotificationsLinks.get();\n\t\tif (readNotificationsLinks.status !== 'completed')\n\t\t\treturn readNotificationsLinks;\n\t\tif (undismissedNotifications.status !== 'completed')\n\t\t\treturn undismissedNotifications;\n\n\t\t/** Aggregate the read notification hashes and filter them by whether they've been dismissed */\n\n\t\tconst allReadNotificationsHashes = uniquify(\n\t\t\tArray.from([] as ActionHash[]).concat(\n\t\t\t\t...readNotificationsLinks.value.map(\n\t\t\t\t\tlink => decode(link.tag) as ActionHash[],\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\n\t\tconst undismissedNotificationsHashes = undismissedNotifications.value.map(\n\t\t\tl => encodeHashToBase64(l.target),\n\t\t);\n\n\t\tconst notificationsHashes = allReadNotificationsHashes.filter(hash =>\n\t\t\tundismissedNotificationsHashes.includes(encodeHashToBase64(hash)),\n\t\t);\n\n\t\t/* If a notification was persistent and has been read but was deleted (usually by someone else performing the action that the notification required), then we dismiss the notification */\n\n\t\t// const deletes = joinAsync(\n\t\t// \tnotificationsHashes.map(hash =>\n\t\t// \t\tthis.notifications.get(hash).deletes.get(),\n\t\t// \t),\n\t\t// );\n\t\t// const entries = joinAsync(\n\t\t// \tnotificationsHashes.map(hash =>\n\t\t// \t\tthis.notifications.get(hash).entry.get(),\n\t\t// \t),\n\t\t// );\n\t\t// if (entries.status !== 'completed') return entries;\n\t\t// if (deletes.status !== 'completed') return deletes;\n\n\t\t// const nonDeletedNotificationHashes: ActionHash[] = [];\n\t\t// const notificationsToDismiss: ActionHash[] = [];\n\n\t\t// for (let i = 0; i < notificationsHashes.length; i++) {\n\t\t// \tif (!entries.value[i].entry.persistent || deletes.value[i].length === 0) {\n\t\t// \t\tnonDeletedNotificationHashes.push(notificationsHashes[i]);\n\t\t// \t} else {\n\t\t// \t\tnotificationsToDismiss.push(notificationsHashes[i]);\n\t\t// \t}\n\t\t// }\n\n\t\t// if (notificationsToDismiss.length > 0) {\n\t\t// \tthis.client.dismissNotifications(notificationsToDismiss);\n\t\t// }\n\n\t\tconst value = slice(this.notifications, notificationsHashes);\n\t\treturn {\n\t\t\tstatus: 'completed',\n\t\t\tvalue,\n\t\t};\n\t})" + "name": "_notificationContents", + "privacy": "private", + "default": "new HoloHashMap<\n\t\tEntryHash,\n\t\tAsyncSignal\n\t>()" }, { - "kind": "field", - "name": "unreadNotifications", - "default": "new AsyncComputed(() => {\n\t\tconst readNotifications = this.readNotifications.get();\n\t\tconst undismissedNotifications = this.undismissedNotificationsLinks.get();\n\n\t\tif (readNotifications.status !== 'completed') return readNotifications;\n\t\tif (undismissedNotifications.status !== 'completed')\n\t\t\treturn undismissedNotifications;\n\n\t\tconst readNotificationsHashes = Array.from(\n\t\t\treadNotifications.value.keys(),\n\t\t).map(h => encodeHashToBase64(h));\n\n\t\tconst links = undismissedNotifications.value.filter(\n\t\t\tlink =>\n\t\t\t\t!readNotificationsHashes.includes(encodeHashToBase64(link.target)),\n\t\t);\n\t\tconst value = slice(this.notifications, uniquify(links.map(l => l.target)));\n\t\treturn {\n\t\t\tstatus: 'completed',\n\t\t\tvalue,\n\t\t};\n\t})" + "kind": "method", + "name": "notificationContents", + "parameters": [ + { + "name": "notificationHash", + "type": { + "text": "EntryHash" + } + }, + { + "name": "notification", + "type": { + "text": "Notification" + } + } + ] }, { "kind": "field", - "name": "deletedNotificationsLinks" + "name": "unreadNotifications" }, { "kind": "field", - "name": "dismissedNotifications", - "default": "new AsyncComputed(() => {\n\t\tconst deletedLinks = this.deletedNotificationsLinks.get();\n\t\tif (deletedLinks.status !== 'completed') return deletedLinks;\n\n\t\tconst value = slice(\n\t\t\tthis.notifications,\n\t\t\tdeletedLinks.value.map(l => l[0].hashed.content.target_address),\n\t\t);\n\n\t\treturn {\n\t\t\tstatus: 'completed',\n\t\t\tvalue,\n\t\t};\n\t})" + "name": "readNotifications" }, { "kind": "field", - "name": "notificationsByTypeAndGroup", - "default": "new LazyMap(\n\t\t(notificationType: string) =>\n\t\t\tnew LazyMap((notificationGroup: string) => ({\n\t\t\t\tread: new AsyncComputed(() => {\n\t\t\t\t\tconst notifications = this.readNotifications.get();\n\t\t\t\t\tif (notifications.status !== 'completed') return notifications;\n\n\t\t\t\t\tconst entries = joinAsyncMap(\n\t\t\t\t\t\tmapValues(notifications.value, n => n.entry.get()),\n\t\t\t\t\t);\n\t\t\t\t\tif (entries.status !== 'completed') return entries;\n\n\t\t\t\t\tconst value = pickBy(\n\t\t\t\t\t\tentries.value,\n\t\t\t\t\t\tn =>\n\t\t\t\t\t\t\tn.entry.notification_type === notificationType &&\n\t\t\t\t\t\t\tn.entry.notification_group === notificationGroup,\n\t\t\t\t\t);\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t\tunread: new AsyncComputed(() => {\n\t\t\t\t\tconst notifications = this.unreadNotifications.get();\n\t\t\t\t\tif (notifications.status !== 'completed') return notifications;\n\n\t\t\t\t\tconst entries = joinAsyncMap(\n\t\t\t\t\t\tmapValues(notifications.value, n => n.entry.get()),\n\t\t\t\t\t);\n\t\t\t\t\tif (entries.status !== 'completed') return entries;\n\n\t\t\t\t\tconst value = pickBy(\n\t\t\t\t\t\tentries.value,\n\t\t\t\t\t\tn =>\n\t\t\t\t\t\t\tn.entry.notification_type === notificationType &&\n\t\t\t\t\t\t\tn.entry.notification_group === notificationGroup,\n\t\t\t\t\t);\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t})),\n\t)", - "description": "Helpers for consuming UIs" + "name": "dismissedNotifications" } ] } @@ -601,6 +384,62 @@ "declarations": [], "exports": [] }, + { + "kind": "javascript-module", + "path": "locales/locales.js", + "declarations": [ + { + "kind": "variable", + "name": "sourceLocale", + "default": "`en`", + "description": "The locale code that templates in this source code are written in." + }, + { + "kind": "variable", + "name": "targetLocales", + "type": { + "text": "array" + }, + "default": "[\n ,\n]", + "description": "The other locale codes that this application is localized into. Sorted\nlexicographically." + }, + { + "kind": "variable", + "name": "allLocales", + "type": { + "text": "array" + }, + "default": "[\n `en`,\n]", + "description": "All valid project locale codes. Sorted lexicographically." + } + ], + "exports": [ + { + "kind": "js", + "name": "sourceLocale", + "declaration": { + "name": "sourceLocale", + "module": "locales/locales.js" + } + }, + { + "kind": "js", + "name": "targetLocales", + "declaration": { + "name": "targetLocales", + "module": "locales/locales.js" + } + }, + { + "kind": "js", + "name": "allLocales", + "declaration": { + "name": "allLocales", + "module": "locales/locales.js" + } + } + ] + }, { "kind": "javascript-module", "path": "src/elements/my-notifications-icon-button.ts", @@ -667,12 +506,6 @@ "text": "boolean" } }, - { - "name": "persistent", - "type": { - "text": "boolean" - } - }, { "name": "notificationGroup", "type": { @@ -687,23 +520,6 @@ } ] }, - { - "kind": "method", - "name": "notificationInfo", - "return": { - "type": { - "text": "AsyncResult" - } - }, - "parameters": [ - { - "name": "notificationHash", - "type": { - "text": "ActionHash" - } - } - ] - }, { "kind": "method", "name": "getNotificationsGroups" diff --git a/ui/src/elements/my-notifications-list.ts b/ui/src/elements/my-notifications-list.ts index 9fed2f4a..da1a64f9 100644 --- a/ui/src/elements/my-notifications-list.ts +++ b/ui/src/elements/my-notifications-list.ts @@ -320,9 +320,10 @@ export class MyNotifications extends SignalWatcher(LitElement) { effect="pulse" style="height: 16px; min-width: 250px;" > +
${i < count - 1 ? html`` : html``} diff --git a/ui/src/mocks.ts b/ui/src/mocks.ts index 5ab25eac..8fdd5636 100644 --- a/ui/src/mocks.ts +++ b/ui/src/mocks.ts @@ -1,254 +1,123 @@ +import { wrapPathInSvg } from '@holochain-open-dev/elements'; import { - AgentPubKeyMap, HashType, HoloHashMap, - RecordBag, ZomeMock, - decodeEntry, - entryState, - fakeCreateAction, - fakeCreateLinkAction, - fakeDeleteEntry, - fakeDeleteLinkAction, - fakeEntry, - fakeRecord, - fakeUpdateEntry, hash, - pickBy, - retype, } from '@holochain-open-dev/utils'; import { - ActionHash, AgentPubKey, AppClient, - Delete, EntryHash, - Link, - NewEntryAction, - Record, - SignedActionHashed, - decodeHashFromBase64, + EntryHashB64, encodeHashToBase64, - fakeActionHash, - fakeAgentPubKey, - fakeDnaHash, - fakeEntryHash, } from '@holochain/client'; -import { encode } from '@msgpack/msgpack'; +import { mdiEarth } from '@mdi/js'; -import { NotificationsClient } from './notifications-client.js'; -import { Notification } from './types.js'; +import { + Notification, + NotificationContents, + NotificationStatus, + NotificationsStatusChanges, +} from './types.js'; export class NotificationsZomeMock extends ZomeMock implements AppClient { constructor(myPubKey?: AgentPubKey) { super('notifications_test', 'notifications', myPubKey, 100); } /** Notification */ - notifications = new HoloHashMap< - ActionHash, - { - deletes: Array>; - revisions: Array; - } + notifications = new HoloHashMap(); + notificationsStatusChanges = new HoloHashMap< + EntryHash, + NotificationsStatusChanges >(); - notificationsForRecipient = new HoloHashMap(); - readNotificationsByRecipient = new HoloHashMap>(); - async create_notification(notification: Notification): Promise { + async send_notification(notification: Notification): Promise { + notification.timestamp = Date.now() * 1000; const entryHash = hash(notification, HashType.ENTRY); - const record = await fakeRecord( - await fakeCreateAction(entryHash), - fakeEntry(notification), - ); - - this.notifications.set(record.signed_action.hashed.hash, { - deletes: [], - revisions: [record], - }); - - const recipient = notification.recipient_profile_hash; - - const existingRecipients = - this.notificationsForRecipient.get(recipient) || []; - this.notificationsForRecipient.set(recipient, [ - ...existingRecipients, - { - base: recipient, - target: record.signed_action.hashed.hash, - author: this.myPubKey, - timestamp: Date.now() * 1000, - zome_index: 0, - link_type: 0, - tag: new Uint8Array(), - create_link_hash: await fakeActionHash(), - }, - ]); - - return record; - } - - async get_notification( - notificationHash: ActionHash, - ): Promise { - const notification = this.notifications.get(notificationHash); - return notification ? notification.revisions[0] : undefined; - } - - async get_all_deletes_for_notification( - notificationHash: ActionHash, - ): Promise> | undefined> { - const notification = this.notifications.get(notificationHash); - return notification ? notification.deletes : undefined; - } - - async get_oldest_delete_for_notification( - notificationHash: ActionHash, - ): Promise | undefined> { - const notification = this.notifications.get(notificationHash); - return notification ? notification.deletes[0] : undefined; - } - async delete_notification( - original_notification_hash: ActionHash, - ): Promise { - const record = await fakeRecord( - await fakeDeleteEntry(original_notification_hash), - ); - - this.notifications - .get(original_notification_hash) - .deletes.push(record.signed_action as SignedActionHashed); - - return record.signed_action.hashed.hash; - } - - async get_notifications_for_recipient( - recipient_prolife_hash: ActionHash, - ): Promise> { - return this.notificationsForRecipient.get(recipient_prolife_hash) || []; + this.notifications.set(entryHash, notification); } - async mark_notifications_as_read(input: { - notifications_hashes: ActionHash[]; - my_profile_hash: ActionHash; - }) { - const readNotifications = - this.readNotificationsByRecipient.get(input.my_profile_hash) || []; - - const link = { - base: input.my_profile_hash, - target: input.my_profile_hash, - author: this.myPubKey, + async change_notifications_status( + status_changes: Record, + ) { + const notificationsStatusChanges: NotificationsStatusChanges = { + status_changes, timestamp: Date.now() * 1000, - zome_index: 0, - link_type: 0, - tag: encode(input.notifications_hashes), - create_link_hash: await fakeActionHash(), }; - this.readNotificationsByRecipient.set(input.my_profile_hash, [ - ...readNotifications, - link, - ]); + + const entryHash = hash(notificationsStatusChanges, HashType.ENTRY); + this.notificationsStatusChanges.set(entryHash, notificationsStatusChanges); + this.emitSignal({ - type: 'LinkCreated', - action: { - hashed: { - content: await fakeCreateLinkAction( - retype(input.my_profile_hash, HashType.ENTRY), - input.my_profile_hash, - 1, - encode(input.notifications_hashes), - ), - hash: link.create_link_hash, - }, - }, - link_type: 'ReadNotifications', + type: 'EntryCreated', }); } - async dismiss_notifications(input: { - notifications_hashes: ActionHash[]; - my_profile_hash: ActionHash; - }) { - const notifications_hashes = input.notifications_hashes; - const undismissedNotifications = - this.notificationsForRecipient.get(input.my_profile_hash) || []; - - const filteredNotifications = undismissedNotifications.filter( - link => - !notifications_hashes.find( - n => encodeHashToBase64(n) === encodeHashToBase64(link.target), - ), - ); - - const dismissedNotifications = undismissedNotifications.filter(link => - notifications_hashes.find( - n => encodeHashToBase64(n) === encodeHashToBase64(link.target), - ), - ); - - this.notificationsForRecipient.set( - input.my_profile_hash, - filteredNotifications, - ); - for (const link of dismissedNotifications) { - this.emitSignal({ - type: 'LinkDeleted', - action: { - hashed: { - content: await fakeDeleteLinkAction(link.create_link_hash), - hash: await fakeActionHash(), - }, - }, - create_link_action: { - hashed: { - content: await fakeCreateLinkAction( - link.base, - link.target, - link.link_type, - link.tag, - ), - }, - }, - link_type: 'RecipientToNotifications', - }); + async query_notifications_and_status(): Promise< + Record< + EntryHashB64, + { notification: Notification; status: NotificationStatus } + > + > { + const result: Record< + EntryHashB64, + { notification: Notification; status: NotificationStatus } + > = {}; + + for (const [hash, notification] of Array.from( + this.notifications.entries(), + )) { + result[encodeHashToBase64(hash)] = { + notification, + status: 'Unread', + }; } - } - async get_undismissed_notifications( - myProfileHash: ActionHash, - ): Promise> { - return this.notificationsForRecipient.get(myProfileHash); - } + const sortedNotificationStatusChanges = Array.from( + this.notificationsStatusChanges.entries(), + ).sort((t1, t2) => t1[1].timestamp - t2[1].timestamp); + + for (const [ + hash, + notificationsStatusChanges, + ] of sortedNotificationStatusChanges) { + for (const [notificationHash, status] of Object.entries( + notificationsStatusChanges.status_changes, + )) { + if (result[notificationHash]) { + result[notificationHash].status = status; + } + } + } - async get_dismissed_notifications( - myProfileHash: ActionHash, - ): Promise> { - return []; + return result; } - async get_read_notifications( - myProfileHash: ActionHash, - ): Promise> { - return this.readNotificationsByRecipient.get(myProfileHash) || []; + async query_notifications_with_status( + notificationStatus: NotificationStatus, + ) { + const notifications = await this.query_notifications_and_status(); + + const result: Record = {}; + + for (const [hash, n] of Object.entries(notifications)) { + if (n.status === notificationStatus) { + result[hash] = n.notification; + } + } + + return result; } -} -export async function sampleNotification( - client: NotificationsClient, - partialNotification: Partial = {}, -): Promise { - return { - ...{ - zome_name: 'example', - notification_type: 'type1', - notification_group: 'Your notifications', - persistent: false, - recipient_profile_hash: await fakeActionHash(), - timestamp: Date.now() * 1000, - content: encode({ - body: 'Hello world!', - }), - }, - ...partialNotification, - }; + async get_notification_contents( + notification: Notification, + ): Promise { + return { + title: 'Hello world!', + body: 'This is an example notification', + icon_src: wrapPathInSvg(mdiEarth), + url_path_to_navigate_to_on_click: '', + }; + } } diff --git a/ui/src/notifications-store.ts b/ui/src/notifications-store.ts index 65d37d2d..18752816 100644 --- a/ui/src/notifications-store.ts +++ b/ui/src/notifications-store.ts @@ -1,41 +1,17 @@ -import { ProfilesStore } from '@holochain-open-dev/profiles'; import { - AsyncComputed, AsyncSignal, AsyncState, Signal, - deletedLinksSignal, - deletesForEntrySignal, fromPromise, - immutableEntrySignal, - joinAsyncMap, - liveLinksSignal, - pipe, - uniquify, } from '@holochain-open-dev/signals'; -import { - HoloHashMap, - LazyHoloHashMap, - LazyMap, - mapValues, - pickBy, - slice, -} from '@holochain-open-dev/utils'; -import { - ActionHash, - EntryHash, - EntryHashB64, - Link, - encodeHashToBase64, -} from '@holochain/client'; -import { decode } from '@msgpack/msgpack'; +import { HoloHashMap } from '@holochain-open-dev/utils'; +import { EntryHash, EntryHashB64 } from '@holochain/client'; import { NotificationsClient } from './notifications-client.js'; import { Notification, NotificationContents, NotificationStatus, - NotificationsConfig, } from './types.js'; export class NotificationsStore { @@ -44,13 +20,6 @@ export class NotificationsStore { // public notificationsConfig: NotificationsConfig, ) {} - // addTypes(notificationsType: Record) { - // this.notificationsConfig.types = { - // ...this.notificationsConfig.types, - // ...notificationsType, - // }; - // } - /** Notifications */ private buildQueryNotificationsWithStatusSignal( statusFilter: NotificationStatus, @@ -119,205 +88,4 @@ export class NotificationsStore { dismissedNotifications = this.buildQueryNotificationsWithStatusSignal('Dismissed'); - // notifications = new LazyHoloHashMap((notificationHash: ActionHash) => ({ - // entry: immutableEntrySignal(() => - // this.client.getNotification(notificationHash), - // ), - // deletes: deletesForEntrySignal(this.client, notificationHash, () => - // this.client.getAllDeletesForNotification(notificationHash), - // ), - // })); - - // private myProfileExistsOrPending = pipe( - // this.client.profilesStore.myProfile, - // myProfile => - // myProfile !== undefined - // ? myProfile - // : { - // status: 'pending', - // }, - // ); - - // private undismissedNotificationsLinks = pipe( - // this.myProfileExistsOrPending, - // myProfile => - // liveLinksSignal( - // this.client, - // myProfile.profileHash, - // () => this.client.getUndismissedNotifications(), - // 'RecipientToNotifications', - // 5000, - // ), - // ); - - // private readNotificationsLinks = pipe( - // this.myProfileExistsOrPending, - // myProfile => - // liveLinksSignal( - // this.client, - // myProfile.profileHash, - // () => this.client.getReadNotifications(), - // 'ReadNotifications', - // ), - // ); - - // readNotifications = new AsyncComputed(() => { - // const readNotificationsLinks = this.readNotificationsLinks.get(); - // const undismissedNotifications = this.undismissedNotificationsLinks.get(); - // if (readNotificationsLinks.status !== 'completed') - // return readNotificationsLinks; - // if (undismissedNotifications.status !== 'completed') - // return undismissedNotifications; - - // /** Aggregate the read notification hashes and filter them by whether they've been dismissed */ - - // const allReadNotificationsHashes = uniquify( - // Array.from([] as ActionHash[]).concat( - // ...readNotificationsLinks.value.map( - // link => decode(link.tag) as ActionHash[], - // ), - // ), - // ); - - // const undismissedNotificationsHashes = undismissedNotifications.value.map( - // l => encodeHashToBase64(l.target), - // ); - - // const notificationsHashes = allReadNotificationsHashes.filter(hash => - // undismissedNotificationsHashes.includes(encodeHashToBase64(hash)), - // ); - - // /* If a notification was persistent and has been read but was deleted (usually by someone else performing the action that the notification required), then we dismiss the notification */ - - // // const deletes = joinAsync( - // // notificationsHashes.map(hash => - // // this.notifications.get(hash).deletes.get(), - // // ), - // // ); - // // const entries = joinAsync( - // // notificationsHashes.map(hash => - // // this.notifications.get(hash).entry.get(), - // // ), - // // ); - // // if (entries.status !== 'completed') return entries; - // // if (deletes.status !== 'completed') return deletes; - - // // const nonDeletedNotificationHashes: ActionHash[] = []; - // // const notificationsToDismiss: ActionHash[] = []; - - // // for (let i = 0; i < notificationsHashes.length; i++) { - // // if (!entries.value[i].entry.persistent || deletes.value[i].length === 0) { - // // nonDeletedNotificationHashes.push(notificationsHashes[i]); - // // } else { - // // notificationsToDismiss.push(notificationsHashes[i]); - // // } - // // } - - // // if (notificationsToDismiss.length > 0) { - // // this.client.dismissNotifications(notificationsToDismiss); - // // } - - // const value = slice(this.notifications, notificationsHashes); - // return { - // status: 'completed', - // value, - // }; - // }); - - // unreadNotifications = new AsyncComputed(() => { - // const readNotifications = this.readNotifications.get(); - // const undismissedNotifications = this.undismissedNotificationsLinks.get(); - - // if (readNotifications.status !== 'completed') return readNotifications; - // if (undismissedNotifications.status !== 'completed') - // return undismissedNotifications; - - // const readNotificationsHashes = Array.from( - // readNotifications.value.keys(), - // ).map(h => encodeHashToBase64(h)); - - // const links = undismissedNotifications.value.filter( - // link => - // !readNotificationsHashes.includes(encodeHashToBase64(link.target)), - // ); - // const value = slice(this.notifications, uniquify(links.map(l => l.target))); - // return { - // status: 'completed', - // value, - // }; - // }); - - // deletedNotificationsLinks = pipe(this.myProfileExistsOrPending, myProfile => - // deletedLinksSignal( - // this.client, - // myProfile.profileHash, - // () => this.client.getDismissedNotifications(), - // 'RecipientToNotifications', - // ), - // ); - - // dismissedNotifications = new AsyncComputed(() => { - // const deletedLinks = this.deletedNotificationsLinks.get(); - // if (deletedLinks.status !== 'completed') return deletedLinks; - - // const value = slice( - // this.notifications, - // deletedLinks.value.map(l => l[0].hashed.content.target_address), - // ); - - // return { - // status: 'completed', - // value, - // }; - // }); - - /** Helpers for consuming UIs */ - - // notificationsByTypeAndGroup = new LazyMap( - // (notificationType: string) => - // new LazyMap((notificationGroup: string) => ({ - // read: new AsyncComputed(() => { - // const notifications = this.readNotifications.get(); - // if (notifications.status !== 'completed') return notifications; - - // const entries = joinAsyncMap( - // mapValues(notifications.value, n => n.entry.get()), - // ); - // if (entries.status !== 'completed') return entries; - - // const value = pickBy( - // entries.value, - // n => - // n.entry.notification_type === notificationType && - // n.entry.notification_group === notificationGroup, - // ); - - // return { - // status: 'completed', - // value, - // }; - // }), - // unread: new AsyncComputed(() => { - // const notifications = this.unreadNotifications.get(); - // if (notifications.status !== 'completed') return notifications; - - // const entries = joinAsyncMap( - // mapValues(notifications.value, n => n.entry.get()), - // ); - // if (entries.status !== 'completed') return entries; - - // const value = pickBy( - // entries.value, - // n => - // n.entry.notification_type === notificationType && - // n.entry.notification_group === notificationGroup, - // ); - - // return { - // status: 'completed', - // value, - // }; - // }), - // })), - // ); } diff --git a/ui/src/types.ts b/ui/src/types.ts index 4fb60a7d..68616158 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -1,6 +1,6 @@ import { AsyncSignal } from '@holochain-open-dev/signals'; import { ActionCommittedSignal, EntryRecord } from '@holochain-open-dev/utils'; -import { ActionHash } from '@holochain/client'; +import { ActionHash, EntryHashB64 } from '@holochain/client'; export type NotificationsSignal = ActionCommittedSignal; @@ -23,6 +23,11 @@ export interface Notification { export type NotificationStatus = 'Unread' | 'Read' | 'Dismissed'; +export interface NotificationsStatusChanges { + status_changes: Record; + timestamp: number; +} + export interface NotificationsConfig { // types: Record;