You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							327 lines
						
					
					
						
							9.8 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							327 lines
						
					
					
						
							9.8 KiB
						
					
					
				
								<template>
							 | 
						|
								  <QuickNav selected="Contacts" />
							 | 
						|
								  <TopMessage />
							 | 
						|
								
							 | 
						|
								  <!-- CONTENT -->
							 | 
						|
								  <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
							 | 
						|
								    <!-- Breadcrumb -->
							 | 
						|
								    <div id="ViewBreadcrumb" class="mb-8">
							 | 
						|
								      <h1 class="text-lg text-center font-light relative px-7">
							 | 
						|
								        <!-- Back -->
							 | 
						|
								        <button
							 | 
						|
								          @click="$router.go(-1)"
							 | 
						|
								          class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
							 | 
						|
								        >
							 | 
						|
								          <fa icon="chevron-left" class="fa-fw"></fa>
							 | 
						|
								        </button>
							 | 
						|
								        Identifier Details
							 | 
						|
								      </h1>
							 | 
						|
								    </div>
							 | 
						|
								
							 | 
						|
								    <!-- Identity Details -->
							 | 
						|
								    <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
							 | 
						|
								      <div>
							 | 
						|
								        <h2 class="text-xl font-semibold">
							 | 
						|
								          {{
							 | 
						|
								            didInfoForContact(viewingDid, activeDid, contact, allMyDids)
							 | 
						|
								              .displayName
							 | 
						|
								          }}
							 | 
						|
								        </h2>
							 | 
						|
								        <button @click="showDidDetails = !showDidDetails" class="ml-2 mr-2">
							 | 
						|
								          Details
							 | 
						|
								          <fa v-if="showDidDetails" icon="chevron-up" class="text-blue-400" />
							 | 
						|
								          <fa v-else icon="chevron-down" class="text-blue-400" />
							 | 
						|
								        </button>
							 | 
						|
								        <!-- Keep the dump contents directly between > and < to avoid weird spacing. -->
							 | 
						|
								        <pre
							 | 
						|
								          v-if="showDidDetails"
							 | 
						|
								          class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md"
							 | 
						|
								          >{{ contactYaml }}</pre
							 | 
						|
								        >
							 | 
						|
								      </div>
							 | 
						|
								      <div class="flex justify-center mt-4">
							 | 
						|
								        <span v-if="contact?.profileImageUrl" class="flex justify-between">
							 | 
						|
								          <EntityIcon
							 | 
						|
								            :icon-size="96"
							 | 
						|
								            :profileImageUrl="contact?.profileImageUrl"
							 | 
						|
								            class="inline-block align-text-bottom border border-slate-300 rounded"
							 | 
						|
								            @click="showLargeIdenticonUrl = contact?.profileImageUrl"
							 | 
						|
								          />
							 | 
						|
								        </span>
							 | 
						|
								      </div>
							 | 
						|
								      <div class="mt-4">
							 | 
						|
								        <div class="flex justify-center">Auto-Generated Icon:</div>
							 | 
						|
								        <div class="flex justify-center">
							 | 
						|
								          <EntityIcon
							 | 
						|
								            :entityId="viewingDid"
							 | 
						|
								            :iconSize="64"
							 | 
						|
								            class="inline-block align-middle border border-slate-300 rounded-md mr-1"
							 | 
						|
								            @click="showLargeIdenticonId = viewingDid"
							 | 
						|
								          />
							 | 
						|
								        </div>
							 | 
						|
								      </div>
							 | 
						|
								      <div
							 | 
						|
								        v-if="showLargeIdenticonId || showLargeIdenticonUrl"
							 | 
						|
								        class="fixed z-[100] top-0 inset-x-0 w-full"
							 | 
						|
								      >
							 | 
						|
								        <div
							 | 
						|
								          class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
							 | 
						|
								        >
							 | 
						|
								          <EntityIcon
							 | 
						|
								            :entityId="showLargeIdenticonId"
							 | 
						|
								            :iconSize="512"
							 | 
						|
								            :profileImageUrl="showLargeIdenticonUrl"
							 | 
						|
								            class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
							 | 
						|
								            @click="
							 | 
						|
								              showLargeIdenticonId = undefined;
							 | 
						|
								              showLargeIdenticonUrl = undefined;
							 | 
						|
								            "
							 | 
						|
								          />
							 | 
						|
								        </div>
							 | 
						|
								      </div>
							 | 
						|
								    </div>
							 | 
						|
								
							 | 
						|
								    <!-- Loading Animation -->
							 | 
						|
								    <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"
							 | 
						|
								      v-if="isLoading"
							 | 
						|
								    >
							 | 
						|
								      <fa icon="spinner" class="fa-spin-pulse"></fa>
							 | 
						|
								    </div>
							 | 
						|
								    <!-- Results List -->
							 | 
						|
								    <div v-if="claims.length > 0" class="mt-4">
							 | 
						|
								      <div class="text-l font-bold text-center">Claims That Involve Them</div>
							 | 
						|
								    </div>
							 | 
						|
								    <InfiniteScroll @reached-bottom="loadMoreData">
							 | 
						|
								      <ul>
							 | 
						|
								        <li
							 | 
						|
								          class="border-b border-slate-300"
							 | 
						|
								          v-for="claim in claims"
							 | 
						|
								          :key="claim.handleId"
							 | 
						|
								        >
							 | 
						|
								          <div class="grid grid-cols-12 gap-4">
							 | 
						|
								            <span class="col-span-2">
							 | 
						|
								              {{ claim.issuedAt.substring(0, 10) }}
							 | 
						|
								            </span>
							 | 
						|
								            <span class="col-span-2">
							 | 
						|
								              {{ capitalizeAndInsertSpacesBeforeCaps(claim.claimType) }}
							 | 
						|
								            </span>
							 | 
						|
								            <span class="col-span-2">
							 | 
						|
								              {{ claimAmount(claim) }}
							 | 
						|
								            </span>
							 | 
						|
								            <span class="col-span-5">
							 | 
						|
								              {{ claimDescription(claim) }}
							 | 
						|
								            </span>
							 | 
						|
								            <span class="col-span-1">
							 | 
						|
								              <a @click="onClickLoadClaim(claim.id)" class="cursor-pointer">
							 | 
						|
								                <fa icon="file-lines" class="pl-2 pt-1 text-blue-500" />
							 | 
						|
								              </a>
							 | 
						|
								            </span>
							 | 
						|
								          </div>
							 | 
						|
								        </li>
							 | 
						|
								      </ul>
							 | 
						|
								    </InfiniteScroll>
							 | 
						|
								
							 | 
						|
								    <div
							 | 
						|
								      v-if="!isLoading && claims.length === 0"
							 | 
						|
								      class="flex justify-center mt-4"
							 | 
						|
								    >
							 | 
						|
								      <span>They Are in No Claims Visible to You</span>
							 | 
						|
								    </div>
							 | 
						|
								  </section>
							 | 
						|
								</template>
							 | 
						|
								
							 | 
						|
								<script lang="ts">
							 | 
						|
								import { Component, Vue } from "vue-facing-decorator";
							 | 
						|
								import { Router } from "vue-router";
							 | 
						|
								import * as yaml from "js-yaml";
							 | 
						|
								
							 | 
						|
								import QuickNav from "@/components/QuickNav.vue";
							 | 
						|
								import InfiniteScroll from "@/components/InfiniteScroll.vue";
							 | 
						|
								import TopMessage from "@/components/TopMessage.vue";
							 | 
						|
								import { NotificationIface } from "@/constants/app";
							 | 
						|
								import { accountsDB, db } from "@/db/index";
							 | 
						|
								import { Contact } from "@/db/tables/contacts";
							 | 
						|
								import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
							 | 
						|
								import {
							 | 
						|
								  capitalizeAndInsertSpacesBeforeCaps,
							 | 
						|
								  didInfoForContact,
							 | 
						|
								  displayAmount,
							 | 
						|
								  getHeaders,
							 | 
						|
								  GenericCredWrapper,
							 | 
						|
								  GenericVerifiableCredential,
							 | 
						|
								  GiveVerifiableCredential,
							 | 
						|
								  OfferVerifiableCredential,
							 | 
						|
								} from "@/libs/endorserServer";
							 | 
						|
								import * as libsUtil from "@/libs/util";
							 | 
						|
								import EntityIcon from "@/components/EntityIcon.vue";
							 | 
						|
								
							 | 
						|
								@Component({
							 | 
						|
								  components: {
							 | 
						|
								    EntityIcon,
							 | 
						|
								    InfiniteScroll,
							 | 
						|
								    QuickNav,
							 | 
						|
								    TopMessage,
							 | 
						|
								  },
							 | 
						|
								})
							 | 
						|
								export default class DIDView extends Vue {
							 | 
						|
								  $notify!: (notification: NotificationIface, timeout?: number) => void;
							 | 
						|
								
							 | 
						|
								  libsUtil = libsUtil;
							 | 
						|
								  yaml = yaml;
							 | 
						|
								
							 | 
						|
								  activeDid = "";
							 | 
						|
								  allMyDids: Array<string> = [];
							 | 
						|
								  apiServer = "";
							 | 
						|
								  claims: Array<GenericCredWrapper<GenericVerifiableCredential>> = [];
							 | 
						|
								  contact?: Contact;
							 | 
						|
								  contactYaml = "";
							 | 
						|
								  hitEnd = false;
							 | 
						|
								  isLoading = false;
							 | 
						|
								  searchBox: { name: string; bbox: BoundingBox } | null = null;
							 | 
						|
								  showDidDetails = false;
							 | 
						|
								  showLargeIdenticonId?: string;
							 | 
						|
								  showLargeIdenticonUrl?: string;
							 | 
						|
								  viewingDid?: string;
							 | 
						|
								
							 | 
						|
								  capitalizeAndInsertSpacesBeforeCaps = capitalizeAndInsertSpacesBeforeCaps;
							 | 
						|
								  didInfoForContact = didInfoForContact;
							 | 
						|
								  displayAmount = displayAmount;
							 | 
						|
								
							 | 
						|
								  async mounted() {
							 | 
						|
								    await db.open();
							 | 
						|
								    const settings = await db.settings.get(MASTER_SETTINGS_KEY);
							 | 
						|
								    this.activeDid = (settings?.activeDid as string) || "";
							 | 
						|
								    this.apiServer = (settings?.apiServer as string) || "";
							 | 
						|
								
							 | 
						|
								    const pathParam = window.location.pathname.substring("/did/".length);
							 | 
						|
								    if (pathParam) {
							 | 
						|
								      this.viewingDid = decodeURIComponent(pathParam);
							 | 
						|
								      this.contact = await db.contacts.get(this.viewingDid);
							 | 
						|
								      this.contactYaml = yaml.dump(this.contact)
							 | 
						|
								      await this.loadClaimsAbout();
							 | 
						|
								    } else {
							 | 
						|
								      this.$notify(
							 | 
						|
								        {
							 | 
						|
								          group: "alert",
							 | 
						|
								          type: "danger",
							 | 
						|
								          title: "Error",
							 | 
						|
								          text: "No claim ID was provided.",
							 | 
						|
								        },
							 | 
						|
								        -1,
							 | 
						|
								      );
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    await accountsDB.open();
							 | 
						|
								    const allAccounts = await accountsDB.accounts.toArray();
							 | 
						|
								    this.allMyDids = allAccounts.map((acc) => acc.did);
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  /**
							 | 
						|
								   * Data loader used by infinite scroller
							 | 
						|
								   * @param payload is the flag from the InfiniteScroll indicating if it should load
							 | 
						|
								   **/
							 | 
						|
								  async loadMoreData(payload: boolean) {
							 | 
						|
								    if (this.claims.length > 0 && !this.hitEnd && payload) {
							 | 
						|
								      this.loadClaimsAbout();
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  public async loadClaimsAbout() {
							 | 
						|
								    if (!this.viewingDid) {
							 | 
						|
								      console.error("This should never be called without a DID.");
							 | 
						|
								      return;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    const queryParams = "claimContents=" + encodeURIComponent(this.viewingDid);
							 | 
						|
								    let postfix = "";
							 | 
						|
								    if (this.claims.length > 0) {
							 | 
						|
								      postfix = "&beforeId=" + this.claims[this.claims.length - 1].id;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    try {
							 | 
						|
								      this.isLoading = true;
							 | 
						|
								      const response = await fetch(
							 | 
						|
								        this.apiServer + "/api/v2/report/claims?" + queryParams + postfix,
							 | 
						|
								        {
							 | 
						|
								          method: "GET",
							 | 
						|
								          headers: await getHeaders(this.activeDid),
							 | 
						|
								        },
							 | 
						|
								      );
							 | 
						|
								
							 | 
						|
								      if (response.status !== 200) {
							 | 
						|
								        const details = await response.text();
							 | 
						|
								        console.error("Problem with full search:", details);
							 | 
						|
								        this.$notify(
							 | 
						|
								          {
							 | 
						|
								            group: "alert",
							 | 
						|
								            type: "danger",
							 | 
						|
								            title: "Error",
							 | 
						|
								            text: `There was a problem accessing the server. Try again later.`,
							 | 
						|
								          },
							 | 
						|
								          5000,
							 | 
						|
								        );
							 | 
						|
								        return;
							 | 
						|
								      }
							 | 
						|
								
							 | 
						|
								      const results = await response.json();
							 | 
						|
								      this.claims = this.claims.concat(results.data);
							 | 
						|
								      this.hitEnd = !results.hitLimit;
							 | 
						|
								
							 | 
						|
								      // eslint-disable-next-line @typescript-eslint/no-explicit-any
							 | 
						|
								    } catch (e: any) {
							 | 
						|
								      console.error("Error with feed load:", e);
							 | 
						|
								      this.$notify(
							 | 
						|
								        {
							 | 
						|
								          group: "alert",
							 | 
						|
								          type: "danger",
							 | 
						|
								          title: "Error",
							 | 
						|
								          text: e.userMessage || "There was a problem retrieving claims.",
							 | 
						|
								        },
							 | 
						|
								        -1,
							 | 
						|
								      );
							 | 
						|
								    } finally {
							 | 
						|
								      this.isLoading = false;
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  onClickLoadClaim(jwtId: string) {
							 | 
						|
								    const route = {
							 | 
						|
								      path: "/claim/" + encodeURIComponent(jwtId),
							 | 
						|
								    };
							 | 
						|
								    (this.$router as Router).push(route);
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  public claimAmount(claim: GenericVerifiableCredential) {
							 | 
						|
								    if (claim.claimType === "GiveAction") {
							 | 
						|
								      const giveClaim = claim.claim as GiveVerifiableCredential;
							 | 
						|
								      if (giveClaim.object?.unitCode && giveClaim.object?.amountOfThisGood) {
							 | 
						|
								        return displayAmount(
							 | 
						|
								          giveClaim.object.unitCode,
							 | 
						|
								          giveClaim.object.amountOfThisGood,
							 | 
						|
								        );
							 | 
						|
								      } else {
							 | 
						|
								        return "";
							 | 
						|
								      }
							 | 
						|
								    } else if (claim.claimType === "Offer") {
							 | 
						|
								      const offerClaim = claim.claim as OfferVerifiableCredential;
							 | 
						|
								      if (
							 | 
						|
								        offerClaim.includesObject?.unitCode &&
							 | 
						|
								        offerClaim.includesObject?.amountOfThisGood
							 | 
						|
								      ) {
							 | 
						|
								        return displayAmount(
							 | 
						|
								          offerClaim.includesObject.unitCode,
							 | 
						|
								          offerClaim.includesObject.amountOfThisGood,
							 | 
						|
								        );
							 | 
						|
								      } else {
							 | 
						|
								        return "";
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								    return "";
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  claimDescription(claim: GenericVerifiableCredential) {
							 | 
						|
								    return claim.claim.name || claim.claim.description || "";
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								</script>
							 | 
						|
								
							 |