From 9904e082aa048f15c5992fce7834af1ca617c16c Mon Sep 17 00:00:00 2001 From: Trent Larson <trent@trentlarson.com> Date: Wed, 18 Dec 2024 16:05:43 -0700 Subject: [PATCH] refine claim certificate view --- src/libs/endorserServer.ts | 10 ++++ src/views/ClaimCertificateView.vue | 75 +++++++++++++++++++++++------- src/views/ClaimView.vue | 48 ++++++++++++------- 3 files changed, 98 insertions(+), 35 deletions(-) diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 54ff1d2f2..ff4c6f341 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -476,6 +476,16 @@ export function didInfo( return didInfoForContact(did, activeDid, contact, allMyDids).displayName; } +/** + * return text description without any references to "you" as user + */ +export function didInfoForCertificate( + did: string | undefined, + contacts: Contact[], +): string { + return didInfoForContact(did, undefined, contactForDid(did, contacts), []).displayName; +} + let passkeyAccessToken: string = ""; let passkeyTokenExpirationEpochSeconds: number = 0; diff --git a/src/views/ClaimCertificateView.vue b/src/views/ClaimCertificateView.vue index c87c39eac..a39567ae0 100644 --- a/src/views/ClaimCertificateView.vue +++ b/src/views/ClaimCertificateView.vue @@ -21,8 +21,8 @@ import { Component, Vue } from "vue-facing-decorator"; import { nextTick } from "vue"; import QRCode from "qrcode"; -import { NotificationIface } from "@/constants/app"; -import { retrieveSettingsForActiveAccount } from "@/db/index"; +import { APP_SERVER, NotificationIface } from "@/constants/app"; +import { db, retrieveSettingsForActiveAccount } from "@/db/index"; import * as endorserServer from "@/libs/endorserServer"; @Component @@ -56,7 +56,9 @@ export default class ClaimViewCertificate extends Vue { if (response.ok) { this.claimData = await response.json(); await nextTick(); // Wait for the DOM to update - this.drawCanvas(); + if (this.claimData) { + this.drawCanvas(this.claimData); + } } else { throw new Error(`Error fetching claim: ${response.statusText}`); } @@ -71,7 +73,12 @@ export default class ClaimViewCertificate extends Vue { } } - drawCanvas() { + async drawCanvas( + claimData: endorserServer.GenericCredWrapper<endorserServer.GenericVerifiableCredential>, + ) { + await db.open(); + const allContacts = await db.contacts.toArray(); + const canvas = this.$refs.claimCanvas as HTMLCanvasElement; if (canvas) { const CANVAS_WIDTH = 1100; @@ -96,21 +103,43 @@ export default class ClaimViewCertificate extends Vue { ctx.font = "bold 20px Arial"; const claimTypeText = this.endorserServer.capitalizeAndInsertSpacesBeforeCaps( - this.claimData.claimType, + claimData.claimType || "", ); const claimTypeWidth = ctx.measureText(claimTypeText).width; ctx.fillText( claimTypeText, (CANVAS_WIDTH - claimTypeWidth) / 2, // Center horizontally - CANVAS_HEIGHT * 0.35, + CANVAS_HEIGHT * 0.33, ); + if (claimData.claim.agent) { + const presentedText = "Presented to "; + ctx.font = "14px Arial"; + const presentedWidth = ctx.measureText(presentedText).width; + ctx.fillText( + presentedText, + (CANVAS_WIDTH - presentedWidth) / 2, // Center horizontally + CANVAS_HEIGHT * 0.37, + ); + const agentText = endorserServer.didInfoForCertificate( + claimData.claim.agent, + allContacts, + ); + ctx.font = "bold 20px Arial"; + const agentWidth = ctx.measureText(agentText).width; + ctx.fillText( + agentText, + (CANVAS_WIDTH - agentWidth) / 2, // Center horizontally + CANVAS_HEIGHT * 0.4, + ); + } + const descriptionText = - this.claimData.claim.description || this.claimData.claim.name; + claimData.claim.name || claimData.claim.description; if (descriptionText) { const descriptionLine = descriptionText.length > 50 - ? descriptionText.substring(0, 47) + "..." + ? descriptionText.substring(0, 85) + "..." : descriptionText; ctx.font = "14px Arial"; const descriptionWidth = ctx.measureText(descriptionLine).width; @@ -121,26 +150,38 @@ export default class ClaimViewCertificate extends Vue { ); } + // Draw claim issuer & recipient + if (claimData.issuer) { + ctx.font = "14px Arial"; + const issuerText = + "Issued by " + + endorserServer.didInfoForCertificate( + claimData.issuer, + allContacts, + ); + ctx.fillText(issuerText, CANVAS_WIDTH * 0.3, CANVAS_HEIGHT * 0.6); + } + // Draw claim ID ctx.font = "14px Arial"; - ctx.fillText(this.claimId, CANVAS_WIDTH * 0.3, CANVAS_HEIGHT * 0.62); + ctx.fillText(this.claimId, CANVAS_WIDTH * 0.3, CANVAS_HEIGHT * 0.7); ctx.fillText( "via EndorserSearch.com", CANVAS_WIDTH * 0.3, - CANVAS_HEIGHT * 0.65, + CANVAS_HEIGHT * 0.73, ); // Generate and draw QR code const qrCodeCanvas = document.createElement("canvas"); - await QRCode.toCanvas(qrCodeCanvas, window.location.href, { - width: 150, - color: { light: "#0000" /* Transparent background */ }, - }); - ctx.drawImage( + await QRCode.toCanvas( qrCodeCanvas, - CANVAS_WIDTH * 0.57, - CANVAS_HEIGHT * 0.55, + APP_SERVER + "/claim/" + this.claimId, + { + width: 150, + color: { light: "#0000" /* Transparent background */ }, + }, ); + ctx.drawImage(qrCodeCanvas, CANVAS_WIDTH * 0.6, CANVAS_HEIGHT * 0.55); }; } } diff --git a/src/views/ClaimView.vue b/src/views/ClaimView.vue index 1e593e32a..c8f2725d9 100644 --- a/src/views/ClaimView.vue +++ b/src/views/ClaimView.vue @@ -20,24 +20,36 @@ <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"> <div class="block flex gap-4 overflow-hidden"> <div class="overflow-hidden"> - <h2 class="text-md font-bold"> - {{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }} - <button - v-if=" - ['GiveAction', 'Offer', 'PlanAction'].includes( - veriClaim.claimType as string, - ) && veriClaim.issuer === activeDid - // a PlanAction agent also could edit one of those, - // but rather than add more Plan-specific logic to detect the agent - // we'll let them click the Project link and edit from there - " - @click="onClickEditClaim" - title="Edit" - data-testId="editClaimButton" - > - <fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1" /> - </button> - </h2> + <div class="flex justify-between"> + <h2 class="text-md font-bold"> + {{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }} + <button + v-if=" + ['GiveAction', 'Offer', 'PlanAction'].includes( + veriClaim.claimType as string, + ) && veriClaim.issuer === activeDid + // a PlanAction agent also could edit one of those, + // but rather than add more Plan-specific logic to detect the agent + // we'll let them click the Project link and edit from there + " + @click="onClickEditClaim" + title="Edit" + data-testId="editClaimButton" + > + <fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1" /> + </button> + </h2> + <!-- + <div> + <router-link + :to="'/claim-cert/' + encodeURIComponent(veriClaim.id)" + class="text-blue-500 mt-2" + > + <fa icon="square" class="text-white bg-yellow-500 p-1" /> + </router-link> + </div> + --> + </div> <div class="text-sm"> <div data-testId="description"> <fa icon="message" class="fa-fw text-slate-400" />