From 4635c1ac481da1d98c016a4a573bf147fa0d2b87 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Wed, 27 Mar 2024 19:57:31 +0800 Subject: [PATCH 1/8] Feed filters dialog --- src/components/FeedFilters.vue | 190 +++++++++++++++++++++++++++++++++ src/views/HomeView.vue | 17 ++- 2 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/components/FeedFilters.vue diff --git a/src/components/FeedFilters.vue b/src/components/FeedFilters.vue new file mode 100644 index 00000000..c711eb5d --- /dev/null +++ b/src/components/FeedFilters.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 4edf0b99..e6172ce4 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -172,10 +172,19 @@ showGivenToUser="true" /> +
-

Latest Activity

+
+

Latest Activity

+ +
  • From 7f66addfe313869d7b9019c1e7bbcfb4bd8ae8bc Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Fri, 29 Mar 2024 15:53:46 +0800 Subject: [PATCH 2/8] Filter options reduced for release --- src/components/FeedFilters.vue | 48 ---------------------------------- 1 file changed, 48 deletions(-) diff --git a/src/components/FeedFilters.vue b/src/components/FeedFilters.vue index c711eb5d..1f054109 100644 --- a/src/components/FeedFilters.vue +++ b/src/components/FeedFilters.vue @@ -38,54 +38,6 @@
-
- -
From my projects
- -
- - - -
- -
-
-
- -
- -
Matched to my topics of interest
- -
- - - -
- -
-
-
- -
- -
Offers
- -
- - - -
- -
-
-
-
Nearby
From cfd53bc186a98e47f8e394635dbcfc4cbb5dc50e Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Fri, 29 Mar 2024 15:55:16 +0800 Subject: [PATCH 3/8] Removed one more --- src/components/FeedFilters.vue | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/components/FeedFilters.vue b/src/components/FeedFilters.vue index 1f054109..0b3cae5c 100644 --- a/src/components/FeedFilters.vue +++ b/src/components/FeedFilters.vue @@ -6,22 +6,6 @@

Show only activities that are…

-
- -
From me
- -
- - - -
- -
-
-
-
From my contacts
From 0b4f2484f75acd90a8c116522fd02e2cafbcf50a Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Fri, 29 Mar 2024 21:41:14 +0800 Subject: [PATCH 4/8] Additions to Account View --- src/views/AccountViewView.vue | 38 ++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index b1dd6e6f..5f6c78e2 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -112,6 +112,8 @@
+ +
Settings
- + Troubleshoot your notification setup. + + + Set Search Area… + + + +
+ Topics of Interest +
+ +
+ Separate topics with a comma. +
-
Usage Limits
+
Usage Limits
Checking… @@ -200,7 +224,7 @@
-
Data Export
+
Data Export
Download Settings & Contacts @@ -221,7 +245,7 @@ If no download happened yet, click again here to download now. @@ -952,13 +976,13 @@ export default class AccountViewView extends Vue { public computedStartDownloadLinkClassNames() { return { - invisible: this.downloadUrl, + hidden: this.downloadUrl, }; } public computedDownloadLinkClassNames() { return { - invisible: !this.downloadUrl, + hidden: !this.downloadUrl, }; } From e90a0be6d982bc7afee20ed312f1b2657a517981 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Wed, 3 Apr 2024 15:51:23 +0800 Subject: [PATCH 5/8] Names and variables for filter toggles --- src/components/FeedFilters.vue | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/FeedFilters.vue b/src/components/FeedFilters.vue index 0b3cae5c..390205b1 100644 --- a/src/components/FeedFilters.vue +++ b/src/components/FeedFilters.vue @@ -12,7 +12,12 @@
- +
@@ -28,7 +33,12 @@
- +
@@ -81,6 +91,8 @@ import { }) export default class FeedFilters extends Vue { visible = false; + isFromMyContacts = false; + isNearby = false; async open() { this.visible = true; From 8e1daf7015efc5910199f33845c056db6212c999 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Wed, 3 Apr 2024 19:54:01 -0600 Subject: [PATCH 6/8] feed filter: save the changed values to the DB, go to map if no location chosen, reload if necessary --- project.task.yaml | 1 + src/components/FeedFilters.vue | 115 +++++++++++++++++++++++++++------ src/db/tables/settings.ts | 4 ++ src/views/HomeView.vue | 28 ++++++-- src/views/SearchAreaView.vue | 5 +- 5 files changed, 129 insertions(+), 24 deletions(-) diff --git a/project.task.yaml b/project.task.yaml index 5a8b1cce..d111576c 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -19,6 +19,7 @@ tasks : - .2 don't show a warning on a totally new project when the authorized agent is set - .2 anchor hash into BTC - .2 list the "show more" contacts alphabetically +- .5 add back the explicit wait for browser subscription timing problems? - .5 make Time Safari a share_target for images diff --git a/src/components/FeedFilters.vue b/src/components/FeedFilters.vue index 390205b1..669e9531 100644 --- a/src/components/FeedFilters.vue +++ b/src/components/FeedFilters.vue @@ -6,7 +6,10 @@

Show only activities that are…

-
+
From my contacts
@@ -14,7 +17,7 @@ @@ -27,11 +30,18 @@
-
+
Nearby
-
+
+
+ +
- -
+
+
@@ -81,6 +97,9 @@ import { LTileLayer, } from "@vue-leaflet/vue-leaflet"; +import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; +import { db } from "@/db/index"; + @Component({ components: { LRectangle, @@ -90,20 +109,80 @@ import { }, }) export default class FeedFilters extends Vue { - visible = false; - isFromMyContacts = false; + callOnCloseIfChanged = () => {}; + hasSearchBox = false; + isInMyContacts = false; isNearby = false; + settingChanged = false; + visible = false; + + async open(callOnCloseIfChanged: () => void) { + this.callOnCloseIfChanged = callOnCloseIfChanged; + + await db.open(); + const settings = await db.settings.get(MASTER_SETTINGS_KEY); + this.isInMyContacts = !!settings?.filterFeedContacts; + this.isNearby = !!settings?.filterFeedNearby; + if (settings?.searchBoxes && settings.searchBoxes.length > 0) { + this.hasSearchBox = true; + } - async open() { + this.settingChanged = false; this.visible = true; } + toggleContacts() { + this.settingChanged = true; + this.isInMyContacts = !this.isInMyContacts; + db.settings.update(MASTER_SETTINGS_KEY, { + filterFeedContacts: this.isInMyContacts, + }); + } + + toggleNearby() { + this.settingChanged = true; + this.isNearby = !this.isNearby; + db.settings.update(MASTER_SETTINGS_KEY, { + filterFeedNearby: this.isNearby, + }); + } + + async clearAll() { + if (this.isInMyContacts || this.isNearby) { + this.settingChanged = true; + } + + db.settings.update(MASTER_SETTINGS_KEY, { + filterFeedNearby: false, + filterFeedContacts: false, + }); + + this.isInMyContacts = false; + this.isNearby = false; + } + + async setAll() { + if (!this.isInMyContacts || !this.isNearby) { + this.settingChanged = true; + } + + db.settings.update(MASTER_SETTINGS_KEY, { + filterFeedNearby: true, + filterFeedContacts: true, + }); + + this.isInMyContacts = true; + this.isNearby = true; + } + close() { - // close the dialog but don't change values (just in case some actions are added later) + if (this.settingChanged) { + this.callOnCloseIfChanged(); + } this.visible = false; } - cancel() { + done() { this.close(); } } diff --git a/src/db/tables/settings.ts b/src/db/tables/settings.ts index f95b3839..479ee637 100644 --- a/src/db/tables/settings.ts +++ b/src/db/tables/settings.ts @@ -16,6 +16,10 @@ export type Settings = { activeDid?: string; // Active Decentralized ID apiServer?: string; // API server URL + + filterFeedNearby?: string; // filter by nearby + filterFeedContacts?: string; // filter by user contacts + firstName?: string; // User's first name isRegistered?: boolean; lastName?: string; // deprecated - put all names in firstName diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index e6172ce4..41941517 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -180,9 +180,9 @@

Latest Activity

@@ -311,6 +311,8 @@ export default class HomeView extends Vue { feedPreviousOldestId?: string; feedLastViewedClaimId?: string; isCreatingIdentifier = false; + isFeedFilteredByContacts = false; + isFeedFilteredByNearby = false; isFeedLoading = true; isRegistered = false; showShortcutBvc = false; @@ -335,7 +337,7 @@ export default class HomeView extends Vue { return headers; } - async created() { + async mounted() { try { await accountsDB.open(); const allAccounts = await accountsDB.accounts.toArray(); @@ -347,6 +349,8 @@ export default class HomeView extends Vue { this.activeDid = settings?.activeDid || ""; this.allContacts = await db.contacts.toArray(); this.feedLastViewedClaimId = settings?.lastViewedClaimId; + this.isFeedFilteredByContacts = !!settings?.filterFeedContacts; + this.isFeedFilteredByNearby = !!settings?.filterFeedNearby; this.isRegistered = !!settings?.isRegistered; this.showShortcutBvc = !!settings?.showShortcutBvc; @@ -378,6 +382,10 @@ export default class HomeView extends Vue { } } + resultsAreFiltered() { + return this.isFeedFilteredByContacts || this.isFeedFilteredByNearby; + } + notificationsSupported() { return "Notification" in window; } @@ -408,6 +416,18 @@ export default class HomeView extends Vue { return headers; } + // only called when a setting was changed + async reloadFeedOnChange() { + await db.open(); + const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; + this.isFeedFilteredByContacts = !!settings?.filterFeedContacts; + this.isFeedFilteredByNearby = !!settings?.filterFeedNearby; + + this.feedData = []; + this.feedPreviousOldestId = undefined; + this.updateAllFeed(); + } + /** * Data loader used by infinite scroller * @param payload is the flag from the InfiniteScroll indicating if it should load @@ -587,7 +607,7 @@ export default class HomeView extends Vue { } openFeedFilters() { - (this.$refs.feedFilters as FeedFilters).open(); + (this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange); } } diff --git a/src/views/SearchAreaView.vue b/src/views/SearchAreaView.vue index 4b3a7501..e9c30e85 100644 --- a/src/views/SearchAreaView.vue +++ b/src/views/SearchAreaView.vue @@ -208,9 +208,9 @@ export default class DiscoverView extends Vue { group: "alert", type: "success", title: "Saved", - text: "That has been saved in your preferences.", + text: "That has been saved in your preferences. You can now filter by it on your home screen feed.", }, - -1, + 7000, ); this.$router.back(); } catch (err) { @@ -246,6 +246,7 @@ export default class DiscoverView extends Vue { await db.open(); db.settings.update(MASTER_SETTINGS_KEY, { searchBoxes: [], + filterFeedNearby: false, }); this.searchBox = null; this.localCenterLat = 0; From 640d2736468fe2d514fbfb3989af02b9a40bea72 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sat, 6 Apr 2024 14:01:18 -0600 Subject: [PATCH 7/8] filter by selections (now all working), add cache for plans --- babel.config.js | 1 + package-lock.json | 149 ++++++++++++++++++++++++++-- package.json | 1 + project.task.yaml | 5 +- src/components/FeedFilters.vue | 50 +++++----- src/db/tables/settings.ts | 8 +- src/libs/endorserServer.ts | 94 +++++++++++++++--- src/libs/util.ts | 14 +-- src/views/ContactAmountsView.vue | 10 +- src/views/ContactsView.vue | 4 +- src/views/HomeView.vue | 113 ++++++++++++++++----- src/views/ProjectViewView.vue | 30 +++--- src/views/ProjectsView.vue | 4 +- src/views/QuickActionBvcEndView.vue | 6 +- src/views/SearchAreaView.vue | 2 +- 15 files changed, 384 insertions(+), 107 deletions(-) diff --git a/babel.config.js b/babel.config.js index 162a3ea9..cfbc5715 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,4 @@ module.exports = { + plugins: ["@babel/plugin-transform-private-methods"], presets: ["@vue/cli-plugin-babel/preset"], }; diff --git a/package-lock.json b/package-lock.json index 1cbcd8ad..4c5c5683 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "js-generate-password": "^0.1.9", "js-yaml": "^4.1.0", "localstorage-slim": "^2.5.0", + "lru-cache": "^10.2.0", "luxon": "^3.4.4", "merkletreejs": "^0.3.11", "moment": "^2.29.4", @@ -3052,6 +3053,18 @@ "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==", "optional": true }, + "node_modules/@digitalcredentials/jsonld/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@digitalcredentials/keypair": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@digitalcredentials/keypair/-/keypair-1.0.5.tgz", @@ -3149,6 +3162,18 @@ "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==", "optional": true }, + "node_modules/@digitalcredentials/vc-status-list/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -4317,6 +4342,19 @@ "node": ">=4" } }, + "node_modules/@expo/cli/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@expo/cli/node_modules/mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", @@ -4714,6 +4752,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@expo/config/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@expo/config/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -8912,6 +8963,18 @@ "node": ">=12" } }, + "node_modules/@transmute/jsonld/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@transmute/ld-key-pair": { "version": "0.7.0-unstable.81", "resolved": "https://registry.npmjs.org/@transmute/ld-key-pair/-/ld-key-pair-0.7.0-unstable.81.tgz", @@ -9674,6 +9737,18 @@ "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==", "optional": true }, + "node_modules/@veramo-community/lds-ecdsa-secp256k1-recovery2020/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@veramo/core": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/@veramo/core/-/core-5.5.3.tgz", @@ -10378,6 +10453,18 @@ "node": ">=8" } }, + "node_modules/@vue/cli-shared-utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@vue/cli-shared-utils/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12425,6 +12512,19 @@ "node": ">= 10" } }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cacache/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -17014,6 +17114,19 @@ "node": ">=10" } }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -19269,6 +19382,18 @@ "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==", "optional": true }, + "node_modules/jsonld/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsonpointer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", @@ -20246,15 +20371,11 @@ } }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "devOptional": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "engines": { - "node": ">=10" + "node": "14 || >=16.14" } }, "node_modules/luxon": { @@ -25124,6 +25245,18 @@ "node": ">=10" } }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "devOptional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", diff --git a/package.json b/package.json index 5425d242..2487cff7 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "js-generate-password": "^0.1.9", "js-yaml": "^4.1.0", "localstorage-slim": "^2.5.0", + "lru-cache": "^10.2.0", "luxon": "^3.4.4", "merkletreejs": "^0.3.11", "moment": "^2.29.4", diff --git a/project.task.yaml b/project.task.yaml index d111576c..c8db872c 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -1,10 +1,13 @@ tasks : +- fix the notification link to the app - 24 contextual tutorials https://docs.google.com/document/d/11C_K3RM0rgo0onih20KFhcIzukZyq_CRWqaWX5om_kM/edit#heading=h.iwiwcydou5hw - 24 Move to Vite +- feeds - add "remote" filter, if they choose 'visible' then warn that they won't see any others, cache list & don't reload front page on change + - .1 add KindSpring link to ideas - .1 on feed, don't show "to someone anonymous" if it's to a project - .1 on ideas, put an "x" to close it assignee-group:ui @@ -122,7 +125,7 @@ tasks : - 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie) - .5 show seed phrase in a QR code for transfer to another device -- .5 on DiscoverView, switch to a filter UI (eg. just from friend +- .5 on DiscoverView, switch to a filter UI (eg. just from friend) - .5 don't show "Offer" on project screen if they aren't registered - 01 especially for iOS, check for new version & update, eg. https://stackoverflow.com/questions/52221805/any-way-yet-to-auto-update-or-just-clear-the-cache-on-a-pwa-on-ios diff --git a/src/components/FeedFilters.vue b/src/components/FeedFilters.vue index 669e9531..a8b029af 100644 --- a/src/components/FeedFilters.vue +++ b/src/components/FeedFilters.vue @@ -3,21 +3,21 @@

Feed Filters

-

Show only activities that are…

+

Show only activities that…

-
From my contacts
+
Include Someone Visible to Me
@@ -30,6 +30,8 @@
+ or +
-
Nearby
+
Are Nearby
@@ -109,20 +111,20 @@ import { db } from "@/db/index"; }, }) export default class FeedFilters extends Vue { - callOnCloseIfChanged = () => {}; + onCloseIfChanged = () => {}; hasSearchBox = false; - isInMyContacts = false; + hasVisibleDid = false; isNearby = false; settingChanged = false; visible = false; - async open(callOnCloseIfChanged: () => void) { - this.callOnCloseIfChanged = callOnCloseIfChanged; + async open(onCloseIfChanged: () => void) { + this.onCloseIfChanged = onCloseIfChanged; await db.open(); const settings = await db.settings.get(MASTER_SETTINGS_KEY); - this.isInMyContacts = !!settings?.filterFeedContacts; - this.isNearby = !!settings?.filterFeedNearby; + this.hasVisibleDid = !!settings?.filterFeedByVisible; + this.isNearby = !!settings?.filterFeedByNearby; if (settings?.searchBoxes && settings.searchBoxes.length > 0) { this.hasSearchBox = true; } @@ -131,11 +133,11 @@ export default class FeedFilters extends Vue { this.visible = true; } - toggleContacts() { + toggleHasVisibleDid() { this.settingChanged = true; - this.isInMyContacts = !this.isInMyContacts; + this.hasVisibleDid = !this.hasVisibleDid; db.settings.update(MASTER_SETTINGS_KEY, { - filterFeedContacts: this.isInMyContacts, + filterFeedByVisible: this.hasVisibleDid, }); } @@ -143,41 +145,41 @@ export default class FeedFilters extends Vue { this.settingChanged = true; this.isNearby = !this.isNearby; db.settings.update(MASTER_SETTINGS_KEY, { - filterFeedNearby: this.isNearby, + filterFeedByNearby: this.isNearby, }); } async clearAll() { - if (this.isInMyContacts || this.isNearby) { + if (this.hasVisibleDid || this.isNearby) { this.settingChanged = true; } db.settings.update(MASTER_SETTINGS_KEY, { - filterFeedNearby: false, - filterFeedContacts: false, + filterFeedByNearby: false, + filterFeedByVisible: false, }); - this.isInMyContacts = false; + this.hasVisibleDid = false; this.isNearby = false; } async setAll() { - if (!this.isInMyContacts || !this.isNearby) { + if (!this.hasVisibleDid || !this.isNearby) { this.settingChanged = true; } db.settings.update(MASTER_SETTINGS_KEY, { - filterFeedNearby: true, - filterFeedContacts: true, + filterFeedByNearby: true, + filterFeedByVisible: true, }); - this.isInMyContacts = true; + this.hasVisibleDid = true; this.isNearby = true; } close() { if (this.settingChanged) { - this.callOnCloseIfChanged(); + this.onCloseIfChanged(); } this.visible = false; } diff --git a/src/db/tables/settings.ts b/src/db/tables/settings.ts index 479ee637..dbc4cf0c 100644 --- a/src/db/tables/settings.ts +++ b/src/db/tables/settings.ts @@ -17,8 +17,8 @@ export type Settings = { activeDid?: string; // Active Decentralized ID apiServer?: string; // API server URL - filterFeedNearby?: string; // filter by nearby - filterFeedContacts?: string; // filter by user contacts + filterFeedByNearby?: boolean; // filter by nearby + filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden firstName?: string; // User's first name isRegistered?: boolean; @@ -42,6 +42,10 @@ export type Settings = { webPushServer?: string; // Web Push server URL }; +export function isAnyFeedFilterOn(settings: Settings): boolean { + return !!(settings.filterFeedByNearby || settings.filterFeedByVisible); +} + /** * Schema for the Settings table in the database. */ diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 5972708c..b1676e9f 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -1,9 +1,11 @@ +import { Axios, AxiosResponse, RawAxiosRequestHeaders } from "axios"; +import * as didJwt from "did-jwt"; +import { LRUCache } from "lru-cache"; import * as R from "ramda"; import { IIdentifier } from "@veramo/core"; -import { accessToken, SimpleSigner } from "@/libs/crypto"; -import * as didJwt from "did-jwt"; -import { Axios, AxiosResponse } from "axios"; + import { Contact } from "@/db/tables/contacts"; +import { accessToken, SimpleSigner } from "@/libs/crypto"; export const SCHEMA_ORG_CONTEXT = "https://schema.org"; // the object in RegisterAction claims @@ -49,7 +51,7 @@ export interface GenericVerifiableCredential { [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any } -export interface GenericServerRecord extends GenericVerifiableCredential { +export interface GenericCredWrapper extends GenericVerifiableCredential { handleId?: string; id: string; issuedAt: string; @@ -58,7 +60,7 @@ export interface GenericServerRecord extends GenericVerifiableCredential { claim: Record; claimType?: string; } -export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = { +export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper = { "@context": SCHEMA_ORG_CONTEXT, "@type": "", claim: {}, @@ -68,7 +70,7 @@ export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = { }; // a summary record; the VC is found the fullClaim field -export interface GiveServerRecord { +export interface GiveSummaryRecord { agentDid: string; amount: number; amountConfirmed: number; @@ -83,7 +85,7 @@ export interface GiveServerRecord { } // a summary record; the VC is found the fullClaim field -export interface OfferServerRecord { +export interface OfferSummaryRecord { amount: number; amountGiven: number; amountGivenConfirmed: number; @@ -101,7 +103,7 @@ export interface OfferServerRecord { } // a summary record; the VC is not currently part of this record -export interface PlanServerRecord { +export interface PlanSummaryRecord { agentDid?: string; // optional, if the issuer wants someone else to manage as well description: string; endTime?: string; @@ -256,6 +258,10 @@ export type CreateAndSubmitClaimResult = SuccessResult | ErrorResult; // See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6 const HIDDEN_DID = "did:none:HIDDEN"; +const planCache: LRUCache = new LRUCache({ + max: 500, +}); + export function isDid(did: string) { return did.startsWith("did:"); } @@ -269,7 +275,7 @@ export function isEmptyOrHiddenDid(did?: string) { } /** - * @return true for any nested string where func(input) === true + * @return true for any string within this primitive/object/array where func(input) === true * * Similar logic is found in endorser-mobile. */ @@ -304,6 +310,12 @@ export function containsHiddenDid(obj: any) { return testRecursivelyOnStrings(isHiddenDid, obj); } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const containsNonHiddenDid = (obj: any) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return testRecursivelyOnStrings((s: any) => isDid(s) && !isHiddenDid(s), obj); +}; + export function stripEndorserPrefix(claimId: string) { if (claimId && claimId.startsWith(ENDORSER_CH_HANDLE_PREFIX)) { return claimId.substring(ENDORSER_CH_HANDLE_PREFIX.length); @@ -423,6 +435,62 @@ export function didInfo( return didInfoForContact(did, activeDid, contact, allMyDids).displayName; } +async function getHeaders(identity: IIdentifier) { + const headers: RawAxiosRequestHeaders = { + "Content-Type": "application/json", + }; + if (identity) { + const token = await accessToken(identity); + headers["Authorization"] = "Bearer " + token; + } + return headers; +} + +export async function getPlanFromCache( + handleId: string, + identity: IIdentifier, + axios: Axios, + apiServer: string, +) { + let cred = planCache.get(handleId); + if (!cred) { + const url = + apiServer + + "/api/v2/report/plans?handleId=" + + encodeURIComponent(handleId); + const headers = await getHeaders(identity); + try { + const resp = await axios.get(url, { headers }); + if (resp.status === 200 && resp.data?.data?.length > 0) { + cred = resp.data.data[0]; + planCache.set(handleId, cred); + } else { + console.log( + "Failed to load plan with handle", + handleId, + " Got data:", + resp.data, + ); + } + } catch (error) { + console.log( + "Failed to load plan with handle", + handleId, + " Got error:", + error, + ); + } + } + return cred; +} + +export async function setPlanInCache( + handleId: string, + planSummary: PlanSummaryRecord, +) { + planCache.set(handleId, planSummary); +} + /** * For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim * @@ -475,7 +543,7 @@ export async function createAndSubmitGive( vcClaim.image = imageUrl; } return createAndSubmitClaim( - vcClaim as GenericServerRecord, + vcClaim as GenericCredWrapper, identity, apiServer, axios, @@ -524,7 +592,7 @@ export async function createAndSubmitOffer( }; } return createAndSubmitClaim( - vcClaim as GenericServerRecord, + vcClaim as GenericCredWrapper, identity, apiServer, axios, @@ -695,7 +763,7 @@ const claimSummary = (claim: Record) => { similar code is also contained in endorser-mobile **/ export const claimSpecialDescription = ( - record: GenericServerRecord, + record: GenericCredWrapper, activeDid: string, identifiers: Array, contacts: Array, @@ -789,7 +857,7 @@ export const claimSpecialDescription = ( "...]" ); } else { - return issuer + " declared " + claimSummary(claim as GenericServerRecord); + return issuer + " declared " + claimSummary(claim as GenericCredWrapper); } }; diff --git a/src/libs/util.ts b/src/libs/util.ts index 1a613df8..f85f550f 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -9,7 +9,7 @@ import { accountsDB, db } from "@/db/index"; import { Account } from "@/db/tables/accounts"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto"; -import { GenericServerRecord, containsHiddenDid } from "@/libs/endorserServer"; +import { GenericCredWrapper, containsHiddenDid } from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer"; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -69,7 +69,7 @@ export const isGlobalUri = (uri: string) => { return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/)); }; -export const giveIsConfirmable = (veriClaim: GenericServerRecord) => { +export const giveIsConfirmable = (veriClaim: GenericCredWrapper) => { return veriClaim.claimType === "GiveAction"; }; @@ -85,7 +85,7 @@ export const doCopyTwoSecRedo = (text: string, fn: () => void) => { * @param veriClaim is expected to have fields: claim, claimType, and issuer */ export const isGiveRecordTheUserCanConfirm = ( - veriClaim: GenericServerRecord, + veriClaim: GenericCredWrapper, activeDid: string, confirmerIdList: string[] = [], ) => { @@ -101,9 +101,9 @@ export const isGiveRecordTheUserCanConfirm = ( * @returns the DID of the person who offered, or undefined if hidden * @param veriClaim is expected to have fields: claim and issuer */ -export const offerGiverDid: ( - arg0: GenericServerRecord, -) => string | undefined = (veriClaim) => { +export const offerGiverDid: (arg0: GenericCredWrapper) => string | undefined = ( + veriClaim, +) => { let giver; if ( veriClaim.claim.offeredBy?.identifier && @@ -120,7 +120,7 @@ export const offerGiverDid: ( * @returns true if the user can fulfill the offer * @param veriClaim is expected to have fields: claim, claimType, and issuer */ -export const canFulfillOffer = (veriClaim: GenericServerRecord) => { +export const canFulfillOffer = (veriClaim: GenericCredWrapper) => { return !!(veriClaim.claimType === "Offer" && offerGiverDid(veriClaim)); }; diff --git a/src/views/ContactAmountsView.vue b/src/views/ContactAmountsView.vue index a6fc0ff9..1d3654ab 100644 --- a/src/views/ContactAmountsView.vue +++ b/src/views/ContactAmountsView.vue @@ -119,7 +119,7 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { accessToken, SimpleSigner } from "@/libs/crypto"; import { AgreeVerifiableCredential, - GiveServerRecord, + GiveSummaryRecord, GiveVerifiableCredential, SCHEMA_ORG_CONTEXT, } from "@/libs/endorserServer"; @@ -131,7 +131,7 @@ export default class ContactAmountssView extends Vue { activeDid = ""; apiServer = ""; contact: Contact | null = null; - giveRecords: Array = []; + giveRecords: Array = []; numAccounts = 0; async beforeCreate() { @@ -197,7 +197,7 @@ export default class ContactAmountssView extends Vue { async loadGives(activeDid: string, contact: Contact) { try { const identity = await this.getIdentity(this.activeDid); - let result: Array = []; + let result: Array = []; const url = this.apiServer + "/api/v2/report/gives?agentDid=" + @@ -252,7 +252,7 @@ export default class ContactAmountssView extends Vue { ); } - const sortedResult: Array = R.sort( + const sortedResult: Array = R.sort( (a, b) => new Date(b.issuedAt).getTime() - new Date(a.issuedAt).getTime(), result, @@ -271,7 +271,7 @@ export default class ContactAmountssView extends Vue { } } - async confirm(record: GiveServerRecord) { + async confirm(record: GiveSummaryRecord) { // Make claim // I use clone here because otherwise it gets a Proxy object. // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index 8384163d..2bf4a9a8 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -303,7 +303,7 @@ import { import { CONTACT_CSV_HEADER, CONTACT_URL_PREFIX, - GiveServerRecord, + GiveSummaryRecord, GiveVerifiableCredential, isDid, RegisterVerifiableCredential, @@ -410,7 +410,7 @@ export default class ContactsView extends Vue { } const handleResponse = ( - resp: { status: number; data: { data: GiveServerRecord[] } }, + resp: { status: number; data: { data: GiveSummaryRecord[] } }, descriptions: Record, confirmed: Record, unconfirmed: Record, diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 41941517..57dca406 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -247,11 +247,17 @@ Loading…

+
+

+ No claims match your filters. +

+