filter by selections (now all working), add cache for plans

This commit is contained in:
2024-04-06 14:01:18 -06:00
parent e3696e3ac5
commit 3fbf68b117
15 changed files with 384 additions and 107 deletions

View File

@@ -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<GiveServerRecord> = [];
giveRecords: Array<GiveSummaryRecord> = [];
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<GiveServerRecord> = [];
let result: Array<GiveSummaryRecord> = [];
const url =
this.apiServer +
"/api/v2/report/gives?agentDid=" +
@@ -252,7 +252,7 @@ export default class ContactAmountssView extends Vue {
);
}
const sortedResult: Array<GiveServerRecord> = R.sort(
const sortedResult: Array<GiveSummaryRecord> = 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

View File

@@ -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<string, string>,
confirmed: Record<string, number>,
unconfirmed: Record<string, number>,

View File

@@ -247,11 +247,17 @@
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
</p>
</div>
<div v-if="!isFeedLoading && feedData.length === 0">
<p class="text-slate-500 text-center italic mt-4 mb-4">
No claims match your filters.
</p>
</div>
</div>
</section>
</template>
<script lang="ts">
import * as R from "ramda";
import { UAParser } from "ua-parser-js";
import { IIdentifier } from "@veramo/core";
import { Component, Vue } from "vue-facing-decorator";
@@ -267,17 +273,24 @@ import { NotificationIface } from "@/constants/app";
import { db, accountsDB } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import {
BoundingBox,
isAnyFeedFilterOn,
MASTER_SETTINGS_KEY,
Settings,
} from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import {
contactForDid,
containsNonHiddenDid,
didInfoForContact,
getPlanFromCache,
GiverInputInfo,
GiveServerRecord,
GiveSummaryRecord,
} from "@/libs/endorserServer";
import { generateSaveAndActivateIdentity } from "@/libs/util";
interface GiveRecordWithContactInfo extends GiveServerRecord {
interface GiveRecordWithContactInfo extends GiveSummaryRecord {
giver: {
displayName: string;
known: boolean;
@@ -310,11 +323,16 @@ export default class HomeView extends Vue {
feedData: GiveRecordWithContactInfo[] = [];
feedPreviousOldestId?: string;
feedLastViewedClaimId?: string;
isAnyFeedFilterOn: boolean;
isCreatingIdentifier = false;
isFeedFilteredByContacts = false;
isFeedFilteredByVisible = false;
isFeedFilteredByNearby = false;
isFeedLoading = true;
isRegistered = false;
searchBoxes: Array<{
name: string;
bbox: BoundingBox;
}> = [];
showShortcutBvc = false;
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
@@ -349,11 +367,14 @@ 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.isFeedFilteredByVisible = !!settings?.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
this.isRegistered = !!settings?.isRegistered;
this.searchBoxes = settings?.searchBoxes || [];
this.showShortcutBvc = !!settings?.showShortcutBvc;
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
if (this.allMyDids.length === 0) {
this.isCreatingIdentifier = true;
this.activeDid = await generateSaveAndActivateIdentity();
@@ -383,7 +404,7 @@ export default class HomeView extends Vue {
}
resultsAreFiltered() {
return this.isFeedFilteredByContacts || this.isFeedFilteredByNearby;
return this.isFeedFilteredByVisible || this.isFeedFilteredByNearby;
}
notificationsSupported() {
@@ -395,21 +416,15 @@ export default class HomeView extends Vue {
"Content-Type": "application/json",
};
const identity = await this.getIdentity(this.activeDid);
if (this.activeDid) {
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
const account = allAccounts.find(
(acc) => acc.did === this.activeDid,
) as Account;
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
if (identity) {
headers["Authorization"] = "Bearer " + (await accessToken(identity));
} else {
throw new Error(
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service. Switch your ID.",
);
}
headers["Authorization"] = "Bearer " + (await accessToken(identity));
} else {
// it's OK without auth... we just won't get any identifiers
}
@@ -420,8 +435,9 @@ export default class HomeView extends Vue {
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.isFeedFilteredByVisible = !!settings?.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
this.feedData = [];
this.feedPreviousOldestId = undefined;
@@ -438,14 +454,30 @@ export default class HomeView extends Vue {
}
}
latLongInAnySearchBox(lat: number, long: number) {
for (const boxInfo of this.searchBoxes) {
if (
boxInfo.bbox.westLong <= long &&
long <= boxInfo.bbox.eastLong &&
boxInfo.bbox.minLat <= lat &&
lat <= boxInfo.bbox.maxLat
) {
return true;
}
}
}
public async updateAllFeed() {
this.isFeedLoading = true;
let endOfResults = true;
await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
.then(async (results) => {
if (results.data.length > 0) {
endOfResults = false;
// include the descriptions of the giver and receiver
const newFeedData: GiveRecordWithContactInfo = results.data.map(
(record: GiveServerRecord) => {
const identity = await this.getIdentity(this.activeDid);
const newFeedData: Array<Promise<GiveRecordWithContactInfo>> =
results.data.map(async (record: GiveSummaryRecord) => {
// similar code is in endorser-mobile utility.ts
// claim.claim happen for some claims wrapped in a Verifiable Credential
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -456,6 +488,33 @@ export default class HomeView extends Vue {
// recipient.did is for legacy data, before March 2023
const recipientDid =
claim.recipient?.identifier || (claim.recipient as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
let anyMatch = false;
if (
this.isFeedFilteredByVisible &&
containsNonHiddenDid(record)
) {
// has a visible DID so it's a keeper
anyMatch = true;
}
if (!anyMatch && this.isFeedFilteredByNearby) {
// check if the associated project has a location inside user's search box
if (record.fulfillsPlanHandleId) {
const plan = await getPlanFromCache(
record.fulfillsPlanHandleId,
identity,
this.axios,
this.apiServer,
);
if (plan?.locLat && plan?.locLon) {
if (this.latLongInAnySearchBox(plan.locLat, plan.locLon)) {
anyMatch = true;
}
}
}
}
if (this.isAnyFeedFilterOn && !anyMatch) {
return null;
}
return {
...record,
giver: didInfoForContact(
@@ -472,9 +531,11 @@ export default class HomeView extends Vue {
this.allMyDids,
),
};
},
);
this.feedData = this.feedData.concat(newFeedData);
});
const allNewFeedData: GiveRecordWithContactInfo[] =
await Promise.all(newFeedData);
const filteredFeedData = allNewFeedData.filter(R.isNotNil);
this.feedData = this.feedData.concat(filteredFeedData);
this.feedPreviousOldestId =
results.data[results.data.length - 1].jwtId;
// The following update is only done on the first load.
@@ -501,6 +562,10 @@ export default class HomeView extends Vue {
-1,
);
});
if (this.feedData.length === 0 && !endOfResults) {
// repeat until there's at least some data
this.updateAllFeed();
}
this.isFeedLoading = false;
}

View File

@@ -360,11 +360,11 @@ import { accessToken } from "@/libs/crypto";
import * as libsUtil from "@/libs/util";
import {
BLANK_GENERIC_SERVER_RECORD,
GenericServerRecord,
GenericCredWrapper,
GiverInputInfo,
GiveServerRecord,
OfferServerRecord,
PlanServerRecord,
GiveSummaryRecord,
OfferSummaryRecord,
PlanSummaryRecord,
} from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer";
@@ -388,14 +388,14 @@ export default class ProjectViewView extends Vue {
apiServer = "";
description = "";
expanded = false;
fulfilledByThis: PlanServerRecord | null = null;
fulfillersToThis: Array<PlanServerRecord> = [];
givesToThis: Array<GiveServerRecord> = [];
fulfilledByThis: PlanSummaryRecord | null = null;
fulfillersToThis: Array<PlanSummaryRecord> = [];
givesToThis: Array<GiveSummaryRecord> = [];
issuer = "";
latitude = 0;
longitude = 0;
name = "";
offersToThis: Array<OfferServerRecord> = [];
offersToThis: Array<OfferSummaryRecord> = [];
projectId = localStorage.getItem("projectId") || ""; // handle ID
showDidCopy = false;
timeSince = "";
@@ -718,8 +718,8 @@ export default class ProjectViewView extends Vue {
this.$router.push(route);
}
checkIsFulfillable(offer: OfferServerRecord) {
const offerRecord: GenericServerRecord = {
checkIsFulfillable(offer: OfferSummaryRecord) {
const offerRecord: GenericCredWrapper = {
...BLANK_GENERIC_SERVER_RECORD,
claim: offer.fullClaim,
claimType: "Offer",
@@ -728,8 +728,8 @@ export default class ProjectViewView extends Vue {
return libsUtil.canFulfillOffer(offerRecord);
}
onClickFulfillGiveToOffer(offer: OfferServerRecord) {
const offerRecord: GenericServerRecord = {
onClickFulfillGiveToOffer(offer: OfferSummaryRecord) {
const offerRecord: GenericCredWrapper = {
...BLANK_GENERIC_SERVER_RECORD,
claim: offer.fullClaim,
issuer: offer.offeredByDid,
@@ -768,8 +768,8 @@ export default class ProjectViewView extends Vue {
}
}
checkIsConfirmable(give: GiveServerRecord) {
const giveDetails: GenericServerRecord = {
checkIsConfirmable(give: GiveSummaryRecord) {
const giveDetails: GenericCredWrapper = {
...BLANK_GENERIC_SERVER_RECORD,
claim: give.fullClaim,
claimType: "GiveAction",
@@ -779,7 +779,7 @@ export default class ProjectViewView extends Vue {
}
// similar code is found in ClaimView
async confirmClaim(give: GiveServerRecord) {
async confirmClaim(give: GiveSummaryRecord) {
if (confirm("Do you personally confirm that this is true?")) {
// similar logic is found in endorser-mobile
const goodClaim = serverUtil.removeSchemaContext(

View File

@@ -223,7 +223,7 @@ import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue";
import ProjectIcon from "@/components/ProjectIcon.vue";
import TopMessage from "@/components/TopMessage.vue";
import { OfferServerRecord, PlanData } from "@/libs/endorserServer";
import { OfferSummaryRecord, PlanData } from "@/libs/endorserServer";
import EntityIcon from "@/components/EntityIcon.vue";
@Component({
@@ -237,7 +237,7 @@ export default class ProjectsView extends Vue {
currentIid: IIdentifier;
isLoading = false;
numAccounts = 0;
offers: OfferServerRecord[] = [];
offers: OfferSummaryRecord[] = [];
showOffers = true;
showProjects = false;

View File

@@ -146,7 +146,7 @@ import {
createAndSubmitConfirmation,
createAndSubmitGive,
ErrorResult,
GenericServerRecord,
GenericCredWrapper,
GenericVerifiableCredential,
} from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
@@ -166,7 +166,7 @@ export default class QuickActionBvcBeginView extends Vue {
allMyDids: Array<string> = [];
apiServer = "";
claimCountWithHidden = 0;
claimsToConfirm: GenericServerRecord[] = [];
claimsToConfirm: GenericCredWrapper[] = [];
claimsToConfirmSelected: string[] = [];
description = "breakfast";
loadingConfirms = true;
@@ -228,7 +228,7 @@ export default class QuickActionBvcBeginView extends Vue {
}
await response.json().then((data) => {
const dataByOthers = R.reject(
(claim: GenericServerRecord) => claim.issuer === this.activeDid,
(claim: GenericCredWrapper) => claim.issuer === this.activeDid,
data,
);
const dataByOthersWithoutHidden = R.reject(

View File

@@ -246,7 +246,7 @@ export default class DiscoverView extends Vue {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
searchBoxes: [],
filterFeedNearby: false,
filterFeedByNearby: false,
});
this.searchBox = null;
this.localCenterLat = 0;