forked from jsnbuchanan/crowd-funder-for-time-pwa
Compare commits
2 Commits
qr-reader
...
remove-old
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e25e5d5ff6 | ||
|
|
8452af7abc |
@@ -99,9 +99,6 @@ See https://tea.xyz
|
|||||||
|
|
||||||
### Reference Material
|
### Reference Material
|
||||||
|
|
||||||
* Notifications can be type of `toast` (self-dismiss), `info`, `success`, `warning`, and `danger`.
|
|
||||||
They are done via [notiwind](https://www.npmjs.com/package/notiwind) and set up in App.vue.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
// reference material from https://github.com/trentlarson/endorser-mobile/blob/8dc8e0353e0cc80ffa7ed89ded15c8b0da92726b/src/utility/idUtility.ts#L83
|
// reference material from https://github.com/trentlarson/endorser-mobile/blob/8dc8e0353e0cc80ffa7ed89ded15c8b0da92726b/src/utility/idUtility.ts#L83
|
||||||
|
|
||||||
|
|||||||
26032
package-lock.json
generated
26032
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
66
package.json
66
package.json
@@ -9,59 +9,57 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethersproject/hdnode": "^5.7.0",
|
"@ethersproject/hdnode": "^5.7.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||||
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
||||||
"@tweenjs/tween.js": "^21.0.0",
|
"@tweenjs/tween.js": "^21.0.0",
|
||||||
"@veramo/core": "^5.2.0",
|
"@veramo/core": "^5.4.1",
|
||||||
"@veramo/credential-w3c": "^5.2.0",
|
"@veramo/credential-w3c": "^5.4.1",
|
||||||
"@veramo/data-store": "^5.2.0",
|
"@veramo/data-store": "^5.4.1",
|
||||||
"@veramo/did-manager": "^5.1.2",
|
"@veramo/did-manager": "^5.4.1",
|
||||||
"@veramo/did-provider-ethr": "^5.1.2",
|
"@veramo/did-provider-ethr": "^5.4.1",
|
||||||
"@veramo/did-resolver": "^5.2.0",
|
"@veramo/did-resolver": "^5.4.1",
|
||||||
"@veramo/key-manager": "^5.1.2",
|
"@veramo/key-manager": "^5.4.1",
|
||||||
"@vueuse/core": "^10.2.1",
|
"@vueuse/core": "^10.4.1",
|
||||||
"@zxing/text-encoding": "^0.9.0",
|
"@zxing/text-encoding": "^0.9.0",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.5.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"core-js": "^3.31.1",
|
"core-js": "^3.32.1",
|
||||||
"dexie": "^3.2.4",
|
"dexie": "^3.2.4",
|
||||||
"dexie-export-import": "^4.0.7",
|
"dexie-export-import": "^4.0.7",
|
||||||
"did-jwt": "^7.2.4",
|
"did-jwt": "^7.2.6",
|
||||||
"ethereum-cryptography": "^2.0.0",
|
"ethereum-cryptography": "^2.1.2",
|
||||||
"ethereumjs-util": "^7.1.5",
|
"ethereumjs-util": "^7.1.5",
|
||||||
"ethr-did-resolver": "^8.0.0",
|
"ethr-did-resolver": "^8.1.2",
|
||||||
"jdenticon": "^3.2.0",
|
"jdenticon": "^3.2.0",
|
||||||
"js-generate-password": "^0.1.9",
|
"js-generate-password": "^0.1.9",
|
||||||
"localstorage-slim": "^2.4.0",
|
"localstorage-slim": "^2.4.0",
|
||||||
"luxon": "^3.3.0",
|
"luxon": "^3.4.2",
|
||||||
"merkletreejs": "^0.3.10",
|
"merkletreejs": "^0.3.10",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"notiwind": "^2.0.2",
|
"notiwind": "^2.0.2",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"pina": "^0.20.2204228",
|
"pina": "^0.20.2204228",
|
||||||
"pinia-plugin-persistedstate": "^3.1.0",
|
"pinia-plugin-persistedstate": "^3.2.0",
|
||||||
"qr-code-generator-vue3": "^1.4.21",
|
"qr-code-generator-vue3": "^1.4.21",
|
||||||
"ramda": "^0.29.0",
|
"ramda": "^0.29.0",
|
||||||
"readable-stream": "^4.4.2",
|
"readable-stream": "^4.4.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"three": "^0.154.0",
|
"three": "^0.155.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^2.1.20",
|
"vue-facing-decorator": "^3.0.2",
|
||||||
"vue-qrcode-reader": "^5.3.4",
|
"vue-router": "^4.2.4",
|
||||||
"vue-router": "^4.2.3",
|
|
||||||
"web-did-resolver": "^2.0.27"
|
"web-did-resolver": "^2.0.27"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/leaflet": "^1.9.4",
|
|
||||||
"@types/ramda": "^0.29.3",
|
"@types/ramda": "^0.29.3",
|
||||||
"@types/three": "^0.152.1",
|
"@types/three": "^0.155.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
||||||
"@typescript-eslint/parser": "^5.61.0",
|
"@typescript-eslint/parser": "^6.5.0",
|
||||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||||
"@vue/cli-plugin-babel": "~5.0.8",
|
"@vue/cli-plugin-babel": "~5.0.8",
|
||||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||||
@@ -71,15 +69,15 @@
|
|||||||
"@vue/cli-plugin-vuex": "~5.0.8",
|
"@vue/cli-plugin-vuex": "~5.0.8",
|
||||||
"@vue/cli-service": "~5.0.8",
|
"@vue/cli-service": "~5.0.8",
|
||||||
"@vue/eslint-config-typescript": "^11.0.3",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.15",
|
||||||
"eslint": "^8.44.0",
|
"eslint": "^8.48.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0-alpha.1",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^9.15.1",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"postcss": "^8.4.24",
|
"postcss": "^8.4.29",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.3",
|
||||||
"tailwindcss": "^3.3.2",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "~5.1.6"
|
"typescript": "~5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- fix "any" warnings
|
|
||||||
- fix missing updateAllFeed in ContactGiftingView page
|
|
||||||
- check that Anonymous users jdenticon are nulls (not "Anonymous")
|
|
||||||
- test alerts on all pages -- or refactor to new "notify" (since AlertMessage refactoring may require a change, et. ContactQRScanShowView)
|
- test alerts on all pages -- or refactor to new "notify" (since AlertMessage refactoring may require a change, et. ContactQRScanShowView)
|
||||||
- .2 bug - on contacts view, click on "to" & "from" and nothing happens
|
- .2 bug - on contacts view, click on "to" & "from" and nothing happens
|
||||||
- 40 notifications :
|
- 40 notifications :
|
||||||
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew
|
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew
|
||||||
|
|
||||||
- .2 Rename repo to crowd-sourcing...
|
- 01 add my bounding box(es) of interest for searches on Nearby part of Discovery page
|
||||||
|
- .5 search by a bounding box(s) of interest for local projects (see API by clicking on "Nearby")
|
||||||
- 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:matthew
|
- 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:matthew
|
||||||
|
|
||||||
- 01 fix the Discovery map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui
|
|
||||||
|
|
||||||
- 08 Scan QR code to import into contacts assignee:matthew
|
- 08 Scan QR code to import into contacts assignee:matthew
|
||||||
- SEE: https://github.com/gruhn/vue-qrcode-reader
|
- SEE: https://github.com/gruhn/vue-qrcode-reader
|
||||||
|
|
||||||
@@ -28,8 +24,6 @@ tasks:
|
|||||||
|
|
||||||
- 24 Move to Vite assignee:matthew
|
- 24 Move to Vite assignee:matthew
|
||||||
|
|
||||||
- .5 Allow edit of a contact name (but not the DID)
|
|
||||||
- .2 Edit Plan does not have icons across the bottom assignee-group:ui
|
|
||||||
- .5 include the hash of the latest commit, and maybe a version
|
- .5 include the hash of the latest commit, and maybe a version
|
||||||
- .5 add link to further project / people when a project pays ahead
|
- .5 add link to further project / people when a project pays ahead
|
||||||
- .5 add project ID to the URL, to make a project publicly-accessible
|
- .5 add project ID to the URL, to make a project publicly-accessible
|
||||||
@@ -40,7 +34,6 @@ tasks:
|
|||||||
- .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent
|
- .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent
|
||||||
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164
|
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164
|
||||||
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
|
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
|
||||||
- .1 change default server in app.ts
|
|
||||||
|
|
||||||
- Discuss whether the remaining tasks are worthwhile before MVP release.
|
- Discuss whether the remaining tasks are worthwhile before MVP release.
|
||||||
|
|
||||||
@@ -109,8 +102,6 @@ tasks:
|
|||||||
- 40 notifications v+ :
|
- 40 notifications v+ :
|
||||||
- pull, w/ scheduled runs
|
- pull, w/ scheduled runs
|
||||||
|
|
||||||
- 01 On nearby search, if user starts changing their box but cancels and goes back to the map it is zoomed far out. Fix to fit the box better.
|
|
||||||
|
|
||||||
log:
|
log:
|
||||||
- videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29
|
- videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29
|
||||||
- project lists, contact totals & actions, multiple identifiers, stats-world, activity feed, rename of this project file (use "--follow --") milestone:2 done:2023-06-27
|
- project lists, contact totals & actions, multiple identifiers, stats-world, activity feed, rename of this project file (use "--follow --") milestone:2 done:2023-06-27
|
||||||
|
|||||||
47
src/components/AlertMessage.vue
Normal file
47
src/components/AlertMessage.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div v-bind:class="computedAlertClassNames()">
|
||||||
|
<button
|
||||||
|
class="close-button bg-amber-400 w-8 leading-loose rounded-full absolute top-2 right-2"
|
||||||
|
@click="onClickClose()"
|
||||||
|
>
|
||||||
|
<fa icon="xmark"></fa>
|
||||||
|
</button>
|
||||||
|
<h4 class="font-bold pr-5">{{ alertTitle }}</h4>
|
||||||
|
<p>{{ alertMessage }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class AlertMessage extends Vue {
|
||||||
|
@Prop alertTitle = "";
|
||||||
|
@Prop alertMessage = "";
|
||||||
|
isAlertVisible = this.alertMessage;
|
||||||
|
|
||||||
|
public onClickClose() {
|
||||||
|
this.isAlertVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public computedAlertClassNames() {
|
||||||
|
return {
|
||||||
|
hidden: !this.isAlertVisible,
|
||||||
|
"dismissable-alert": true,
|
||||||
|
"bg-amber-200": true,
|
||||||
|
"p-5": true,
|
||||||
|
rounded: true,
|
||||||
|
"drop-shadow-lg": true,
|
||||||
|
fixed: true,
|
||||||
|
"top-3": true,
|
||||||
|
"inset-x-3": true,
|
||||||
|
"transition-transform": true,
|
||||||
|
"ease-in": true,
|
||||||
|
"duration-300": true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped></style>
|
||||||
@@ -8,7 +8,7 @@ import { toSvg } from "jdenticon";
|
|||||||
@Component
|
@Component
|
||||||
export default class EntityIcon extends Vue {
|
export default class EntityIcon extends Vue {
|
||||||
@Prop entityId = "";
|
@Prop entityId = "";
|
||||||
@Prop iconSize = 0;
|
@Prop iconSize = "";
|
||||||
|
|
||||||
generateIdenticon() {
|
generateIdenticon() {
|
||||||
const svgString = toSvg(this.entityId, this.iconSize);
|
const svgString = toSvg(this.entityId, this.iconSize);
|
||||||
|
|||||||
@@ -52,18 +52,18 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component, Prop, Emit } from "vue-facing-decorator";
|
import { Vue, Component, Prop, Emit } from "vue-facing-decorator";
|
||||||
import { GiverInputInfo, GiverOutputInfo } from "@/libs/endorserServer";
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class GiftedDialog extends Vue {
|
export default class GiftedDialog extends Vue {
|
||||||
@Prop message = "";
|
@Prop message = "";
|
||||||
|
|
||||||
giver?: GiverInputInfo;
|
giver = null;
|
||||||
description = "";
|
description = "";
|
||||||
hours = "0";
|
hours = "0";
|
||||||
visible = false;
|
visible = false;
|
||||||
|
|
||||||
open(giver: GiverInputInfo) {
|
open(giver) {
|
||||||
|
// giver: GiverInputInfo
|
||||||
this.giver = giver;
|
this.giver = giver;
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ export default class GiftedDialog extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Emit("dialog-result")
|
@Emit("dialog-result")
|
||||||
confirm(): GiverOutputInfo {
|
confirm() {
|
||||||
const result = {
|
const result = {
|
||||||
action: "confirm",
|
action: "confirm",
|
||||||
giver: this.giver,
|
giver: this.giver,
|
||||||
@@ -90,14 +90,14 @@ export default class GiftedDialog extends Vue {
|
|||||||
};
|
};
|
||||||
this.close();
|
this.close();
|
||||||
this.description = "";
|
this.description = "";
|
||||||
this.giver = undefined;
|
this.giver = null;
|
||||||
this.hours = "0";
|
this.hours = "0";
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Emit("dialog-result")
|
@Emit("dialog-result")
|
||||||
cancel(): GiverOutputInfo {
|
cancel() {
|
||||||
const result = { action: "cancel" };
|
const result = { action: "cancel" };
|
||||||
this.close();
|
this.close();
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Generic strings that could be used throughout the app.
|
* Generic strings that could be used throughout the app.
|
||||||
*
|
|
||||||
* See also ../libs/veramo/setup.ts
|
|
||||||
*/
|
*/
|
||||||
export enum AppString {
|
export enum AppString {
|
||||||
APP_NAME = "Time Safari",
|
APP_NAME = "Kick-Start with Time",
|
||||||
|
|
||||||
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
||||||
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
|
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
|
||||||
@@ -12,13 +10,3 @@ export enum AppString {
|
|||||||
|
|
||||||
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
|
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* See notiwind package
|
|
||||||
*/
|
|
||||||
export interface NotificationIface {
|
|
||||||
group: string;
|
|
||||||
type: string; // "toast" | "info" | "success" | "warning" | "danger"
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ export interface Contact {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ContactsSchema = {
|
export const ContactsSchema = {
|
||||||
contacts: "&did, name, publicKeyBase64, registered, seesMe",
|
contacts: "++did, name, publicKeyBase64, registered, seesMe",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
export type BoundingBox = {
|
|
||||||
eastLong: number;
|
|
||||||
maxLat: number;
|
|
||||||
minLat: number;
|
|
||||||
westLong: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
// a singleton
|
// a singleton
|
||||||
export type Settings = {
|
export type Settings = {
|
||||||
id: number; // there's only one entry: MASTER_SETTINGS_KEY
|
id: number; // there's only one entry: MASTER_SETTINGS_KEY
|
||||||
@@ -14,10 +7,6 @@ export type Settings = {
|
|||||||
firstName?: string;
|
firstName?: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
lastViewedClaimId?: string;
|
lastViewedClaimId?: string;
|
||||||
searchBoxes?: Array<{
|
|
||||||
name: string;
|
|
||||||
bbox: BoundingBox;
|
|
||||||
}>;
|
|
||||||
showContactGivesInline?: boolean;
|
showContactGivesInline?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
|
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
|
||||||
import { getRandomBytesSync } from "ethereum-cryptography/random";
|
import { getRandomBytesSync } from "ethereum-cryptography/random";
|
||||||
import { entropyToMnemonic } from "ethereum-cryptography/bip39";
|
import { entropyToMnemonic } from "ethereum-cryptography/bip39";
|
||||||
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english";
|
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english";
|
||||||
@@ -6,9 +7,6 @@ import { HDNode } from "@ethersproject/hdnode";
|
|||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import * as u8a from "uint8arrays";
|
import * as u8a from "uint8arrays";
|
||||||
|
|
||||||
import { ENDORSER_JWT_URL_LOCATION } from "@/libs/endorserServer";
|
|
||||||
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
|
|
||||||
|
|
||||||
export const DEFAULT_ROOT_DERIVATION_PATH = "m/76798669'/0'/0'/0'";
|
export const DEFAULT_ROOT_DERIVATION_PATH = "m/76798669'/0'/0'/0'";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,24 +150,3 @@ export function fromJose(signature: string): {
|
|||||||
export function bytesToHex(b: Uint8Array): string {
|
export function bytesToHex(b: Uint8Array): string {
|
||||||
return u8a.toString(b, "base16");
|
return u8a.toString(b, "base16");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
@return results of uportJwtPayload:
|
|
||||||
{ iat: number, iss: string (DID), own: { name, publicEncKey (base64-encoded key) } }
|
|
||||||
|
|
||||||
Note that similar code is also contained in time-safari
|
|
||||||
*/
|
|
||||||
export const getContactPayloadFromJwtUrl = (jwtUrlText: string) => {
|
|
||||||
let jwtText = jwtUrlText;
|
|
||||||
const endorserContextLoc = jwtText.indexOf(ENDORSER_JWT_URL_LOCATION);
|
|
||||||
if (endorserContextLoc > -1) {
|
|
||||||
jwtText = jwtText.substring(
|
|
||||||
endorserContextLoc + ENDORSER_JWT_URL_LOCATION.length,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// JWT format: { header, payload, signature, data }
|
|
||||||
const jwt = didJwt.decodeJWT(jwtText);
|
|
||||||
|
|
||||||
return jwt.payload;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ import { Axios, AxiosResponse } from "axios";
|
|||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
|
||||||
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
||||||
// the object in RegisterAction claims
|
|
||||||
export const SERVICE_ID = "endorser.ch";
|
export const SERVICE_ID = "endorser.ch";
|
||||||
// the prefix for the contact URL
|
|
||||||
export const CONTACT_URL_PREFIX = "https://endorser.ch";
|
|
||||||
// the suffix for the contact URL
|
|
||||||
export const ENDORSER_JWT_URL_LOCATION = "/contact?jwt=";
|
|
||||||
|
|
||||||
export interface AgreeVerifiableCredential {
|
export interface AgreeVerifiableCredential {
|
||||||
"@context": string;
|
"@context": string;
|
||||||
@@ -26,13 +21,6 @@ export interface GiverInputInfo {
|
|||||||
name?: string;
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GiverOutputInfo {
|
|
||||||
action: string;
|
|
||||||
giver?: GiverInputInfo;
|
|
||||||
description?: string;
|
|
||||||
hours?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ClaimResult {
|
export interface ClaimResult {
|
||||||
success: { claimId: string; handleId: string };
|
success: { claimId: string; handleId: string };
|
||||||
error: { code: string; message: string };
|
error: { code: string; message: string };
|
||||||
@@ -67,30 +55,7 @@ export interface GiveVerifiableCredential {
|
|||||||
fulfills?: { "@type": string; identifier: string };
|
fulfills?: { "@type": string; identifier: string };
|
||||||
identifier?: string;
|
identifier?: string;
|
||||||
object?: { amountOfThisGood: number; unitCode: string };
|
object?: { amountOfThisGood: number; unitCode: string };
|
||||||
recipient?: { identifier: string };
|
recipient: { identifier: string };
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlanVerifiableCredential {
|
|
||||||
"@context": "https://schema.org";
|
|
||||||
"@type": "PlanAction";
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
identifier?: string;
|
|
||||||
location?: {
|
|
||||||
geo: { "@type": "GeoCoordinates"; latitude: number; longitude: number };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlanServerRecord {
|
|
||||||
agentDid?: string; // optional, if the issuer wants someone else to manage as well
|
|
||||||
description: string;
|
|
||||||
endTime?: string;
|
|
||||||
issuerDid: string;
|
|
||||||
handleId: string;
|
|
||||||
locLat?: number;
|
|
||||||
locLon?: number;
|
|
||||||
startTime?: string;
|
|
||||||
url?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterVerifiableCredential {
|
export interface RegisterVerifiableCredential {
|
||||||
@@ -98,7 +63,7 @@ export interface RegisterVerifiableCredential {
|
|||||||
"@type": string;
|
"@type": string;
|
||||||
agent: { identifier: string };
|
agent: { identifier: string };
|
||||||
object: string;
|
object: string;
|
||||||
participant: { identifier: string };
|
recipient: { identifier: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InternalError {
|
export interface InternalError {
|
||||||
@@ -110,7 +75,7 @@ export interface InternalError {
|
|||||||
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
|
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
|
||||||
const HIDDEN_DID = "did:none:HIDDEN";
|
const HIDDEN_DID = "did:none:HIDDEN";
|
||||||
|
|
||||||
export function isHiddenDid(did: string) {
|
export function isHiddenDid(did) {
|
||||||
return did === HIDDEN_DID;
|
return did === HIDDEN_DID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +88,7 @@ export function didInfo(
|
|||||||
allMyDids: Array<string>,
|
allMyDids: Array<string>,
|
||||||
contacts: Array<Contact>,
|
contacts: Array<Contact>,
|
||||||
): string {
|
): string {
|
||||||
const myId: string | undefined = R.find(R.equals(did), allMyDids);
|
const myId: string | undefined = R.find(R.equals(did), allMyDids, did);
|
||||||
if (myId) {
|
if (myId) {
|
||||||
return "You" + (myId !== activeDid ? " (Alt ID)" : "");
|
return "You" + (myId !== activeDid ? " (Alt ID)" : "");
|
||||||
} else {
|
} else {
|
||||||
@@ -140,22 +105,6 @@ export function didInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResultWithType {
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SuccessResult extends ResultWithType {
|
|
||||||
type: "success";
|
|
||||||
response: AxiosResponse<ClaimResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ErrorResult {
|
|
||||||
type: "error";
|
|
||||||
error: InternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CreateAndSubmitGiveResult = SuccessResult | ErrorResult;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
|
* For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
|
||||||
*
|
*
|
||||||
@@ -169,115 +118,76 @@ export async function createAndSubmitGive(
|
|||||||
axios: Axios,
|
axios: Axios,
|
||||||
apiServer: string,
|
apiServer: string,
|
||||||
identity: IIdentifier,
|
identity: IIdentifier,
|
||||||
fromDid?: string,
|
fromDid: string,
|
||||||
toDid?: string,
|
toDid: string,
|
||||||
description?: string,
|
description: string,
|
||||||
hours?: number,
|
hours: number,
|
||||||
fulfillsProjectHandleId?: string,
|
fulfillsProjectHandleId?: string,
|
||||||
): Promise<CreateAndSubmitGiveResult> {
|
): Promise<AxiosResponse<ClaimResult> | InternalError> {
|
||||||
try {
|
// Make a claim
|
||||||
// Make a claim
|
const vcClaim: GiveVerifiableCredential = {
|
||||||
const vcClaim: GiveVerifiableCredential = {
|
"@context": "https://schema.org",
|
||||||
"@context": "https://schema.org",
|
"@type": "GiveAction",
|
||||||
"@type": "GiveAction",
|
recipient: { identifier: toDid },
|
||||||
};
|
};
|
||||||
if (toDid) {
|
if (fromDid) {
|
||||||
vcClaim.recipient = { identifier: toDid };
|
vcClaim.agent = { identifier: fromDid };
|
||||||
}
|
}
|
||||||
if (fromDid) {
|
if (description) {
|
||||||
vcClaim.agent = { identifier: fromDid };
|
vcClaim.description = description;
|
||||||
}
|
}
|
||||||
if (description) {
|
if (hours) {
|
||||||
vcClaim.description = description;
|
vcClaim.object = { amountOfThisGood: hours, unitCode: "HUR" };
|
||||||
}
|
}
|
||||||
if (hours) {
|
if (fulfillsProjectHandleId) {
|
||||||
vcClaim.object = { amountOfThisGood: hours, unitCode: "HUR" };
|
vcClaim.fulfills = {
|
||||||
}
|
"@type": "PlanAction",
|
||||||
if (fulfillsProjectHandleId) {
|
identifier: fulfillsProjectHandleId,
|
||||||
vcClaim.fulfills = {
|
|
||||||
"@type": "PlanAction",
|
|
||||||
identifier: fulfillsProjectHandleId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Make a payload for the claim
|
|
||||||
const vcPayload = {
|
|
||||||
vc: {
|
|
||||||
"@context": ["https://www.w3.org/2018/credentials/v1"],
|
|
||||||
type: ["VerifiableCredential"],
|
|
||||||
credentialSubject: vcClaim,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a signature using private key of identity
|
|
||||||
const firstKey = identity.keys[0];
|
|
||||||
if (!firstKey || !firstKey.privateKeyHex) {
|
|
||||||
throw {
|
|
||||||
error: "No private key",
|
|
||||||
message: `Your identifier ${identity.did} is not configured correctly. Use a different identifier.`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const privateKeyHex = firstKey.privateKeyHex;
|
|
||||||
|
|
||||||
if (!privateKeyHex) {
|
|
||||||
throw {
|
|
||||||
error: "No private key",
|
|
||||||
message: `Your identifier ${identity.did} is not configured correctly. Use a different identifier.`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const signer = await SimpleSigner(privateKeyHex);
|
|
||||||
const alg = undefined;
|
|
||||||
|
|
||||||
// Create a JWT for the request
|
|
||||||
|
|
||||||
const vcJwt: string = await didJwt.createJWT(vcPayload, {
|
|
||||||
alg: alg,
|
|
||||||
issuer: identity.did,
|
|
||||||
signer: signer,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make the xhr request payload
|
|
||||||
|
|
||||||
const payload = JSON.stringify({ jwtEncoded: vcJwt });
|
|
||||||
const url = apiServer + "/api/v2/claim";
|
|
||||||
const token = await accessToken(identity);
|
|
||||||
const headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await axios.post(url, payload, { headers });
|
|
||||||
return {
|
|
||||||
type: "success",
|
|
||||||
response,
|
|
||||||
};
|
|
||||||
} catch (error: unknown) {
|
|
||||||
let errorMessage: string;
|
|
||||||
|
|
||||||
if (error instanceof Error) {
|
|
||||||
// If it's a JavaScript Error object
|
|
||||||
errorMessage = error.message;
|
|
||||||
} else if (
|
|
||||||
typeof error === "object" &&
|
|
||||||
error !== null &&
|
|
||||||
"message" in error
|
|
||||||
) {
|
|
||||||
// If it's an object that has a 'message' property
|
|
||||||
errorMessage = (error as { message: string }).message;
|
|
||||||
} else {
|
|
||||||
// Unknown error shape, default message
|
|
||||||
errorMessage = "Unknown error";
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "error",
|
|
||||||
error: {
|
|
||||||
error: errorMessage,
|
|
||||||
userMessage: "Failed to create and submit the claim.",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// Make a payload for the claim
|
||||||
|
const vcPayload = {
|
||||||
|
vc: {
|
||||||
|
"@context": ["https://www.w3.org/2018/credentials/v1"],
|
||||||
|
type: ["VerifiableCredential"],
|
||||||
|
credentialSubject: vcClaim,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Create a signature using private key of identity
|
||||||
|
if (identity.keys[0].privateKeyHex == null) {
|
||||||
|
return new Promise<InternalError>((resolve, reject) => {
|
||||||
|
reject({
|
||||||
|
error: "No private key",
|
||||||
|
message:
|
||||||
|
"Your identifier " +
|
||||||
|
identity.did +
|
||||||
|
" is not configured correctly. Use a different identifier.",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const privateKeyHex: string = identity.keys[0].privateKeyHex!;
|
||||||
|
const signer = await SimpleSigner(privateKeyHex);
|
||||||
|
const alg = undefined;
|
||||||
|
// Create a JWT for the request
|
||||||
|
const vcJwt: string = await didJwt.createJWT(vcPayload, {
|
||||||
|
alg: alg,
|
||||||
|
issuer: identity.did,
|
||||||
|
signer: signer,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make the xhr request payload
|
||||||
|
|
||||||
|
const payload = JSON.stringify({ jwtEncoded: vcJwt });
|
||||||
|
const url = apiServer + "/api/v2/claim";
|
||||||
|
const token = await accessToken(identity);
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + token,
|
||||||
|
};
|
||||||
|
|
||||||
|
return axios.post(url, payload, { headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
// from https://stackoverflow.com/a/175787/845494
|
// from https://stackoverflow.com/a/175787/845494
|
||||||
|
|||||||
@@ -1,7 +1,151 @@
|
|||||||
// see also ../constants/app.ts and
|
// Created from the setup in https://veramo.io/docs/guides/react_native
|
||||||
|
|
||||||
|
// Core interfaces
|
||||||
|
/* import {
|
||||||
|
createAgent,
|
||||||
|
IDIDManager,
|
||||||
|
IResolver,
|
||||||
|
IDataStore,
|
||||||
|
IKeyManager,
|
||||||
|
} from "@veramo/core";
|
||||||
|
*/
|
||||||
|
// Core identity manager plugin
|
||||||
|
//import { DIDManager } from "@veramo/did-manager";
|
||||||
|
|
||||||
|
// Ethr did identity provider
|
||||||
|
//import { EthrDIDProvider } from "@veramo/did-provider-ethr";
|
||||||
|
|
||||||
|
// Core key manager plugin
|
||||||
|
//import { KeyManager } from "@veramo/key-manager";
|
||||||
|
|
||||||
|
// Custom key management system for RN
|
||||||
|
//import { KeyManagementSystem } from '@veramo/kms-local-react-native'
|
||||||
|
|
||||||
|
// Custom resolver
|
||||||
|
// Custom resolvers
|
||||||
|
//import { DIDResolverPlugin } from "@veramo/did-resolver";
|
||||||
|
/* import { Resolver } from "did-resolver";
|
||||||
|
import { getResolver as ethrDidResolver } from "ethr-did-resolver";
|
||||||
|
import { getResolver as webDidResolver } from "web-did-resolver";
|
||||||
|
*/
|
||||||
|
// for VCs and VPs https://veramo.io/docs/api/credential-w3c
|
||||||
|
//import { CredentialIssuer } from '@veramo/credential-w3c'
|
||||||
|
|
||||||
|
// Storage plugin using TypeOrm
|
||||||
|
/* import {
|
||||||
|
Entities,
|
||||||
|
KeyStore,
|
||||||
|
DIDStore,
|
||||||
|
IDataStoreORM,
|
||||||
|
} from "@veramo/data-store";
|
||||||
|
*/
|
||||||
|
// TypeORM is installed with @veramo/typeorm
|
||||||
|
//import { createConnection } from 'typeorm'
|
||||||
|
|
||||||
|
//import * as R from "ramda";
|
||||||
|
|
||||||
|
/*
|
||||||
|
import { Contact } from '../entity/contact'
|
||||||
|
import { Settings } from '../entity/settings'
|
||||||
|
import { PrivateData } from '../entity/privateData'
|
||||||
|
|
||||||
|
import { Initial1616938713828 } from '../migration/1616938713828-initial'
|
||||||
|
import { SettingsContacts1616967972293 } from '../migration/1616967972293-settings-contacts'
|
||||||
|
import { EncryptedSeed1637856484788 } from '../migration/1637856484788-EncryptedSeed'
|
||||||
|
import { HomeScreenConfig1639947962124 } from '../migration/1639947962124-HomeScreenConfig'
|
||||||
|
import { HandlePublicKeys1652142819353 } from '../migration/1652142819353-HandlePublicKeys'
|
||||||
|
import { LastClaimsSeen1656811846836 } from '../migration/1656811846836-LastClaimsSeen'
|
||||||
|
import { ContactRegistered1662256903367 }from '../migration/1662256903367-ContactRegistered'
|
||||||
|
import { PrivateData1663080623479 } from '../migration/1663080623479-PrivateData'
|
||||||
|
|
||||||
|
const ALL_ENTITIES = Entities.concat([Contact, Settings, PrivateData])
|
||||||
|
|
||||||
|
// Create react native DB connection configured by ormconfig.js
|
||||||
|
|
||||||
|
export const dbConnection = createConnection({
|
||||||
|
database: 'endorser-mobile.sqlite',
|
||||||
|
entities: ALL_ENTITIES,
|
||||||
|
location: 'default',
|
||||||
|
logging: ['error', 'info', 'warn'],
|
||||||
|
migrations: [ Initial1616938713828, SettingsContacts1616967972293, EncryptedSeed1637856484788, HomeScreenConfig1639947962124, HandlePublicKeys1652142819353, LastClaimsSeen1656811846836, ContactRegistered1662256903367, PrivateData1663080623479 ],
|
||||||
|
migrationsRun: true,
|
||||||
|
type: 'react-native',
|
||||||
|
})
|
||||||
|
*/
|
||||||
function didProviderName(netName: string) {
|
function didProviderName(netName: string) {
|
||||||
return "did:ethr" + (netName === "mainnet" ? "" : ":" + netName);
|
return "did:ethr" + (netName === "mainnet" ? "" : ":" + netName);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_DID_PROVIDER_NAME = didProviderName("mainnet");
|
//const NETWORK_NAMES = ["mainnet", "rinkeby"];
|
||||||
|
|
||||||
|
const DEFAULT_DID_PROVIDER_NETWORK_NAME = "mainnet";
|
||||||
|
|
||||||
|
export const DEFAULT_DID_PROVIDER_NAME = didProviderName(
|
||||||
|
DEFAULT_DID_PROVIDER_NETWORK_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const HANDY_APP = false;
|
||||||
|
|
||||||
|
// this is used as the object in RegisterAction claims
|
||||||
|
export const SERVICE_ID = "endorser.ch";
|
||||||
|
|
||||||
|
//const INFURA_PROJECT_ID = "INFURA_PROJECT_ID";
|
||||||
|
/*
|
||||||
|
const providers = {}
|
||||||
|
NETWORK_NAMES.forEach((networkName) => {
|
||||||
|
providers[didProviderName(networkName)] = new EthrDIDProvider({
|
||||||
|
defaultKms: 'local',
|
||||||
|
network: networkName,
|
||||||
|
rpcUrl: 'https://' + networkName + '.infura.io/v3/' + INFURA_PROJECT_ID,
|
||||||
|
gas: 1000001,
|
||||||
|
ttl: 60 * 60 * 24 * 30 * 12 + 1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const didManager = new DIDManager({
|
||||||
|
store: new DIDStore(dbConnection),
|
||||||
|
defaultProvider: DEFAULT_DID_PROVIDER_NAME,
|
||||||
|
providers: providers,
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* const basicDidResolvers = NETWORK_NAMES.map((networkName) => [
|
||||||
|
networkName,
|
||||||
|
new Resolver({
|
||||||
|
ethr: ethrDidResolver({
|
||||||
|
networks: [
|
||||||
|
{
|
||||||
|
name: networkName,
|
||||||
|
rpcUrl:
|
||||||
|
"https://" + networkName + ".infura.io/v3/" + INFURA_PROJECT_ID,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).ethr,
|
||||||
|
web: webDidResolver().web,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const basicResolverMap = R.fromPairs(basicDidResolvers)
|
||||||
|
|
||||||
|
export const DEFAULT_BASIC_RESOLVER = basicResolverMap[DEFAULT_DID_PROVIDER_NETWORK_NAME]
|
||||||
|
|
||||||
|
const agentDidResolvers = NETWORK_NAMES.map((networkName) => {
|
||||||
|
return new DIDResolverPlugin({
|
||||||
|
resolver: basicResolverMap[networkName],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let allPlugins = [
|
||||||
|
new CredentialIssuer(),
|
||||||
|
new KeyManager({
|
||||||
|
store: new KeyStore(dbConnection),
|
||||||
|
kms: {
|
||||||
|
local: new KeyManagementSystem(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
didManager,
|
||||||
|
].concat(agentDidResolvers)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//export const agent = createAgent<IDIDManager & IKeyManager & IDataStore & IDataStoreORM & IResolver>({ plugins: allPlugins })
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import {
|
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
|
||||||
createRouter,
|
import { accountsDB } from "@/db";
|
||||||
createWebHistory,
|
|
||||||
NavigationGuardNext,
|
|
||||||
RouteLocationNormalized,
|
|
||||||
RouteRecordRaw,
|
|
||||||
} from "vue-router";
|
|
||||||
import { accountsDB } from "@/db/index";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -13,11 +7,7 @@ import { accountsDB } from "@/db/index";
|
|||||||
* @param from :RouteLocationNormalized
|
* @param from :RouteLocationNormalized
|
||||||
* @param next :NavigationGuardNext
|
* @param next :NavigationGuardNext
|
||||||
*/
|
*/
|
||||||
const enterOrStart = async (
|
const enterOrStart = async (to, from, next) => {
|
||||||
to: RouteLocationNormalized,
|
|
||||||
from: RouteLocationNormalized,
|
|
||||||
next: NavigationGuardNext,
|
|
||||||
) => {
|
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const num_accounts = await accountsDB.accounts.count();
|
const num_accounts = await accountsDB.accounts.count();
|
||||||
if (num_accounts > 0) {
|
if (num_accounts > 0) {
|
||||||
@@ -200,12 +190,7 @@ const router = createRouter({
|
|||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorHandler = (
|
const errorHandler = (error, to, from) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
error: any,
|
|
||||||
to: RouteLocationNormalized,
|
|
||||||
from: RouteLocationNormalized,
|
|
||||||
) => {
|
|
||||||
// Handle the error here
|
// Handle the error here
|
||||||
console.error("Caught in top level error handler:", error, to, from);
|
console.error("Caught in top level error handler:", error, to, from);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import axios from "axios";
|
|||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import { AppString } from "@/constants/app";
|
import { AppString } from "@/constants/app";
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
import { SERVICE_ID } from "../libs/endorserServer";
|
import { SERVICE_ID } from "../libs/veramo/setup";
|
||||||
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
|
||||||
|
|||||||
@@ -170,33 +170,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<div class="flex py-2">
|
|
||||||
<button class="text-center text-md text-blue-500" @click="checkLimits()">
|
|
||||||
Check Limits
|
|
||||||
</button>
|
|
||||||
<!-- show spinner if loading limits -->
|
|
||||||
<div v-if="loadingLimits" class="ml-2">
|
|
||||||
Checking... <fa icon="spinner" class="fa-spin"></fa>
|
|
||||||
</div>
|
|
||||||
<div class="ml-2">
|
|
||||||
{{ limitsMessage }}
|
|
||||||
</div>
|
|
||||||
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9">
|
|
||||||
<span class="font-bold">Rate Limits</span>
|
|
||||||
<p>
|
|
||||||
You have done {{ limits.doneClaimsThisWeek }} claims out of
|
|
||||||
{{ limits.maxClaimsPerWeek }} for this week. Your claims counter
|
|
||||||
resets at {{ readableTime(limits.nextWeekBeginDateTime) }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
You have done {{ limits.doneRegistrationsThisMonth }} registrations
|
|
||||||
out of {{ limits.maxRegistrationsPerMonth }} for this month. Your
|
|
||||||
registrations counter resets at
|
|
||||||
{{ readableTime(limits.nextMonthBeginDateTime) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3
|
<h3
|
||||||
class="text-sm uppercase font-semibold mb-3"
|
class="text-sm uppercase font-semibold mb-3"
|
||||||
@click="showAdvanced = !showAdvanced"
|
@click="showAdvanced = !showAdvanced"
|
||||||
@@ -229,6 +202,36 @@
|
|||||||
<div class="ml-2">Show amounts given with contacts</div>
|
<div class="ml-2">Show amounts given with contacts</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<div class="flex py-2">
|
||||||
|
<button
|
||||||
|
class="text-center text-md text-blue-500"
|
||||||
|
@click="checkLimits()"
|
||||||
|
>
|
||||||
|
Check Limits
|
||||||
|
</button>
|
||||||
|
<!-- show spinner if loading limits -->
|
||||||
|
<div v-if="loadingLimits" class="ml-2">
|
||||||
|
Checking... <fa icon="spinner" class="fa-spin"></fa>
|
||||||
|
</div>
|
||||||
|
<div class="ml-2">
|
||||||
|
{{ limitsMessage }}
|
||||||
|
</div>
|
||||||
|
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9">
|
||||||
|
<span class="font-bold">Rate Limits</span>
|
||||||
|
<p>
|
||||||
|
You have done {{ limits.doneClaimsThisWeek }} claims out of
|
||||||
|
{{ limits.maxClaimsPerWeek }} for this week. Your claims counter
|
||||||
|
resets at {{ readableTime(limits.nextWeekBeginDateTime) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You have done {{ limits.doneRegistrationsThisMonth }} registrations
|
||||||
|
out of {{ limits.maxRegistrationsPerMonth }} for this month. Your
|
||||||
|
registrations counter resets at
|
||||||
|
{{ readableTime(limits.nextMonthBeginDateTime) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex py-2">
|
<div class="flex py-2">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'identity-switcher' }"
|
:to="{ name: 'identity-switcher' }"
|
||||||
@@ -287,33 +290,23 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AxiosError } from "axios/index";
|
|
||||||
import "dexie-export-import";
|
import "dexie-export-import";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
import { AppString } from "@/constants/app";
|
import { AppString } from "@/constants/app";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import { AxiosError } from "axios/index";
|
||||||
|
import QuickNav from "@/components/QuickNav";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { ErrorResponse, RateLimits } from "@/libs/endorserServer";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const Buffer = require("buffer/").Buffer;
|
const Buffer = require("buffer/").Buffer;
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class AccountViewView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
Constants = AppString;
|
Constants = AppString;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
@@ -336,10 +329,8 @@ export default class AccountViewView extends Vue {
|
|||||||
showPubCopy = false;
|
showPubCopy = false;
|
||||||
|
|
||||||
showAdvanced = false;
|
showAdvanced = false;
|
||||||
alertMessage = "";
|
|
||||||
alertTitle = "";
|
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const account = await accountsDB.accounts
|
const account = await accountsDB.accounts
|
||||||
.where("did")
|
.where("did")
|
||||||
@@ -349,7 +340,7 @@ export default class AccountViewView extends Vue {
|
|||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getHeaders(identity: IIdentifier) {
|
public async getHeaders(identity) {
|
||||||
const token = await accessToken(identity);
|
const token = await accessToken(identity);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -359,7 +350,7 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// call fn, copy text to the clipboard, then redo fn after 2 seconds
|
// call fn, copy text to the clipboard, then redo fn after 2 seconds
|
||||||
doCopyTwoSecRedo(text: string, fn: () => void) {
|
doCopyTwoSecRedo(text, fn) {
|
||||||
fn();
|
fn();
|
||||||
useClipboard()
|
useClipboard()
|
||||||
.copy(text)
|
.copy(text)
|
||||||
@@ -411,8 +402,7 @@ export default class AccountViewView extends Vue {
|
|||||||
});
|
});
|
||||||
this.checkLimitsFor(identity);
|
this.checkLimitsFor(identity);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (err) {
|
||||||
} catch (err: any) {
|
|
||||||
if (
|
if (
|
||||||
err.message ===
|
err.message ===
|
||||||
"Attempted to load account records with no identity available."
|
"Attempted to load account records with no identity available."
|
||||||
@@ -515,8 +505,7 @@ export default class AccountViewView extends Vue {
|
|||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
this.limits = resp.data;
|
this.limits = resp.data;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (error: unknown) {
|
||||||
} catch (error: any) {
|
|
||||||
if (
|
if (
|
||||||
error.message ===
|
error.message ===
|
||||||
"Attempted to load Give records with no identity available."
|
"Attempted to load Give records with no identity available."
|
||||||
@@ -527,9 +516,13 @@ export default class AccountViewView extends Vue {
|
|||||||
const serverError = error as AxiosError;
|
const serverError = error as AxiosError;
|
||||||
console.error("Bad response retrieving limits: ", serverError);
|
console.error("Bad response retrieving limits: ", serverError);
|
||||||
|
|
||||||
const data = (serverError.response &&
|
const data: ErrorResponse | undefined =
|
||||||
serverError.response.data) as ErrorResponse;
|
serverError.response && serverError.response.data;
|
||||||
this.limitsMessage = data?.error?.message || "Bad server response.";
|
if (data && data.error && data.error.message) {
|
||||||
|
this.limitsMessage = data.error.message;
|
||||||
|
} else {
|
||||||
|
this.limitsMessage = "Bad server response.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,7 +572,7 @@ export default class AccountViewView extends Vue {
|
|||||||
this.apiServer = this.apiServerInput;
|
this.apiServer = this.apiServerInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
setApiServerInput(value: string) {
|
setApiServerInput(value) {
|
||||||
this.apiServerInput = value;
|
this.apiServerInput = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
|
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||||
@@ -109,20 +109,10 @@ import {
|
|||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
import { IIdentifier } from "@veramo/core";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class ContactsView extends Vue {
|
export default class ContactsView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
contact: Contact | null = null;
|
contact: Contact | null = null;
|
||||||
@@ -134,7 +124,7 @@ export default class ContactsView extends Vue {
|
|||||||
this.numAccounts = await accountsDB.accounts.count();
|
this.numAccounts = await accountsDB.accounts.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const account = await accountsDB.accounts
|
const account = await accountsDB.accounts
|
||||||
.where("did")
|
.where("did")
|
||||||
@@ -150,7 +140,7 @@ export default class ContactsView extends Vue {
|
|||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getHeaders(identity: IIdentifier) {
|
public async getHeaders(identity) {
|
||||||
const token = await accessToken(identity);
|
const token = await accessToken(identity);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -172,8 +162,7 @@ export default class ContactsView extends Vue {
|
|||||||
if (this.activeDid && this.contact) {
|
if (this.activeDid && this.contact) {
|
||||||
this.loadGives(this.activeDid, this.contact);
|
this.loadGives(this.activeDid, this.contact);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (err) {
|
||||||
} catch (err: any) {
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -191,7 +180,7 @@ export default class ContactsView extends Vue {
|
|||||||
async loadGives(activeDid: string, contact: Contact) {
|
async loadGives(activeDid: string, contact: Contact) {
|
||||||
try {
|
try {
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
let result: Array<GiveServerRecord> = [];
|
let result = [];
|
||||||
const url =
|
const url =
|
||||||
this.apiServer +
|
this.apiServer +
|
||||||
"/api/v2/report/gives?agentDid=" +
|
"/api/v2/report/gives?agentDid=" +
|
||||||
|
|||||||
@@ -82,47 +82,35 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db";
|
||||||
import { AccountsSchema } from "@/db/tables/accounts";
|
import { AccountsSchema } from "@/db/tables/accounts";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import {
|
import { createAndSubmitGive } from "@/libs/endorserServer";
|
||||||
createAndSubmitGive,
|
import { Account } from "@/db/tables/accounts";
|
||||||
CreateAndSubmitGiveResult,
|
|
||||||
ErrorResult,
|
|
||||||
GiverInputInfo,
|
|
||||||
GiverOutputInfo,
|
|
||||||
} from "@/libs/endorserServer";
|
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon";
|
||||||
import { IIdentifier } from "@veramo/core";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { GiftedDialog, QuickNav, EntityIcon },
|
components: { GiftedDialog, QuickNav, EntityIcon },
|
||||||
})
|
})
|
||||||
export default class HomeView extends Vue {
|
export default class HomeView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
|
allAccounts: Array<Account> = [];
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
accounts: typeof AccountsSchema;
|
isHiddenSpinner = true;
|
||||||
|
accounts: AccountsSchema;
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
|
|
||||||
async beforeCreate() {
|
async beforeCreate() {
|
||||||
accountsDB.open();
|
accountsDB.open();
|
||||||
this.numAccounts = await accountsDB.accounts.count();
|
this.accounts = accountsDB.accounts;
|
||||||
|
this.numAccounts = await this.accounts.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const account = await accountsDB.accounts
|
const account = await accountsDB.accounts
|
||||||
.where("did")
|
.where("did")
|
||||||
@@ -138,7 +126,7 @@ export default class HomeView extends Vue {
|
|||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getHeaders(identity: IIdentifier) {
|
public async getHeaders(identity) {
|
||||||
const token = await accessToken(identity);
|
const token = await accessToken(identity);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -149,20 +137,23 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
|
await accountsDB.open();
|
||||||
|
this.allAccounts = await accountsDB.accounts.toArray();
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await db.contacts.toArray();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
this.feedLastViewedId = settings?.lastViewedClaimId;
|
||||||
} catch (err: any) {
|
this.updateAllFeed();
|
||||||
|
} catch (err) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text:
|
text:
|
||||||
err.message ||
|
err.userMessage ||
|
||||||
"There was an error retrieving the latest sweet, sweet action.",
|
"There was an error retrieving the latest sweet, sweet action.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
@@ -170,20 +161,37 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openDialog(giver: GiverInputInfo) {
|
public async buildHeaders() {
|
||||||
(this.$refs.customDialog as GiftedDialog).open(giver);
|
const headers = { "Content-Type": "application/json" };
|
||||||
|
|
||||||
|
if (this.activeDid) {
|
||||||
|
await accountsDB.open();
|
||||||
|
const allAccounts = await accountsDB.accounts.toArray();
|
||||||
|
const account = allAccounts.find((acc) => acc.did === this.activeDid);
|
||||||
|
const identity = JSON.parse(account?.identity || "null");
|
||||||
|
|
||||||
|
if (!identity) {
|
||||||
|
throw new Error(
|
||||||
|
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
headers["Authorization"] = "Bearer " + (await accessToken(identity));
|
||||||
|
} else {
|
||||||
|
// it's OK without auth... we just won't get any identifiers
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDialogResult(result: GiverOutputInfo) {
|
openDialog(giver) {
|
||||||
|
this.$refs.customDialog.open(giver);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDialogResult(result) {
|
||||||
if (result.action === "confirm") {
|
if (result.action === "confirm") {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.recordGive(
|
this.recordGive(result.contact?.did, result.description, result.hours);
|
||||||
result.giver?.did,
|
resolve();
|
||||||
result.description,
|
|
||||||
result.hours,
|
|
||||||
).then(() => {
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// action was "cancel" so do nothing
|
// action was "cancel" so do nothing
|
||||||
@@ -196,11 +204,7 @@ export default class HomeView extends Vue {
|
|||||||
* @param description may be an empty string
|
* @param description may be an empty string
|
||||||
* @param hours may be 0
|
* @param hours may be 0
|
||||||
*/
|
*/
|
||||||
public async recordGive(
|
public async recordGive(giverDid, description, hours) {
|
||||||
giverDid?: string,
|
|
||||||
description?: string,
|
|
||||||
hours?: number,
|
|
||||||
) {
|
|
||||||
if (!this.activeDid) {
|
if (!this.activeDid) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -239,8 +243,8 @@ export default class HomeView extends Vue {
|
|||||||
hours,
|
hours,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.isGiveCreationError(result)) {
|
if (isGiveCreationError(result)) {
|
||||||
const errorMessage = this.getGiveCreationErrorMessage(result);
|
const errorMessage = getGiveCreationErrorMessage(result);
|
||||||
console.log("Error with give result:", result);
|
console.log("Error with give result:", result);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -262,20 +266,16 @@ export default class HomeView extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (error) {
|
||||||
} catch (error: any) {
|
|
||||||
console.log("Error with give caught:", error);
|
console.log("Error with give caught:", error);
|
||||||
|
|
||||||
const message =
|
|
||||||
error.userMessage ||
|
|
||||||
error.response?.data?.error?.message ||
|
|
||||||
"There was an error recording the Give.";
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: message,
|
text:
|
||||||
|
getGiveErrorMessage(error) ||
|
||||||
|
"There was an error recording the give.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -284,12 +284,16 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
// Helper functions for readability
|
// Helper functions for readability
|
||||||
|
|
||||||
isGiveCreationError(result: CreateAndSubmitGiveResult) {
|
isGiveCreationError(result) {
|
||||||
return result.type == "error";
|
return result.status !== 201 || result.data?.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
getGiveCreationErrorMessage(result: CreateAndSubmitGiveResult) {
|
getGiveCreationErrorMessage(result) {
|
||||||
return (result as ErrorResult).error?.userMessage;
|
return result.data?.error?.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
getGiveErrorMessage(error) {
|
||||||
|
return error.userMessage || error.response?.data?.error?.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="p-6 pb-24">
|
<section id="Content" class="p-6 pb-24">
|
||||||
<!-- Heading -->
|
<!-- Heading -->
|
||||||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4">
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
||||||
Your Contact Info
|
Your Contact Info
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -17,54 +17,35 @@
|
|||||||
:dotsOptions="{ type: 'square' }"
|
:dotsOptions="{ type: 'square' }"
|
||||||
class="flex justify-center"
|
class="flex justify-center"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h1 class="text-4xl text-center font-light pt-4">Scan Contact Info</h1>
|
|
||||||
<qrcode-stream @detect="onScanDetect" @error="onScanError" />
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import QRCodeVue3 from "qr-code-generator-vue3";
|
import QRCodeVue3 from "qr-code-generator-vue3";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { QrcodeStream } from "vue-qrcode-reader";
|
import { accountsDB, db } from "@/db";
|
||||||
|
|
||||||
import { accountsDB, db } from "@/db/index";
|
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { SimpleSigner } from "@/libs/crypto";
|
import { SimpleSigner } from "@/libs/crypto";
|
||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
import {
|
|
||||||
CONTACT_URL_PREFIX,
|
|
||||||
ENDORSER_JWT_URL_LOCATION,
|
|
||||||
} from "@/libs/endorserServer";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const Buffer = require("buffer/").Buffer;
|
const Buffer = require("buffer/").Buffer;
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
QrcodeStream,
|
|
||||||
QRCodeVue3,
|
QRCodeVue3,
|
||||||
QuickNav,
|
QuickNav,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class ContactQRScanShow extends Vue {
|
export default class ContactQRScanShow extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
qrValue = "";
|
qrValue = "";
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
const account: Account | undefined = R.find(
|
const account: Account | undefined = R.find(
|
||||||
@@ -122,47 +103,9 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
issuer: identity.did,
|
issuer: identity.did,
|
||||||
signer: signer,
|
signer: signer,
|
||||||
});
|
});
|
||||||
const viewPrefix = CONTACT_URL_PREFIX + ENDORSER_JWT_URL_LOCATION;
|
const viewPrefix = "https://endorser.ch/contact?jwt=";
|
||||||
this.qrValue = viewPrefix + vcJwt;
|
this.qrValue = viewPrefix + vcJwt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param content is the result of a QR scan, an array with one item with a rawValue property
|
|
||||||
*/
|
|
||||||
// Unfortunately, there are not typescript definitions for the qrcode-stream component yet.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
onScanDetect(content: any) {
|
|
||||||
if (content[0]?.rawValue) {
|
|
||||||
console.log("onDetect", content[0].rawValue);
|
|
||||||
localStorage.setItem("contactEndorserUrl", content[0].rawValue);
|
|
||||||
this.$router.push({ name: "contacts" });
|
|
||||||
} else {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "warning",
|
|
||||||
title: "Invalid Contact QR Code",
|
|
||||||
text: "No QR code detected with contact information.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
onScanError(error: any) {
|
|
||||||
console.log("Scan was invalid:", error);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "warning",
|
|
||||||
title: "Invalid Scan",
|
|
||||||
text: "The scan was invalid.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -20,11 +20,6 @@
|
|||||||
|
|
||||||
<!-- New Contact -->
|
<!-- New Contact -->
|
||||||
<div class="mb-4 flex">
|
<div class="mb-4 flex">
|
||||||
<span class="self-center bg-slate-500 text-white px-1.5 py-1 rounded-md">
|
|
||||||
<router-link :to="{ name: 'contact-qr' }">
|
|
||||||
<fa icon="qrcode" class="fa-fw" />
|
|
||||||
</router-link>
|
|
||||||
</span>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="DID, Name, Public Key"
|
placeholder="DID, Name, Public Key"
|
||||||
@@ -126,21 +121,19 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
v-if="contact.registered"
|
||||||
|
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
|
||||||
|
title="Registered"
|
||||||
|
>
|
||||||
|
<fa icon="person-circle-check" class="fa-fw" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
@click="register(contact)"
|
@click="register(contact)"
|
||||||
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
|
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
|
||||||
|
title="Registration unknown"
|
||||||
>
|
>
|
||||||
<fa
|
<fa icon="person-circle-question" class="fa-fw" />
|
||||||
v-if="contact.registered"
|
|
||||||
icon="person-circle-check"
|
|
||||||
class="fa-fw"
|
|
||||||
title="Registered"
|
|
||||||
/>
|
|
||||||
<fa
|
|
||||||
v-else
|
|
||||||
icon="person-circle-question"
|
|
||||||
class="fa-fw"
|
|
||||||
title="Registration Unknown"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -216,26 +209,19 @@
|
|||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
|
|
||||||
import { NotificationIface } from "@/constants/app";
|
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||||
import {
|
import {
|
||||||
accessToken,
|
|
||||||
getContactPayloadFromJwtUrl,
|
|
||||||
SimpleSigner,
|
|
||||||
} from "@/libs/crypto";
|
|
||||||
import {
|
|
||||||
GiveServerRecord,
|
|
||||||
GiveVerifiableCredential,
|
GiveVerifiableCredential,
|
||||||
RegisterVerifiableCredential,
|
RegisterVerifiableCredential,
|
||||||
SERVICE_ID,
|
SERVICE_ID,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const Buffer = require("buffer/").Buffer;
|
const Buffer = require("buffer/").Buffer;
|
||||||
@@ -244,12 +230,9 @@ const Buffer = require("buffer/").Buffer;
|
|||||||
components: { QuickNav, EntityIcon },
|
components: { QuickNav, EntityIcon },
|
||||||
})
|
})
|
||||||
export default class ContactsView extends Vue {
|
export default class ContactsView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
contacts: Array<Contact> = [];
|
contacts: Array<Contact> = [];
|
||||||
contactEndorserUrl = localStorage.getItem("contactEndorserUrl") || "";
|
|
||||||
contactInput = "";
|
contactInput = "";
|
||||||
// { "did:...": concatenated-descriptions } entry for each contact
|
// { "did:...": concatenated-descriptions } entry for each contact
|
||||||
givenByMeDescriptions: Record<string, string> = {};
|
givenByMeDescriptions: Record<string, string> = {};
|
||||||
@@ -284,15 +267,9 @@ export default class ContactsView extends Vue {
|
|||||||
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
||||||
allContacts,
|
allContacts,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.contactEndorserUrl) {
|
|
||||||
await this.newContactFromScan(this.contactEndorserUrl);
|
|
||||||
localStorage.removeItem("contactEndorserUrl");
|
|
||||||
this.contactEndorserUrl = "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
const account = R.find((acc) => acc.did === activeDid, accounts);
|
const account = R.find((acc) => acc.did === activeDid, accounts);
|
||||||
@@ -306,7 +283,7 @@ export default class ContactsView extends Vue {
|
|||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getHeaders(identity: IIdentifier) {
|
public async getHeaders(identity) {
|
||||||
const token = await accessToken(identity);
|
const token = await accessToken(identity);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -315,7 +292,7 @@ export default class ContactsView extends Vue {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getHeadersAndIdentity(activeDid: string) {
|
public async getHeadersAndIdentity(activeDid) {
|
||||||
const identity = await this.getIdentity(activeDid);
|
const identity = await this.getIdentity(activeDid);
|
||||||
const headers = await this.getHeaders(identity);
|
const headers = await this.getHeaders(identity);
|
||||||
|
|
||||||
@@ -324,11 +301,11 @@ export default class ContactsView extends Vue {
|
|||||||
|
|
||||||
async loadGives() {
|
async loadGives() {
|
||||||
const handleResponse = (
|
const handleResponse = (
|
||||||
resp: { status: number; data: { data: GiveServerRecord[] } },
|
resp,
|
||||||
descriptions: Record<string, string>,
|
descriptions,
|
||||||
confirmed: Record<string, number>,
|
confirmed,
|
||||||
unconfirmed: Record<string, number>,
|
unconfirmed,
|
||||||
useRecipient: boolean,
|
useRecipient,
|
||||||
) => {
|
) => {
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
const allData = resp.data.data;
|
const allData = resp.data.data;
|
||||||
@@ -360,8 +337,9 @@ export default class ContactsView extends Vue {
|
|||||||
title: "Error With Server",
|
title: "Error With Server",
|
||||||
text:
|
text:
|
||||||
"Got an error retrieving your " +
|
"Got an error retrieving your " +
|
||||||
(useRecipient ? "given" : "received") +
|
resp.config.url.includes("recipientDid")
|
||||||
" time from the server.",
|
? "received"
|
||||||
|
: "given" + " time from the server.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -425,18 +403,6 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onClickNewContact(): Promise<void> {
|
async onClickNewContact(): Promise<void> {
|
||||||
if (!this.contactInput) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "warning",
|
|
||||||
title: "No Contact",
|
|
||||||
text: "There was no contact info to add.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let did = this.contactInput;
|
let did = this.contactInput;
|
||||||
let name, publicKeyBase64;
|
let name, publicKeyBase64;
|
||||||
const commaPos1 = this.contactInput.indexOf(",");
|
const commaPos1 = this.contactInput.indexOf(",");
|
||||||
@@ -455,74 +421,12 @@ export default class ContactsView extends Vue {
|
|||||||
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
|
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
|
||||||
}
|
}
|
||||||
const newContact = { did, name, publicKeyBase64 };
|
const newContact = { did, name, publicKeyBase64 };
|
||||||
return this.addContact(newContact);
|
await db.contacts.add(newContact);
|
||||||
}
|
const allContacts = this.contacts.concat([newContact]);
|
||||||
|
this.contacts = R.sort(
|
||||||
async newContactFromScan(url: string): Promise<void> {
|
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
||||||
const payload = getContactPayloadFromJwtUrl(url);
|
allContacts,
|
||||||
if (!payload) {
|
);
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "No Contact Info",
|
|
||||||
text: "The contact info could not be parsed.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
return this.addContact({
|
|
||||||
did: payload.iss,
|
|
||||||
name: payload.own.name,
|
|
||||||
publicKeyBase64: payload.own.publicEncKey,
|
|
||||||
} as Contact);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async addContact(newContact: Contact) {
|
|
||||||
if (!newContact.did) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Incomplete Contact",
|
|
||||||
text: "Cannot add a contact without a DID.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return db.contacts
|
|
||||||
.add(newContact)
|
|
||||||
.then(() => {
|
|
||||||
const allContacts = this.contacts.concat([newContact]);
|
|
||||||
this.contacts = R.sort(
|
|
||||||
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
|
||||||
allContacts,
|
|
||||||
);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "success",
|
|
||||||
title: "Contact added",
|
|
||||||
text: newContact.name + " was added.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("Error when adding contact to storage:", err);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Contact Not Added",
|
|
||||||
text: "An error prevented importing.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteContact(contact: Contact) {
|
async deleteContact(contact: Contact) {
|
||||||
@@ -546,9 +450,6 @@ export default class ContactsView extends Vue {
|
|||||||
confirm(
|
confirm(
|
||||||
"Are you sure you want to use one of your registrations for " +
|
"Are you sure you want to use one of your registrations for " +
|
||||||
this.nameForDid(this.contacts, contact.did) +
|
this.nameForDid(this.contacts, contact.did) +
|
||||||
(contact.registered
|
|
||||||
? " -- especially since they are already marked as registered"
|
|
||||||
: "") +
|
|
||||||
"?",
|
"?",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -700,8 +601,6 @@ export default class ContactsView extends Vue {
|
|||||||
this.apiServer +
|
this.apiServer +
|
||||||
"/api/report/canDidExplicitlySeeMe?did=" +
|
"/api/report/canDidExplicitlySeeMe?did=" +
|
||||||
encodeURIComponent(contact.did);
|
encodeURIComponent(contact.did);
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
|
||||||
const headers = await this.getHeaders(identity);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await this.axios.get(url, { headers });
|
const resp = await this.axios.get(url, { headers });
|
||||||
@@ -779,17 +678,11 @@ export default class ContactsView extends Vue {
|
|||||||
|
|
||||||
// if they have unconfirmed amounts, ask to confirm those first
|
// if they have unconfirmed amounts, ask to confirm those first
|
||||||
if (toDid == identity?.did && this.givenToMeUnconfirmed[fromDid] > 0) {
|
if (toDid == identity?.did && this.givenToMeUnconfirmed[fromDid] > 0) {
|
||||||
const isare = this.givenToMeUnconfirmed[fromDid] == 1 ? "is" : "are";
|
|
||||||
const hours = this.givenToMeUnconfirmed[fromDid] == 1 ? "hour" : "hours";
|
|
||||||
if (
|
if (
|
||||||
confirm(
|
confirm(
|
||||||
"There " +
|
"There are " +
|
||||||
isare +
|
|
||||||
" " +
|
|
||||||
this.givenToMeUnconfirmed[fromDid] +
|
this.givenToMeUnconfirmed[fromDid] +
|
||||||
" unconfirmed " +
|
" unconfirmed hours from them." +
|
||||||
hours +
|
|
||||||
" from them." +
|
|
||||||
" Would you like to confirm some of those hours?",
|
" Would you like to confirm some of those hours?",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -797,7 +690,6 @@ export default class ContactsView extends Vue {
|
|||||||
name: "contact-amounts",
|
name: "contact-amounts",
|
||||||
query: { contactDid: fromDid },
|
query: { contactDid: fromDid },
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this.isNumeric(this.hourInput)) {
|
if (!this.isNumeric(this.hourInput)) {
|
||||||
@@ -848,9 +740,7 @@ export default class ContactsView extends Vue {
|
|||||||
confirm(
|
confirm(
|
||||||
"Are you sure you want to record " +
|
"Are you sure you want to record " +
|
||||||
this.hourInput +
|
this.hourInput +
|
||||||
" hour" +
|
" hours " +
|
||||||
(this.hourInput == "1" ? "" : "s") +
|
|
||||||
" " +
|
|
||||||
toFrom +
|
toFrom +
|
||||||
description +
|
description +
|
||||||
"?",
|
"?",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<!-- Quick Search -->
|
<!-- Quick Search -->
|
||||||
<div id="QuickSearch" class="mb-4 flex" v-on:keyup.enter="searchAll()">
|
<div id="QuickSearch" class="mb-4 flex" v-on:keyup.enter="search()">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
v-model="searchTerms"
|
v-model="searchTerms"
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2"
|
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@click="searchAll()"
|
@click="search()"
|
||||||
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
||||||
>
|
>
|
||||||
<fa icon="magnifying-glass" class="fa-fw"></fa>
|
<fa icon="magnifying-glass" class="fa-fw"></fa>
|
||||||
@@ -32,8 +32,6 @@
|
|||||||
href="#"
|
href="#"
|
||||||
@click="
|
@click="
|
||||||
projects = [];
|
projects = [];
|
||||||
isLocalActive = true;
|
|
||||||
isRemoteActive = false;
|
|
||||||
searchLocal();
|
searchLocal();
|
||||||
"
|
"
|
||||||
v-bind:class="computedLocalTabClassNames()"
|
v-bind:class="computedLocalTabClassNames()"
|
||||||
@@ -51,9 +49,7 @@
|
|||||||
v-bind:class="computedRemoteTabClassNames()"
|
v-bind:class="computedRemoteTabClassNames()"
|
||||||
@click="
|
@click="
|
||||||
projects = [];
|
projects = [];
|
||||||
isRemoteActive = true;
|
search();
|
||||||
isLocalActive = false;
|
|
||||||
searchAll();
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
Remote
|
Remote
|
||||||
@@ -66,49 +62,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isLocalActive">
|
|
||||||
<div v-if="!isChoosingSearchBox">
|
|
||||||
<button
|
|
||||||
class="ml-2 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
|
||||||
@click="isChoosingSearchBox = true"
|
|
||||||
>
|
|
||||||
Select a {{ searchBox ? "Different" : "" }} Location for Nearby Search
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<button v-if="!searchBox && !isNewMarkerSet" class="m-4 px-4 py-2">
|
|
||||||
Choose Location Below for Nearby Search
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="isNewMarkerSet"
|
|
||||||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
|
||||||
@click="storeSearchBox"
|
|
||||||
>
|
|
||||||
Store This Location for Nearby Search
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="searchBox"
|
|
||||||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
|
||||||
@click="forgetSearchBox"
|
|
||||||
>
|
|
||||||
Delete Stored Location
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="isNewMarkerSet"
|
|
||||||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
|
||||||
@click="resetLatLong"
|
|
||||||
>
|
|
||||||
Reset Marker
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="ml-2 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
|
||||||
@click="cancelSearchBoxSelect"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Loading Animation -->
|
<!-- Loading Animation -->
|
||||||
<div
|
<div
|
||||||
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
||||||
@@ -118,7 +71,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Results List -->
|
<!-- Results List -->
|
||||||
<InfiniteScroll @reached-bottom="loadMoreData" v-if="!isChoosingSearchBox">
|
<InfiniteScroll @reached-bottom="loadMoreData">
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
class="border-b border-slate-300"
|
class="border-b border-slate-300"
|
||||||
@@ -150,103 +103,35 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="isLocalActive && isChoosingSearchBox"
|
|
||||||
style="height: 600px; width: 800px"
|
|
||||||
>
|
|
||||||
<l-map
|
|
||||||
ref="map"
|
|
||||||
:center="[localCenterLat, localCenterLong]"
|
|
||||||
v-model:zoom="localZoom"
|
|
||||||
@click="setMapPoint"
|
|
||||||
>
|
|
||||||
<l-tile-layer
|
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
||||||
layer-type="base"
|
|
||||||
name="OpenStreetMap"
|
|
||||||
/>
|
|
||||||
<l-marker
|
|
||||||
v-if="isNewMarkerSet"
|
|
||||||
:lat-lng="[localCenterLat, localCenterLong]"
|
|
||||||
@click="isNewMarkerSet = false"
|
|
||||||
/>
|
|
||||||
<l-rectangle
|
|
||||||
v-if="isNewMarkerSet"
|
|
||||||
:bounds="[
|
|
||||||
[localCenterLat - localLatDiff, localCenterLong - localLongDiff],
|
|
||||||
[localCenterLat + localLatDiff, localCenterLong + localLongDiff],
|
|
||||||
]"
|
|
||||||
:weight="1"
|
|
||||||
/>
|
|
||||||
</l-map>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { LeafletMouseEvent } from "leaflet";
|
|
||||||
import "leaflet/dist/leaflet.css";
|
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import {
|
|
||||||
LMap,
|
|
||||||
LMarker,
|
|
||||||
LRectangle,
|
|
||||||
LTileLayer,
|
|
||||||
} from "@vue-leaflet/vue-leaflet";
|
|
||||||
|
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import { didInfo, ProjectData } from "@/libs/endorserServer";
|
import { didInfo } from "@/libs/endorserServer";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon";
|
||||||
|
|
||||||
const DEFAULT_LAT_LONG_DIFF = 0.01;
|
|
||||||
const WORLD_ZOOM = 2;
|
|
||||||
const DEFAULT_ZOOM = 2;
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: { QuickNav, InfiniteScroll, EntityIcon },
|
||||||
LRectangle,
|
|
||||||
QuickNav,
|
|
||||||
InfiniteScroll,
|
|
||||||
EntityIcon,
|
|
||||||
LMap,
|
|
||||||
LMarker,
|
|
||||||
LTileLayer,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
export default class DiscoverView extends Vue {
|
export default class DiscoverView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
searchTerms = "";
|
searchTerms = "";
|
||||||
projects: ProjectData[] = [];
|
projects: ProjectData[] = [];
|
||||||
isChoosingSearchBox = false;
|
|
||||||
isLocalActive = true;
|
isLocalActive = true;
|
||||||
isRemoteActive = false;
|
isRemoteActive = false;
|
||||||
isNewMarkerSet = false;
|
|
||||||
localCenterLat = 0;
|
|
||||||
localCenterLong = 0;
|
|
||||||
localLatDiff = DEFAULT_LAT_LONG_DIFF;
|
|
||||||
localLongDiff = DEFAULT_LAT_LONG_DIFF;
|
|
||||||
localCount = 0;
|
localCount = 0;
|
||||||
localZoom = DEFAULT_ZOOM;
|
|
||||||
remoteCount = 0;
|
remoteCount = 0;
|
||||||
searchBox: { name: string; bbox: BoundingBox } | null = null;
|
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
|
||||||
// make this function available to the Vue template
|
// make this function available to the Vue template
|
||||||
@@ -257,9 +142,6 @@ export default class DiscoverView extends Vue {
|
|||||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.searchBox = settings?.searchBoxes?.[0] || null;
|
|
||||||
this.resetLatLong();
|
|
||||||
|
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await db.contacts.toArray();
|
||||||
|
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
@@ -269,10 +151,8 @@ export default class DiscoverView extends Vue {
|
|||||||
this.searchLocal();
|
this.searchLocal();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async buildHeaders(): Promise<HeadersInit> {
|
public async buildHeaders() {
|
||||||
const headers: HeadersInit = {
|
const headers = { "Content-Type": "application/json" };
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.activeDid) {
|
if (this.activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
@@ -293,13 +173,16 @@ export default class DiscoverView extends Vue {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async searchAll(beforeId?: string) {
|
public async search(beforeId?: string) {
|
||||||
let queryParams = "claimContents=" + encodeURIComponent(this.searchTerms);
|
let queryParams = "claimContents=" + encodeURIComponent(this.searchTerms);
|
||||||
|
|
||||||
if (beforeId) {
|
if (beforeId) {
|
||||||
queryParams = queryParams + `&beforeId=${beforeId}`;
|
queryParams = queryParams + `&beforeId=${beforeId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isRemoteActive = true;
|
||||||
|
this.isLocalActive = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -312,13 +195,12 @@ export default class DiscoverView extends Vue {
|
|||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
const details = await response.text();
|
const details = await response.text();
|
||||||
console.log("Problem with full search:", details);
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: `There was a problem accessing the server. Try again later.`,
|
text: `There was a problem accessing the server. Please try again later. (${details})`,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -331,15 +213,14 @@ export default class DiscoverView extends Vue {
|
|||||||
const plans: ProjectData[] = results.data;
|
const plans: ProjectData[] = results.data;
|
||||||
if (plans) {
|
if (plans) {
|
||||||
for (const plan of plans) {
|
for (const plan of plans) {
|
||||||
const { name, description, handleId, rowid } = plan;
|
const { name, description, handleId, rowid, issuerDid } = plan;
|
||||||
this.projects.push({ name, description, handleId, rowid });
|
this.projects.push({ name, description, handleId, rowid, issuerDid });
|
||||||
}
|
}
|
||||||
this.remoteCount = this.projects.length;
|
this.remoteCount = this.projects.length;
|
||||||
} else {
|
} else {
|
||||||
throw JSON.stringify(results);
|
throw JSON.stringify(results);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (e) {
|
||||||
} catch (e: any) {
|
|
||||||
console.log("Error with feed load:", e);
|
console.log("Error with feed load:", e);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -356,19 +237,14 @@ export default class DiscoverView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async searchLocal(beforeId?: string) {
|
public async searchLocal(beforeId?: string) {
|
||||||
if (!this.searchBox) {
|
|
||||||
this.projects = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const claimContents =
|
const claimContents =
|
||||||
"claimContents=" + encodeURIComponent(this.searchTerms);
|
"claimContents=" + encodeURIComponent(this.searchTerms);
|
||||||
let queryParams = [
|
let queryParams = [
|
||||||
claimContents,
|
claimContents,
|
||||||
"minLocLat=" + this.searchBox.bbox.minLat,
|
"minLocLat=40.901000",
|
||||||
"maxLocLat=" + this.searchBox.bbox.maxLat,
|
"maxLocLat=40.904000",
|
||||||
"westLocLon=" + this.searchBox.bbox.westLong,
|
"westLocLon=-111.914000",
|
||||||
"eastLocLon=" + this.searchBox.bbox.eastLong,
|
"eastLocLon=-111.909000",
|
||||||
].join("&");
|
].join("&");
|
||||||
|
|
||||||
if (beforeId) {
|
if (beforeId) {
|
||||||
@@ -377,6 +253,8 @@ export default class DiscoverView extends Vue {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
this.isLocalActive = true;
|
||||||
|
this.isRemoteActive = false;
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
this.apiServer + "/api/v2/report/plansByLocation?" + queryParams,
|
this.apiServer + "/api/v2/report/plansByLocation?" + queryParams,
|
||||||
{
|
{
|
||||||
@@ -387,13 +265,12 @@ export default class DiscoverView extends Vue {
|
|||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
const details = await response.text();
|
const details = await response.text();
|
||||||
console.log("Problem with nearby search:", details);
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: "There was a problem accessing the server. Try again later.",
|
text: `There was a problem accessing the server. Please try again later. (${details})`,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -418,8 +295,7 @@ export default class DiscoverView extends Vue {
|
|||||||
} else {
|
} else {
|
||||||
throw JSON.stringify(results);
|
throw JSON.stringify(results);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (e) {
|
||||||
} catch (e: any) {
|
|
||||||
console.log("Error with feed load:", e);
|
console.log("Error with feed load:", e);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -445,7 +321,7 @@ export default class DiscoverView extends Vue {
|
|||||||
if (this.isLocalActive) {
|
if (this.isLocalActive) {
|
||||||
this.searchLocal(latestProject["rowid"]);
|
this.searchLocal(latestProject["rowid"]);
|
||||||
} else if (this.isRemoteActive) {
|
} else if (this.isRemoteActive) {
|
||||||
this.searchAll(latestProject["rowid"]);
|
this.search(latestProject["rowid"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,128 +338,6 @@ export default class DiscoverView extends Vue {
|
|||||||
this.$router.push(route);
|
this.$router.push(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
setMapPoint(event: LeafletMouseEvent) {
|
|
||||||
if (this.isNewMarkerSet) {
|
|
||||||
this.localLatDiff = Math.abs(event.latlng.lat - this.localCenterLat);
|
|
||||||
this.localLongDiff = Math.abs(event.latlng.lng - this.localCenterLong);
|
|
||||||
} else {
|
|
||||||
// marker is not set
|
|
||||||
this.localCenterLat = event.latlng.lat;
|
|
||||||
this.localCenterLong = event.latlng.lng;
|
|
||||||
|
|
||||||
let latDiff = DEFAULT_LAT_LONG_DIFF;
|
|
||||||
let longDiff = DEFAULT_LAT_LONG_DIFF;
|
|
||||||
// Guess at a size for the bounding box.
|
|
||||||
// This doesn't seem like the right approach but it's the only way I can find to get the screen bounds.
|
|
||||||
const bounds = event.target.boxZoom?._map?.getBounds();
|
|
||||||
if (bounds) {
|
|
||||||
latDiff = Math.abs(bounds._northEast.lat - bounds._southWest.lat) / 8;
|
|
||||||
longDiff = Math.abs(bounds._northEast.lng - bounds._southWest.lng) / 8;
|
|
||||||
}
|
|
||||||
this.localLatDiff = latDiff;
|
|
||||||
this.localLongDiff = longDiff;
|
|
||||||
this.isNewMarkerSet = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetLatLong() {
|
|
||||||
if (this.searchBox?.bbox) {
|
|
||||||
const bbox = this.searchBox.bbox;
|
|
||||||
this.localCenterLat = (bbox.maxLat + bbox.minLat) / 2;
|
|
||||||
this.localCenterLong = (bbox.eastLong + bbox.westLong) / 2;
|
|
||||||
this.localLatDiff = (bbox.maxLat - bbox.minLat) / 2;
|
|
||||||
this.localLongDiff = (bbox.eastLong - bbox.westLong) / 2;
|
|
||||||
this.localZoom = WORLD_ZOOM;
|
|
||||||
this.isNewMarkerSet = true;
|
|
||||||
} else {
|
|
||||||
this.isNewMarkerSet = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async storeSearchBox() {
|
|
||||||
if (this.localCenterLong || this.localCenterLat) {
|
|
||||||
try {
|
|
||||||
const newSearchBox = {
|
|
||||||
name: "Local",
|
|
||||||
bbox: {
|
|
||||||
eastLong: this.localCenterLong + this.localLongDiff,
|
|
||||||
maxLat: this.localCenterLat + this.localLatDiff,
|
|
||||||
minLat: this.localCenterLat - this.localLatDiff,
|
|
||||||
westLong: this.localCenterLong - this.localLongDiff,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
await db.open();
|
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
|
||||||
searchBoxes: [newSearchBox],
|
|
||||||
});
|
|
||||||
this.searchBox = newSearchBox;
|
|
||||||
this.isChoosingSearchBox = false;
|
|
||||||
this.searchLocal();
|
|
||||||
} catch (err) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error Updating Search Settings",
|
|
||||||
text: "Try going to a different page and then coming back.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
console.error(
|
|
||||||
"Telling user to retry the location search setting because:",
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "warning",
|
|
||||||
title: "No Location Selected",
|
|
||||||
text: "Select a location on the map.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async forgetSearchBox() {
|
|
||||||
try {
|
|
||||||
await db.open();
|
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
|
||||||
searchBoxes: [],
|
|
||||||
});
|
|
||||||
this.searchBox = null;
|
|
||||||
this.localCenterLat = 0;
|
|
||||||
this.localCenterLong = 0;
|
|
||||||
this.localLatDiff = DEFAULT_LAT_LONG_DIFF;
|
|
||||||
this.localLongDiff = DEFAULT_LAT_LONG_DIFF;
|
|
||||||
this.localZoom = DEFAULT_ZOOM;
|
|
||||||
this.isChoosingSearchBox = false;
|
|
||||||
this.isNewMarkerSet = false;
|
|
||||||
this.searchLocal();
|
|
||||||
} catch (err) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error Updating Search Settings",
|
|
||||||
text: "Try going to a different page and then coming back.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
console.error(
|
|
||||||
"Telling user to retry the location search setting because:",
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public cancelSearchBoxSelect() {
|
|
||||||
this.isChoosingSearchBox = false;
|
|
||||||
this.localZoom = WORLD_ZOOM;
|
|
||||||
}
|
|
||||||
|
|
||||||
public computedLocalTabClassNames() {
|
public computedLocalTabClassNames() {
|
||||||
return {
|
return {
|
||||||
"inline-block": true,
|
"inline-block": true,
|
||||||
|
|||||||
@@ -181,7 +181,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Package from "../../package.json";
|
import * as Package from "../../package.json";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class Help extends Vue {
|
export default class Help extends Vue {
|
||||||
|
|||||||
@@ -202,42 +202,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import {
|
import { createAndSubmitGive, didInfo } from "@/libs/endorserServer";
|
||||||
createAndSubmitGive,
|
|
||||||
didInfo,
|
|
||||||
GiverInputInfo,
|
|
||||||
GiverOutputInfo,
|
|
||||||
GiveServerRecord,
|
|
||||||
} from "@/libs/endorserServer";
|
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon";
|
||||||
import { IIdentifier } from "@veramo/core";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { GiftedDialog, QuickNav, EntityIcon },
|
components: { GiftedDialog, QuickNav, EntityIcon },
|
||||||
})
|
})
|
||||||
export default class HomeView extends Vue {
|
export default class HomeView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
feedAllLoaded = false;
|
feedAllLoaded = false;
|
||||||
feedData = [];
|
feedData = [];
|
||||||
feedPreviousOldestId?: string;
|
feedPreviousOldestId = null;
|
||||||
feedLastViewedId?: string;
|
feedLastViewedId = null;
|
||||||
isHiddenSpinner = true;
|
isHiddenSpinner = true;
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
|
|
||||||
@@ -246,7 +230,7 @@ export default class HomeView extends Vue {
|
|||||||
this.numAccounts = await accountsDB.accounts.count();
|
this.numAccounts = await accountsDB.accounts.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const account = await accountsDB.accounts
|
const account = await accountsDB.accounts
|
||||||
.where("did")
|
.where("did")
|
||||||
@@ -262,7 +246,7 @@ export default class HomeView extends Vue {
|
|||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getHeaders(identity: IIdentifier) {
|
public async getHeaders(identity) {
|
||||||
const token = await accessToken(identity);
|
const token = await accessToken(identity);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -284,8 +268,7 @@ export default class HomeView extends Vue {
|
|||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await db.contacts.toArray();
|
||||||
this.feedLastViewedId = settings?.lastViewedClaimId;
|
this.feedLastViewedId = settings?.lastViewedClaimId;
|
||||||
this.updateAllFeed();
|
this.updateAllFeed();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (err) {
|
||||||
} catch (err: any) {
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -301,9 +284,7 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async buildHeaders() {
|
public async buildHeaders() {
|
||||||
const headers: HeadersInit = {
|
const headers = { "Content-Type": "application/json" };
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.activeDid) {
|
if (this.activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
@@ -326,7 +307,7 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
public async updateAllFeed() {
|
public async updateAllFeed() {
|
||||||
this.isHiddenSpinner = false;
|
this.isHiddenSpinner = false;
|
||||||
await this.retrieveClaims(this.apiServer, this.feedPreviousOldestId)
|
await this.retrieveClaims(this.apiServer, null, this.feedPreviousOldestId)
|
||||||
.then(async (results) => {
|
.then(async (results) => {
|
||||||
if (results.data.length > 0) {
|
if (results.data.length > 0) {
|
||||||
this.feedData = this.feedData.concat(results.data);
|
this.feedData = this.feedData.concat(results.data);
|
||||||
@@ -361,7 +342,7 @@ export default class HomeView extends Vue {
|
|||||||
this.isHiddenSpinner = true;
|
this.isHiddenSpinner = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async retrieveClaims(endorserApiServer: string, beforeId?: string) {
|
public async retrieveClaims(endorserApiServer, identifier, beforeId) {
|
||||||
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
|
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
endorserApiServer + "/api/v2/report/gives?" + beforeQuery,
|
endorserApiServer + "/api/v2/report/gives?" + beforeQuery,
|
||||||
@@ -384,13 +365,13 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
giveDescription(giveRecord: GiveServerRecord) {
|
giveDescription(giveRecord) {
|
||||||
// claim.claim happen for some claims wrapped in a Verifiable Credential
|
let claim = giveRecord.fullClaim;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
if (claim.claim) {
|
||||||
const claim = (giveRecord.fullClaim as any).claim || giveRecord.fullClaim;
|
claim = claim.claim;
|
||||||
|
}
|
||||||
// agent.did is for legacy data, before March 2023
|
// agent.did is for legacy data, before March 2023
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const giverDid = claim.agent?.identifier || claim.agent?.did;
|
||||||
const giverDid = claim.agent?.identifier || (claim.agent as any)?.did;
|
|
||||||
const giverInfo = didInfo(
|
const giverInfo = didInfo(
|
||||||
giverDid,
|
giverDid,
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
@@ -401,9 +382,7 @@ export default class HomeView extends Vue {
|
|||||||
? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
|
? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
|
||||||
: claim.description || "something unknown";
|
: claim.description || "something unknown";
|
||||||
// recipient.did is for legacy data, before March 2023
|
// recipient.did is for legacy data, before March 2023
|
||||||
const gaveRecipientId =
|
const gaveRecipientId = claim.recipient?.identifier || claim.recipient?.did;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
claim.recipient?.identifier || (claim.recipient as any)?.did;
|
|
||||||
const gaveRecipientInfo = gaveRecipientId
|
const gaveRecipientInfo = gaveRecipientId
|
||||||
? " to " +
|
? " to " +
|
||||||
didInfo(
|
didInfo(
|
||||||
@@ -416,28 +395,23 @@ export default class HomeView extends Vue {
|
|||||||
return giverInfo + " gave " + gaveAmount + gaveRecipientInfo;
|
return giverInfo + " gave " + gaveAmount + gaveRecipientInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAmount(code: string, amt: number) {
|
displayAmount(code, amt) {
|
||||||
return "" + amt + " " + this.currencyShortWordForCode(code, amt === 1);
|
return "" + amt + " " + this.currencyShortWordForCode(code, amt === 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
currencyShortWordForCode(unitCode: string, single: boolean) {
|
currencyShortWordForCode(unitCode, single) {
|
||||||
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
openDialog(giver: GiverInputInfo) {
|
openDialog(giver) {
|
||||||
(this.$refs.customDialog as GiftedDialog).open(giver);
|
this.$refs.customDialog.open(giver);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDialogResult(result: GiverOutputInfo) {
|
handleDialogResult(result) {
|
||||||
if (result.action === "confirm") {
|
if (result.action === "confirm") {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.recordGive(
|
this.recordGive(result.giver?.did, result.description, result.hours);
|
||||||
result.giver?.did,
|
resolve();
|
||||||
result.description,
|
|
||||||
result.hours,
|
|
||||||
).then(() => {
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// action was "cancel" so do nothing
|
// action was "cancel" so do nothing
|
||||||
@@ -450,11 +424,7 @@ export default class HomeView extends Vue {
|
|||||||
* @param description may be an empty string
|
* @param description may be an empty string
|
||||||
* @param hours may be 0
|
* @param hours may be 0
|
||||||
*/
|
*/
|
||||||
public async recordGive(
|
public async recordGive(giverDid, description, hours) {
|
||||||
giverDid?: string,
|
|
||||||
description?: string,
|
|
||||||
hours?: number,
|
|
||||||
) {
|
|
||||||
if (!this.activeDid) {
|
if (!this.activeDid) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -516,19 +486,16 @@ export default class HomeView extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (error) {
|
||||||
} catch (error: any) {
|
|
||||||
console.log("Error with give caught:", error);
|
console.log("Error with give caught:", error);
|
||||||
const message =
|
|
||||||
error.userMessage ||
|
|
||||||
error.response?.data?.error?.message ||
|
|
||||||
"There was an error recording the give.";
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: message,
|
text:
|
||||||
|
this.getGiveErrorMessage(error) ||
|
||||||
|
"There was an error recording the give.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -537,14 +504,16 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
// Helper functions for readability
|
// Helper functions for readability
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
isGiveCreationError(result) {
|
||||||
isGiveCreationError(result: any) {
|
|
||||||
return result.status !== 201 || result.data?.error;
|
return result.status !== 201 || result.data?.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
getGiveCreationErrorMessage(result) {
|
||||||
getGiveCreationErrorMessage(result: any) {
|
|
||||||
return result.data?.error?.message;
|
return result.data?.error?.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGiveErrorMessage(error) {
|
||||||
|
return error.userMessage || error.response?.data?.error?.message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -67,33 +67,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { AppString } from "@/constants/app";
|
import { AppString } from "@/constants/app";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db";
|
||||||
import { AccountsSchema } from "@/db/tables/accounts";
|
import { AccountsSchema } from "@/db/tables/accounts";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class IdentitySwitcherView extends Vue {
|
export default class IdentitySwitcherView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
Constants = AppString;
|
Constants = AppString;
|
||||||
public accounts: typeof AccountsSchema;
|
public accounts: AccountsSchema;
|
||||||
public activeDid = "";
|
public activeDid;
|
||||||
public apiServer = "";
|
public firstName;
|
||||||
public apiServerInput = "";
|
public lastName;
|
||||||
public firstName = "";
|
public otherIdentities = [];
|
||||||
public lastName = "";
|
|
||||||
public otherIdentities: Array<{ did: string }> = [];
|
|
||||||
public showContactGives = false;
|
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const account = await accountsDB.accounts
|
const account = await accountsDB.accounts
|
||||||
.where("did")
|
.where("did")
|
||||||
@@ -130,20 +118,31 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.$notify(
|
if (
|
||||||
{
|
err.message ===
|
||||||
group: "alert",
|
"Attempted to load account records with no identity available."
|
||||||
type: "danger",
|
) {
|
||||||
title: "Error Loading Accounts",
|
this.limitsMessage = "No identity.";
|
||||||
text: "Clear your cache and start over (after data backup).",
|
this.loadingLimits = false;
|
||||||
},
|
} else {
|
||||||
-1,
|
this.$notify(
|
||||||
);
|
{
|
||||||
console.error("Telling user to clear cache at page create because:", err);
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error Creating Account",
|
||||||
|
text: "Clear your cache and start over (after data backup).",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
"Telling user to clear cache at page create because:",
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async switchAccount(did?: string) {
|
async switchAccount(did: string) {
|
||||||
// 0 means none
|
// 0 means none
|
||||||
if (did === "0") {
|
if (did === "0") {
|
||||||
did = undefined;
|
did = undefined;
|
||||||
@@ -152,7 +151,7 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
activeDid: did,
|
activeDid: did,
|
||||||
});
|
});
|
||||||
this.activeDid = did || "";
|
this.activeDid = did;
|
||||||
this.otherIdentities = [];
|
this.otherIdentities = [];
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ import {
|
|||||||
deriveAddress,
|
deriveAddress,
|
||||||
newIdentifier,
|
newIdentifier,
|
||||||
} from "../libs/crypto";
|
} from "../libs/crypto";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ import {
|
|||||||
deriveAddress,
|
deriveAddress,
|
||||||
newIdentifier,
|
newIdentifier,
|
||||||
} from "../libs/crypto";
|
} from "../libs/crypto";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -87,9 +87,9 @@ export default class ImportAccountView extends Vue {
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
const seedDids: Record<string, Array<string>> = {};
|
const seedDids = {};
|
||||||
accounts.forEach((account) => {
|
accounts.forEach((account) => {
|
||||||
const prevDids: Array<string> = seedDids[account.mnemonic] || [];
|
const prevDids = seedDids[account.mnemonic] || [];
|
||||||
seedDids[account.mnemonic] = prevDids.concat([account.did]);
|
seedDids[account.mnemonic] = prevDids.concat([account.did]);
|
||||||
});
|
});
|
||||||
this.didArrays = Object.values(seedDids);
|
this.didArrays = Object.values(seedDids);
|
||||||
@@ -107,9 +107,9 @@ export default class ImportAccountView extends Vue {
|
|||||||
public async incrementDerivation() {
|
public async incrementDerivation() {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
// find the maximum derivation path for the selected DIDs
|
// find the maximum derivation path for the selected DIDs
|
||||||
const selectedArray: Array<string> =
|
const selectedArray: Array<string> = this.didArrays.find(
|
||||||
this.didArrays.find((dids) => dids[0] === this.selectedArrayFirstDid) ||
|
(dids) => dids[0] === this.selectedArrayFirstDid,
|
||||||
[];
|
);
|
||||||
const allMatchingAccounts = await accountsDB.accounts
|
const allMatchingAccounts = await accountsDB.accounts
|
||||||
.where("did")
|
.where("did")
|
||||||
.anyOf(...selectedArray)
|
.anyOf(...selectedArray)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { db } from "@/db/index";
|
import { db } from "@/db";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|||||||
@@ -107,26 +107,16 @@ import * as didJwt from "did-jwt";
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
||||||
|
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||||
import { useAppStore } from "@/store/app";
|
import { useAppStore } from "@/store/app";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { PlanVerifiableCredential } from "@/libs/endorserServer";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { LMap, LMarker, LTileLayer },
|
components: { LMap, LMarker, LTileLayer },
|
||||||
})
|
})
|
||||||
export default class NewEditProjectView extends Vue {
|
export default class NewEditProjectView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
description = "";
|
description = "";
|
||||||
@@ -143,7 +133,7 @@ export default class NewEditProjectView extends Vue {
|
|||||||
this.numAccounts = await accountsDB.accounts.count();
|
this.numAccounts = await accountsDB.accounts.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const account = await accountsDB.accounts
|
const account = await accountsDB.accounts
|
||||||
.where("did")
|
.where("did")
|
||||||
@@ -159,7 +149,7 @@ export default class NewEditProjectView extends Vue {
|
|||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getHeaders(identity: IIdentifier) {
|
public async getHeaders(identity) {
|
||||||
const token = await accessToken(identity);
|
const token = await accessToken(identity);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -218,7 +208,7 @@ export default class NewEditProjectView extends Vue {
|
|||||||
|
|
||||||
private async SaveProject(identity: IIdentifier) {
|
private async SaveProject(identity: IIdentifier) {
|
||||||
// Make a claim
|
// Make a claim
|
||||||
const vcClaim: PlanVerifiableCredential = {
|
const vcClaim: VerifiableCredential = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "PlanAction",
|
"@type": "PlanAction",
|
||||||
name: this.projectName,
|
name: this.projectName,
|
||||||
@@ -273,15 +263,17 @@ export default class NewEditProjectView extends Vue {
|
|||||||
// version shows up here: https://api.endorser.ch/api-docs/
|
// version shows up here: https://api.endorser.ch/api-docs/
|
||||||
if (resp.data?.success?.handleId || resp.data?.success?.fullIri) {
|
if (resp.data?.success?.handleId || resp.data?.success?.fullIri) {
|
||||||
this.errorMessage = "";
|
this.errorMessage = "";
|
||||||
|
|
||||||
// handleId is new in server v release-1.6.0; remove fullIri when that
|
// handleId is new in server v release-1.6.0; remove fullIri when that
|
||||||
// version shows up here: https://api.endorser.ch/api-docs/
|
// version shows up here: https://api.endorser.ch/api-docs/
|
||||||
useAppStore().setProjectId(
|
useAppStore().setProjectId(
|
||||||
resp.data.success.handleId || resp.data.success.fullIri,
|
resp.data.success.handleId || resp.data.success.fullIri,
|
||||||
);
|
);
|
||||||
setTimeout(
|
setTimeout(
|
||||||
function (that: NewEditProjectView) {
|
function (that: Vue) {
|
||||||
that.$router.push({ name: "project" });
|
const route = {
|
||||||
|
name: "project",
|
||||||
|
};
|
||||||
|
that.$router.push(route);
|
||||||
},
|
},
|
||||||
2000,
|
2000,
|
||||||
this,
|
this,
|
||||||
@@ -289,13 +281,11 @@ export default class NewEditProjectView extends Vue {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let userMessage = "There was an error saving the project.";
|
let userMessage = "There was an error saving the project.";
|
||||||
const serverError = error as AxiosError<{
|
const serverError = error as AxiosError;
|
||||||
error?: { message?: string };
|
|
||||||
}>;
|
|
||||||
if (serverError) {
|
if (serverError) {
|
||||||
if (Object.prototype.hasOwnProperty.call(serverError, "message")) {
|
if (Object.prototype.hasOwnProperty.call(serverError, "message")) {
|
||||||
console.log(serverError);
|
console.log(serverError);
|
||||||
userMessage = serverError.response?.data?.error?.message || ""; // This is info for the user.
|
userMessage = serverError.response.data.error.message; // This is info for the user.
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
|
|||||||
@@ -40,10 +40,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "dexie-export-import";
|
import "dexie-export-import";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db";
|
||||||
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
|
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class AccountViewView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
|
|||||||
@@ -204,40 +204,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AxiosError, RawAxiosRequestHeaders } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import * as moment from "moment";
|
import * as moment from "moment";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import {
|
import {
|
||||||
createAndSubmitGive,
|
createAndSubmitGive,
|
||||||
didInfo,
|
didInfo,
|
||||||
GiverInputInfo,
|
|
||||||
GiverOutputInfo,
|
|
||||||
GiveServerRecord,
|
GiveServerRecord,
|
||||||
ResultWithType,
|
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon";
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { GiftedDialog, QuickNav, EntityIcon },
|
components: { GiftedDialog, QuickNav, EntityIcon },
|
||||||
})
|
})
|
||||||
export default class ProjectViewView extends Vue {
|
export default class ProjectViewView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
@@ -271,7 +259,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
this.LoadProject(identity);
|
this.LoadProject(identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const account = await accountsDB.accounts
|
const account = await accountsDB.accounts
|
||||||
.where("did")
|
.where("did")
|
||||||
@@ -287,7 +275,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getHeaders(identity: IIdentifier) {
|
public async getHeaders(identity) {
|
||||||
const token = await accessToken(identity);
|
const token = await accessToken(identity);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -305,12 +293,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Isn't there a better way to make this available to the template?
|
// Isn't there a better way to make this available to the template?
|
||||||
didInfo(
|
didInfo(did, activeDid, dids, contacts) {
|
||||||
did: string,
|
|
||||||
activeDid: string,
|
|
||||||
dids: Array<string>,
|
|
||||||
contacts: Array<Contact>,
|
|
||||||
) {
|
|
||||||
return didInfo(did, activeDid, dids, contacts);
|
return didInfo(did, activeDid, dids, contacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +310,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
this.apiServer +
|
this.apiServer +
|
||||||
"/api/claim/byHandle/" +
|
"/api/claim/byHandle/" +
|
||||||
encodeURIComponent(this.projectId);
|
encodeURIComponent(this.projectId);
|
||||||
const headers: RawAxiosRequestHeaders = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
};
|
};
|
||||||
if (identity) {
|
if (identity) {
|
||||||
@@ -461,9 +444,8 @@ export default class ProjectViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openDialog(contact: GiverInputInfo) {
|
openDialog(contact) {
|
||||||
const dialog: GiftedDialog = this.$refs.customDialog as GiftedDialog;
|
this.$refs.customDialog.open(contact);
|
||||||
dialog.open(contact);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getOpenStreetMapUrl() {
|
getOpenStreetMapUrl() {
|
||||||
@@ -480,16 +462,11 @@ export default class ProjectViewView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDialogResult(result: GiverOutputInfo) {
|
handleDialogResult(result) {
|
||||||
if (result.action === "confirm") {
|
if (result.action === "confirm") {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.recordGive(
|
this.recordGive(result.contact?.did, result.description, result.hours);
|
||||||
result.giver?.did,
|
resolve();
|
||||||
result.description,
|
|
||||||
result.hours,
|
|
||||||
).then(() => {
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// action was not "confirm" so do nothing
|
// action was not "confirm" so do nothing
|
||||||
@@ -502,7 +479,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
* @param description may be an empty string
|
* @param description may be an empty string
|
||||||
* @param hours may be 0
|
* @param hours may be 0
|
||||||
*/
|
*/
|
||||||
async recordGive(giverDid?: string, description?: string, hours?: number) {
|
async recordGive(giverDid, description, hours) {
|
||||||
if (!this.activeDid) {
|
if (!this.activeDid) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -526,7 +503,10 @@ export default class ProjectViewView extends Vue {
|
|||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
const result = await createAndSubmitGive(
|
const result = await createAndSubmitGive(
|
||||||
this.axios,
|
this.axios,
|
||||||
@@ -538,7 +518,21 @@ export default class ProjectViewView extends Vue {
|
|||||||
hours,
|
hours,
|
||||||
this.projectId,
|
this.projectId,
|
||||||
);
|
);
|
||||||
if (result.type == "success") {
|
|
||||||
|
if (result.status !== 201 || result.data?.error) {
|
||||||
|
console.log("Error with give result:", result);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text:
|
||||||
|
result.data?.error?.message ||
|
||||||
|
"There was an error recording the give.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -548,27 +542,21 @@ export default class ProjectViewView extends Vue {
|
|||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
console.log("Error with give creation:", result);
|
|
||||||
if (result.type != "error") {
|
|
||||||
console.log(
|
|
||||||
"... and it has an unexpected result type of",
|
|
||||||
(result as ResultWithType).type,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const message =
|
|
||||||
result?.error?.userMessage ||
|
|
||||||
"There was an error recording the Give.";
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: message,
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error with give caught:", e);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text:
|
||||||
|
e.userMessage ||
|
||||||
|
e.response?.data?.error?.message ||
|
||||||
|
"There was an error recording the give.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,34 +72,22 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon";
|
||||||
import { ProjectData } from "@/libs/endorserServer";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { InfiniteScroll, QuickNav, EntityIcon },
|
components: { InfiniteScroll, QuickNav, EntityIcon },
|
||||||
})
|
})
|
||||||
export default class ProjectsView extends Vue {
|
export default class ProjectsView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
projects: ProjectData[] = [];
|
projects: ProjectData[] = [];
|
||||||
current: IIdentifier;
|
current: IIdentifier;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
alertTitle = "";
|
|
||||||
alertMessage = "";
|
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
|
|
||||||
async beforeCreate() {
|
async beforeCreate() {
|
||||||
@@ -124,30 +112,21 @@ export default class ProjectsView extends Vue {
|
|||||||
if (resp.status === 200 || !resp.data.data) {
|
if (resp.status === 200 || !resp.data.data) {
|
||||||
const plans: ProjectData[] = resp.data.data;
|
const plans: ProjectData[] = resp.data.data;
|
||||||
for (const plan of plans) {
|
for (const plan of plans) {
|
||||||
const { name, description, handleId, rowid } = plan;
|
const { name, description, handleId = plan.fullIri, rowid } = plan;
|
||||||
this.projects.push({ name, description, handleId, rowid });
|
this.projects.push({ name, description, handleId, rowid });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("Bad server response & data:", resp.status, resp.data);
|
console.log("Bad server response & data:", resp.status, resp.data);
|
||||||
this.$notify(
|
throw Error("Failed to get projects from the server.");
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "Failed to get projects from the server. Try again later.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (error) {
|
||||||
} catch (error: any) {
|
console.error("Got error loading projects:", error.message);
|
||||||
console.error("Got error loading projects:", error.message || error);
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: "Got an error loading projects.",
|
text: "Got an error loading projects: " + error.message,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -191,7 +170,7 @@ export default class ProjectsView extends Vue {
|
|||||||
await this.dataLoader(url, token);
|
await this.dataLoader(url, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const account = await accountsDB.accounts
|
const account = await accountsDB.accounts
|
||||||
.where("did")
|
.where("did")
|
||||||
|
|||||||
@@ -55,27 +55,14 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
|
|
||||||
interface Account {
|
|
||||||
mnemonic: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class SeedBackupView extends Vue {
|
export default class SeedBackupView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
activeAccount = null;
|
||||||
|
|
||||||
activeAccount: Account | null | undefined = null;
|
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
showSeed = false;
|
showSeed = false;
|
||||||
|
|
||||||
@@ -90,7 +77,7 @@ export default class SeedBackupView extends Vue {
|
|||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
this.numAccounts = accounts.length;
|
this.numAccounts = accounts.length;
|
||||||
this.activeAccount = R.find((acc) => acc.did === activeDid, accounts);
|
this.activeAccount = R.find((acc) => acc.did === activeDid, accounts);
|
||||||
} catch (err: unknown) {
|
} catch (err) {
|
||||||
console.error("Got an error loading an identity:", err);
|
console.error("Got an error loading an identity:", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { accountsDB } from "@/db/index";
|
import { accountsDB } from "@/db";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {},
|
components: {},
|
||||||
|
|||||||
@@ -37,32 +37,15 @@
|
|||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
|
import { SVGRenderer } from "three/addons/renderers/SVGRenderer.js";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { World } from "@/components/World/World.js";
|
import { World } from "@/components/World/World.js";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav";
|
||||||
|
|
||||||
interface RendererSVGType {
|
|
||||||
domElement: Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dictionary<T> {
|
|
||||||
[key: string]: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({ components: { World, QuickNav } })
|
@Component({ components: { World, QuickNav } })
|
||||||
export default class StatisticsView extends Vue {
|
export default class StatisticsView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
world: World;
|
world: World;
|
||||||
worldProperties: Dictionary<number> = {};
|
worldProperties: WorldProperties = {};
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
try {
|
try {
|
||||||
@@ -70,14 +53,14 @@ export default class StatisticsView extends Vue {
|
|||||||
const newWorld = new World(container, this);
|
const newWorld = new World(container, this);
|
||||||
newWorld.start();
|
newWorld.start();
|
||||||
this.world = newWorld;
|
this.world = newWorld;
|
||||||
} catch (err: unknown) {
|
} catch (err) {
|
||||||
const error = err as Error;
|
console.log(err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Mounting Error",
|
title: "Mounting Error",
|
||||||
text: error.message,
|
text: err.message,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -95,12 +78,12 @@ export default class StatisticsView extends Vue {
|
|||||||
ExportToSVG(rendererSVG, "test.svg");
|
ExportToSVG(rendererSVG, "test.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
public setWorldProperty(propertyName: string, propertyValue: number) {
|
public setWorldProperty(propertyName, propertyValue) {
|
||||||
this.worldProperties[propertyName] = propertyValue;
|
this.worldProperties[propertyName] = propertyValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExportToSVG(rendererSVG: RendererSVGType, filename: string) {
|
function ExportToSVG(rendererSVG, filename) {
|
||||||
const XMLS = new XMLSerializer();
|
const XMLS = new XMLSerializer();
|
||||||
const svgfile = XMLS.serializeToString(rendererSVG.domElement);
|
const svgfile = XMLS.serializeToString(rendererSVG.domElement);
|
||||||
const svgData = svgfile;
|
const svgData = svgfile;
|
||||||
|
|||||||
@@ -1,47 +1,41 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true,
|
"target": "esnext",
|
||||||
"resolveJsonModule": true,
|
"module": "esnext",
|
||||||
"target": "esnext",
|
"strict": true,
|
||||||
"module": "esnext",
|
"jsx": "preserve",
|
||||||
"strict": true,
|
"moduleResolution": "node",
|
||||||
"strictPropertyInitialization": false,
|
"experimentalDecorators": true,
|
||||||
"jsx": "preserve",
|
"skipLibCheck": true,
|
||||||
"moduleResolution": "node",
|
"esModuleInterop": true,
|
||||||
"experimentalDecorators": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"skipLibCheck": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"esModuleInterop": true,
|
"useDefineForClassFields": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"sourceMap": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"baseUrl": ".",
|
||||||
"useDefineForClassFields": true,
|
"types": [
|
||||||
"sourceMap": true,
|
"webpack-env"
|
||||||
"baseUrl": "./src",
|
|
||||||
"types": [
|
|
||||||
"webpack-env"
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"@/components/*": ["components/*"],
|
|
||||||
"@/views/*": ["views/*"],
|
|
||||||
"@/db/*": ["db/*"],
|
|
||||||
"@/libs/*": ["libs/*"],
|
|
||||||
"@/constants/*": ["constants/*"],
|
|
||||||
"@/store/*": ["store/*"],
|
|
||||||
},
|
|
||||||
"lib": [
|
|
||||||
"esnext",
|
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"scripthost"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*.ts",
|
|
||||||
"src/**/*.tsx",
|
|
||||||
"src/**/*.vue",
|
|
||||||
"tests/**/*.ts",
|
|
||||||
"tests/**/*.tsx"
|
|
||||||
],
|
],
|
||||||
"exclude": [
|
"paths": {
|
||||||
"node_modules"
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"tests/**/*.ts",
|
||||||
|
"tests/**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user