@@ -271,8 +272,15 @@ export default class DiscoverView extends Vue {
const plans: PlanData[] = results.data;
if (plans) {
for (const plan of plans) {
- const { name, description, handleId, issuerDid, rowid } = plan;
- this.projects.push({ name, description, handleId, issuerDid, rowid });
+ const { name, description, handleId, image, issuerDid, rowid } = plan;
+ this.projects.push({
+ name,
+ description,
+ handleId,
+ image,
+ issuerDid,
+ rowid,
+ });
}
this.remoteCount = this.projects.length;
} else {
diff --git a/src/views/NewEditProjectView.vue b/src/views/NewEditProjectView.vue
index d1abe2cec..62547b116 100644
--- a/src/views/NewEditProjectView.vue
+++ b/src/views/NewEditProjectView.vue
@@ -29,10 +29,31 @@
v-model="fullClaim.name"
/>
+
@@ -155,23 +176,31 @@ import "leaflet/dist/leaflet.css";
import { AxiosError } from "axios";
import * as didJwt from "did-jwt";
import { DateTime } from "luxon";
+import { IIdentifier } from "@veramo/core";
import { Component, Vue } from "vue-facing-decorator";
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
import QuickNav from "@/components/QuickNav.vue";
-import { NotificationIface } from "@/constants/app";
+import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken, SimpleSigner } from "@/libs/crypto";
+import * as libsUtil from "@/libs/util";
import { useAppStore } from "@/store/app";
-import { IIdentifier } from "@veramo/core";
import { PlanVerifiableCredential } from "@/libs/endorserServer";
+import ImageMethodDialog from "@/components/ImageMethodDialog.vue";
@Component({
- components: { LMap, LMarker, LTileLayer, QuickNav },
+ components: { ImageMethodDialog, LMap, LMarker, LTileLayer, QuickNav },
})
export default class NewEditProjectView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
+ errNote(message) {
+ this.$notify(
+ { group: "alert", type: "danger", title: "Error", text: message },
+ 5000,
+ );
+ }
activeDid = "";
agentDid = "";
@@ -183,6 +212,7 @@ export default class NewEditProjectView extends Vue {
name: "",
description: "",
}; // this default is only to avoid errors before plan is loaded
+ imageUrl = "";
includeLocation = false;
isHiddenSave = false;
isHiddenSpinner = true;
@@ -197,10 +227,7 @@ export default class NewEditProjectView extends Vue {
zoneName = DateTime.local().zoneName;
zoom = 2;
- async beforeCreate() {
- await accountsDB.open();
- this.numAccounts = await accountsDB.accounts.count();
- }
+ libsUtil = libsUtil;
public async getIdentity(activeDid: string) {
await accountsDB.open();
@@ -228,6 +255,9 @@ export default class NewEditProjectView extends Vue {
}
async mounted() {
+ await accountsDB.open();
+ this.numAccounts = await accountsDB.accounts.count();
+
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = (settings?.activeDid as string) || "";
@@ -235,7 +265,7 @@ export default class NewEditProjectView extends Vue {
if (this.projectId) {
if (this.numAccounts === 0) {
- console.error("Error: no account was found.");
+ this.errNote("There was a problem loading your account info.");
} else {
const identity = await this.getIdentity(this.activeDid);
if (!identity) {
@@ -264,6 +294,7 @@ export default class NewEditProjectView extends Vue {
if (resp.status === 200) {
this.projectIssuerDid = resp.data.issuer;
this.fullClaim = resp.data.claim;
+ this.imageUrl = resp.data.claim.image || "";
this.lastClaimJwtId = resp.data.id;
if (this.fullClaim?.location) {
this.includeLocation = true;
@@ -283,6 +314,84 @@ export default class NewEditProjectView extends Vue {
}
} catch (error) {
console.error("Got error retrieving that project", error);
+ this.errNote("There was an error retrieving that project.");
+ }
+ }
+
+ openImageDialog() {
+ (this.$refs.imageDialog as ImageMethodDialog).open((imgUrl) => {
+ this.imageUrl = imgUrl;
+ }, "PlanAction");
+ }
+
+ confirmDeleteImage() {
+ this.$notify(
+ {
+ group: "modal",
+ type: "confirm",
+ title: "Are you sure you want to delete the image?",
+ text: "",
+ onYes: this.deleteImage,
+ },
+ -1,
+ );
+ }
+
+ async deleteImage() {
+ if (!this.imageUrl) {
+ return;
+ }
+ try {
+ const identity = await libsUtil.getIdentity(this.activeDid);
+ const token = await accessToken(identity);
+ const response = await this.axios.delete(
+ DEFAULT_IMAGE_API_SERVER +
+ "/image/" +
+ encodeURIComponent(this.imageUrl),
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ },
+ );
+ if (response.status === 204) {
+ // don't bother with a notification
+ // (either they'll simply continue or they're canceling and going back)
+ } else {
+ console.error("Problem deleting image:", response);
+ this.$notify(
+ {
+ group: "alert",
+ type: "danger",
+ title: "Error",
+ text: "There was a problem deleting the image.",
+ },
+ 5000,
+ );
+ return;
+ }
+
+ this.imageUrl = "";
+ } catch (error) {
+ console.error("Error deleting image:", error);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ if ((error as any).response.status === 404) {
+ console.log("The image was already deleted:", error);
+
+ this.imageUrl = "";
+
+ // it already doesn't exist so we won't say anything to the user
+ } else {
+ this.$notify(
+ {
+ group: "alert",
+ type: "danger",
+ title: "Error",
+ text: "There was an error deleting the image.",
+ },
+ 5000,
+ );
+ }
}
}
@@ -299,6 +408,11 @@ export default class NewEditProjectView extends Vue {
} else {
delete vcClaim.agent;
}
+ if (this.imageUrl) {
+ vcClaim.image = this.imageUrl;
+ } else {
+ delete vcClaim.image;
+ }
if (this.includeLocation) {
vcClaim.location = {
geo: {
diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue
index 707bf511e..0361e3d91 100644
--- a/src/views/ProjectViewView.vue
+++ b/src/views/ProjectViewView.vue
@@ -22,13 +22,14 @@
-
-
+
+
+ :imageUrl="imageUrl"
+ class="block border border-slate-300 rounded-md max-h-16 max-w-16"
+ />
@@ -399,6 +400,7 @@ export default class ProjectViewView extends Vue {
fulfillersToHitLimit = false;
givesToThis: Array
= [];
givesHitLimit = false;
+ imageUrl = "";
issuer = "";
latitude = 0;
longitude = 0;
@@ -427,7 +429,7 @@ export default class ProjectViewView extends Vue {
const accountsArr: Account[] = await accounts?.toArray();
this.allMyDids = accountsArr.map((acc) => acc.did);
const account = accountsArr.find((acc) => acc.did === this.activeDid);
- const identity = JSON.parse(account?.identity || "null");
+ const identity = JSON.parse((account?.identity as string) || "null");
const pathParam = window.location.pathname.substring("/project/".length);
if (pathParam) {
@@ -488,6 +490,7 @@ export default class ProjectViewView extends Vue {
startDateTime.toLocaleTimeString();
}
this.agentDid = resp.data.claim?.agent?.identifier;
+ this.imageUrl = resp.data.claim?.image;
this.issuer = resp.data.issuer;
this.name = resp.data.claim?.name || "(no name)";
this.description = resp.data.claim?.description || "(no description)";
diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue
index c5878adbb..659445ce7 100644
--- a/src/views/ProjectsView.vue
+++ b/src/views/ProjectsView.vue
@@ -86,12 +86,12 @@
:key="offer.handleId"
>
-
+
+ class="inline-block align-middle border border-slate-300 rounded-md max-h-12 max-w-12"
+ />
-
+
+ :imageUrl="project.image"
+ class="inline-block align-middle border border-slate-300 rounded-md max-h-12 max-w-12"
+ />
@@ -211,6 +212,7 @@