show contact's or user's icon in more places
This commit is contained in:
@@ -5,15 +5,23 @@
|
||||
import { createAvatar, StyleOptions } from "@dicebear/core";
|
||||
import { avataaars } from "@dicebear/collection";
|
||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
|
||||
@Component
|
||||
export default class EntityIcon extends Vue {
|
||||
@Prop entityId = "";
|
||||
@Prop contact: Contact;
|
||||
@Prop entityId = ""; // overridden by contact.did or profileImageUrl
|
||||
@Prop iconSize = 0;
|
||||
@Prop profileImageUrl = ""; // overridden by contact.profileImageUrl
|
||||
|
||||
generateIcon() {
|
||||
const imageUrl = this.contact?.profileImageUrl || this.profileImageUrl;
|
||||
if (imageUrl) {
|
||||
return `<img src="${imageUrl}" alt="avatar" width="${this.iconSize}" height="${this.iconSize}" />`;
|
||||
} else {
|
||||
const identifier = this.contact?.did || this.entityId;
|
||||
const options: StyleOptions<object> = {
|
||||
seed: this.entityId || "",
|
||||
seed: (identifier as string) || "",
|
||||
size: this.iconSize,
|
||||
};
|
||||
const avatar = createAvatar(avataaars, options);
|
||||
@@ -21,5 +29,6 @@ export default class EntityIcon extends Vue {
|
||||
return svgString;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -133,7 +133,8 @@ export default class GiftedPhotoDialog extends Vue {
|
||||
activeDeviceNumber = 0;
|
||||
activeDid = "";
|
||||
blob: Blob | null = null;
|
||||
crop: boolean;
|
||||
claimType = "GiveAction";
|
||||
crop = false;
|
||||
mirror = false;
|
||||
numDevices = 0;
|
||||
setImage: (arg: string) => void = () => {};
|
||||
@@ -162,9 +163,10 @@ export default class GiftedPhotoDialog extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
open(setImageFn: (arg: string) => void, crop?: boolean) {
|
||||
open(setImageFn: (arg: string) => void, crop?: boolean, claimType?: string) {
|
||||
this.visible = true;
|
||||
this.crop = !!crop;
|
||||
this.claimType = claimType || "GiveAction";
|
||||
const bottomNav = document.querySelector("#QuickNav") as HTMLElement;
|
||||
if (bottomNav) {
|
||||
bottomNav.style.display = "none";
|
||||
@@ -329,7 +331,7 @@ export default class GiftedPhotoDialog extends Vue {
|
||||
return;
|
||||
}
|
||||
formData.append("image", this.blob, "snapshot.png"); // png is set in snapshot()
|
||||
formData.append("claimType", "GiveAction");
|
||||
formData.append("claimType", this.claimType);
|
||||
try {
|
||||
const response = await axios.post(
|
||||
DEFAULT_IMAGE_API_SERVER + "/image",
|
||||
|
||||
@@ -2,6 +2,7 @@ export interface Contact {
|
||||
did: string;
|
||||
name?: string;
|
||||
nextPubKeyHashB64?: string; // base64-encoded SHA256 hash of next public key
|
||||
profileImageUrl?: string;
|
||||
publicKeyBase64?: string;
|
||||
seesMe?: boolean;
|
||||
registered?: boolean;
|
||||
|
||||
@@ -63,24 +63,34 @@
|
||||
|
||||
<!-- Identity Details -->
|
||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||
<h2 v-if="givenName" class="text-xl font-semibold mb-2">
|
||||
<div v-if="givenName">
|
||||
<h2 class="text-xl font-semibold mb-2">
|
||||
{{ givenName }}
|
||||
<router-link :to="{ name: 'new-edit-account' }">
|
||||
<fa icon="pen" class="text-xs text-blue-500 mb-1"></fa>
|
||||
</router-link>
|
||||
</h2>
|
||||
</div>
|
||||
<span v-else>
|
||||
<router-link
|
||||
:to="{ name: 'new-edit-account' }"
|
||||
class="block w-full text-center text-md bg-amber-200 text-blue-500 uppercase border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
|
||||
>
|
||||
Set Your Name
|
||||
</router-link>
|
||||
</span>
|
||||
<div class="flex justify-center mt-4">
|
||||
<span v-if="profileImageUrl" class="flex justify-between">
|
||||
<a
|
||||
:href="profileImageUrl"
|
||||
target="_blank"
|
||||
class="text-blue-500 ml-4"
|
||||
>
|
||||
<img :src="profileImageUrl" class="h-24 rounded-xl" />
|
||||
</a>
|
||||
<EntityIcon
|
||||
:icon-size="96"
|
||||
:profileImageUrl="profileImageUrl"
|
||||
class="inline-block align-text-bottom border border-slate-300 rounded"
|
||||
@click="showLargeIdenticonUrl = profileImageUrl"
|
||||
/>
|
||||
<fa
|
||||
icon="trash-can"
|
||||
@click="confirmDeleteImage"
|
||||
class="text-red-500 fa-fw ml-8 mt-10"
|
||||
class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
@@ -92,15 +102,38 @@
|
||||
</span>
|
||||
<GiftedPhotoDialog ref="photoDialog" />
|
||||
</div>
|
||||
</h2>
|
||||
<span v-else>
|
||||
<router-link
|
||||
:to="{ name: 'new-edit-account' }"
|
||||
class="block w-full text-center text-md bg-amber-200 text-blue-500 uppercase border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
|
||||
<div class="mt-4">
|
||||
<div class="flex justify-center">
|
||||
... and those without your image see this:
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<EntityIcon
|
||||
:entityId="activeDid"
|
||||
:iconSize="64"
|
||||
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||
@click="showLargeIdenticonId = activeDid"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="showLargeIdenticonId || showLargeIdenticonUrl"
|
||||
class="fixed z-[100] top-0 inset-x-0 w-full"
|
||||
>
|
||||
Set Your Name
|
||||
</router-link>
|
||||
</span>
|
||||
<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 class="text-slate-500 text-sm font-bold">ID</div>
|
||||
<div class="text-sm text-slate-500 flex justify-start items-center mb-1">
|
||||
@@ -580,6 +613,8 @@ import {
|
||||
ImageRateLimits,
|
||||
} from "@/libs/endorserServer";
|
||||
import { Buffer } from "buffer/";
|
||||
import EntityIcon from "@/components/EntityIcon.vue";
|
||||
import {Contact} from "@/db/tables/contacts";
|
||||
|
||||
interface IAccount {
|
||||
did: string;
|
||||
@@ -591,7 +626,7 @@ interface IAccount {
|
||||
const inputFileNameRef = ref<Blob>();
|
||||
|
||||
@Component({
|
||||
components: { GiftedPhotoDialog, QuickNav, TopMessage },
|
||||
components: {EntityIcon, GiftedPhotoDialog, QuickNav, TopMessage },
|
||||
})
|
||||
export default class AccountViewView extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
@@ -614,6 +649,8 @@ export default class AccountViewView extends Vue {
|
||||
profileImageUrl?: string;
|
||||
publicHex = "";
|
||||
publicBase64 = "";
|
||||
showLargeIdenticonId?: string;
|
||||
showLargeIdenticonUrl?: string;
|
||||
webPushServer = "";
|
||||
webPushServerInput = "";
|
||||
|
||||
@@ -1290,14 +1327,18 @@ export default class AccountViewView extends Vue {
|
||||
}
|
||||
|
||||
openPhotoDialog() {
|
||||
(this.$refs.photoDialog as GiftedPhotoDialog).open(async (imgUrl) => {
|
||||
(this.$refs.photoDialog as GiftedPhotoDialog).open(
|
||||
async (imgUrl) => {
|
||||
await db.open();
|
||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
profileImageUrl: imgUrl,
|
||||
});
|
||||
this.profileImageUrl = imgUrl;
|
||||
//console.log("Got image URL:", imgUrl);
|
||||
}, true);
|
||||
},
|
||||
true,
|
||||
"profile",
|
||||
);
|
||||
}
|
||||
|
||||
confirmDeleteImage() {
|
||||
@@ -1351,14 +1392,14 @@ export default class AccountViewView extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
this.profileImageUrl = "";
|
||||
this.profileImageUrl = undefined;
|
||||
} 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.profileImageUrl = "";
|
||||
this.profileImageUrl = undefined;
|
||||
|
||||
// it already doesn't exist so we won't say anything to the user
|
||||
} else {
|
||||
|
||||
@@ -415,12 +415,11 @@ import { accessToken } from "@/libs/crypto";
|
||||
import * as serverUtil from "@/libs/endorserServer";
|
||||
import * as libsUtil from "@/libs/util";
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import EntityIcon from "@/components/EntityIcon.vue";
|
||||
import { Account } from "@/db/tables/accounts";
|
||||
import { GiverInputInfo } from "@/libs/endorserServer";
|
||||
|
||||
@Component({
|
||||
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav },
|
||||
components: { GiftedDialog, OfferDialog, QuickNav },
|
||||
})
|
||||
export default class ClaimView extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<h2 class="text-base flex gap-4 items-center">
|
||||
<span class="grow font-semibold">
|
||||
<EntityIcon
|
||||
:entityId="contact.did"
|
||||
:contact="contact"
|
||||
:iconSize="32"
|
||||
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||
/>
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
:dotsOptions="{ type: 'square' }"
|
||||
class="flex justify-center"
|
||||
/>
|
||||
<span> Click QR to copy your contact URL to your clipboard. </span>
|
||||
<span> Click that QR to copy your contact URL to your clipboard. </span>
|
||||
<div>Not scanning? Show it in pieces.</div>
|
||||
</div>
|
||||
<div class="text-center" v-else>
|
||||
You have no identitifiers yet, so
|
||||
@@ -81,7 +82,7 @@ import { useClipboard } from "@vueuse/core";
|
||||
import { NotificationIface } from "@/constants/app";
|
||||
import { accountsDB, db } from "@/db/index";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { deriveAddress, nextDerivationPath, SimpleSigner } from "@/libs/crypto";
|
||||
import {deriveAddress, getContactPayloadFromJwtUrl, nextDerivationPath, SimpleSigner} from "@/libs/crypto";
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import { Account } from "@/db/tables/accounts";
|
||||
import {
|
||||
@@ -153,6 +154,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
(settings?.lastName ? ` ${settings.lastName}` : ""), // deprecated, pre v 0.1.3
|
||||
publicEncKey,
|
||||
nextPublicEncKeyHash: nextPublicEncKeyHashBase64,
|
||||
profileImageUrl: settings?.profileImageUrl,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -177,9 +179,24 @@ export default class ContactQRScanShow extends Vue {
|
||||
// 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) {
|
||||
localStorage.setItem("contactEndorserUrl", content[0].rawValue);
|
||||
const url = content[0]?.rawValue;
|
||||
if (url) {
|
||||
try {
|
||||
const fullData = getContactPayloadFromJwtUrl(url);
|
||||
console.log("fullData", fullData);
|
||||
localStorage.setItem("contactEndorserUrl", url);
|
||||
this.$router.push({ name: "contacts" });
|
||||
} catch (e) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "Invalid Contact QR Code",
|
||||
text: "The QR code isn't in the right format.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.$notify(
|
||||
{
|
||||
@@ -188,7 +205,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
title: "Invalid Contact QR Code",
|
||||
text: "No QR code detected with contact information.",
|
||||
},
|
||||
-1,
|
||||
5000,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -203,7 +220,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
title: "Invalid Scan",
|
||||
text: "The scan was invalid.",
|
||||
},
|
||||
-1,
|
||||
5000,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -94,17 +94,17 @@
|
||||
<div class="grow overflow-hidden">
|
||||
<h2 class="text-base font-semibold">
|
||||
<EntityIcon
|
||||
:entityId="contact.did"
|
||||
:contact="contact"
|
||||
:iconSize="24"
|
||||
class="inline-block align-text-bottom border border-slate-300 rounded"
|
||||
@click="showLargeIdenticon = contact.did"
|
||||
></EntityIcon>
|
||||
@click="showLargeIdenticon = contact"
|
||||
/>
|
||||
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
||||
<button
|
||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 rounded-md"
|
||||
@click="
|
||||
contactEdit = contact;
|
||||
contactNewName = contact.name;
|
||||
contactNewName = contact.name || '';
|
||||
"
|
||||
title="Edit"
|
||||
>
|
||||
@@ -246,10 +246,10 @@
|
||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||
>
|
||||
<EntityIcon
|
||||
:entityId="showLargeIdenticon"
|
||||
:contact="showLargeIdenticon"
|
||||
:iconSize="512"
|
||||
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||
@click="showLargeIdenticon = ''"
|
||||
@click="showLargeIdenticon = undefined"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -348,7 +348,7 @@ export default class ContactsView extends Vue {
|
||||
showGiveNumbers = false;
|
||||
showGiveTotals = true;
|
||||
showGiveConfirmed = true;
|
||||
showLargeIdenticon = "";
|
||||
showLargeIdenticon?: Contact;
|
||||
|
||||
AppString = AppString;
|
||||
libsUtil = libsUtil;
|
||||
@@ -672,6 +672,7 @@ export default class ContactsView extends Vue {
|
||||
did: payload.iss,
|
||||
name: payload.own.name,
|
||||
nextPubKeyHashB64: payload.own.nextPublicEncKeyHash,
|
||||
profileImageUrl: payload.own.profileImageUrl,
|
||||
publicKeyBase64: payload.own.publicEncKey,
|
||||
} as Contact);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,6 @@ import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
||||
import EntityIcon from "@/components/EntityIcon.vue";
|
||||
import ProjectIcon from "@/components/ProjectIcon.vue";
|
||||
import TopMessage from "@/components/TopMessage.vue";
|
||||
import { NotificationIface } from "@/constants/app";
|
||||
@@ -143,7 +142,6 @@ import { didInfo, PlanData } from "@/libs/endorserServer";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
EntityIcon,
|
||||
InfiniteScroll,
|
||||
ProjectIcon,
|
||||
QuickNav,
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
@click="openDialog(contact)"
|
||||
>
|
||||
<EntityIcon
|
||||
:entityId="contact.did"
|
||||
:contact="contact"
|
||||
:iconSize="64"
|
||||
class="mx-auto border border-slate-300 rounded-md mb-1"
|
||||
/>
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
@click="openGiftDialog(contact)"
|
||||
>
|
||||
<EntityIcon
|
||||
:entityId="contact.did"
|
||||
:contact="contact"
|
||||
:iconSize="64"
|
||||
class="mx-auto border border-slate-300 rounded-md mb-1"
|
||||
/>
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
:entityId="offer.recipientDid"
|
||||
:iconSize="48"
|
||||
class="inline-block align-middle border border-slate-300 rounded-md"
|
||||
></EntityIcon>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user