Merge branch 'deep_linking'

This commit is contained in:
Matthew Raymer
2025-03-17 03:01:53 +00:00
196 changed files with 15947 additions and 4598 deletions

View File

@@ -39,12 +39,15 @@
:to="{ name: 'contact-qr' }"
class="bg-slate-500 text-white px-1.5 py-1 rounded-md"
>
<fa icon="qrcode" class="fa-fw text-xl"></fa>
<font-awesome icon="qrcode" class="fa-fw text-xl"></font-awesome>
</router-link>
</span>
{{ givenName }}
<router-link :to="{ name: 'new-edit-account' }">
<fa icon="pen" class="text-xs text-blue-500 ml-2 mb-1"></fa>
<font-awesome
icon="pen"
class="text-xs text-blue-500 ml-2 mb-1"
></font-awesome>
</router-link>
</h2>
</div>
@@ -53,13 +56,13 @@
class="block w-full text-center text-md bg-amber-200 border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
>
<button
class="inline-block text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
@click="
() =>
(this.$refs.userNameDialog as UserNameDialog).open(
(name) => (this.givenName = name),
($refs.userNameDialog as UserNameDialog).open(
(name) => (givenName = name),
)
"
class="inline-block text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
>
Set Your Name
</button>
@@ -69,23 +72,23 @@
<span v-if="profileImageUrl" class="flex justify-between">
<EntityIcon
:icon-size="96"
:profileImageUrl="profileImageUrl"
:profile-image-url="profileImageUrl"
class="inline-block align-text-bottom border border-slate-300 rounded"
@click="showLargeIdenticonUrl = profileImageUrl"
/>
<fa
<font-awesome
icon="trash-can"
@click="confirmDeleteImage"
class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12"
@click="confirmDeleteImage"
/>
</span>
<div v-else class="text-center">
<div class @click="openImageDialog()">
<fa
<font-awesome
icon="image-portrait"
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-l"
/>
<fa
<font-awesome
icon="camera"
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-r"
/>
@@ -101,8 +104,8 @@
</div>
<div class="flex justify-center">
<EntityIcon
:entityId="activeDid"
:iconSize="64"
:entity-id="activeDid"
:icon-size="64"
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
@click="showLargeIdenticonId = activeDid"
/>
@@ -116,9 +119,9 @@
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
>
<EntityIcon
:entityId="showLargeIdenticonId"
:iconSize="512"
:profileImageUrl="showLargeIdenticonUrl"
:entity-id="showLargeIdenticonId"
:icon-size="512"
:profile-image-url="showLargeIdenticonUrl"
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
@click="
showLargeIdenticonId = undefined;
@@ -135,12 +138,12 @@
>
<code class="truncate">{{ activeDid }}</code>
<button
class="ml-2"
@click="
doCopyTwoSecRedo(activeDid, () => (showDidCopy = !showDidCopy))
"
class="ml-2"
>
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
<font-awesome icon="copy" class="text-slate-400 fa-fw"></font-awesome>
</button>
<span v-show="showDidCopy">Copied</span>
</div>
@@ -185,7 +188,7 @@
<!-- label -->
<div>
Reminder Notification
<fa
<font-awesome
icon="question-circle"
class="text-slate-400 fa-fw ml-2 cursor-pointer"
@click.stop="showReminderNotificationInfo"
@@ -197,7 +200,7 @@
@click="showReminderNotificationChoice()"
>
<!-- input -->
<input type="checkbox" v-model="notifyingReminder" class="sr-only" />
<input v-model="notifyingReminder" type="checkbox" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
@@ -214,7 +217,7 @@
<!-- label -->
<div>
New Activity Notification
<fa
<font-awesome
icon="question-circle"
class="text-slate-400 fa-fw ml-2 cursor-pointer"
@click.stop="showNewActivityNotificationInfo"
@@ -227,8 +230,8 @@
>
<!-- input -->
<input
type="checkbox"
v-model="notifyingNewActivity"
type="checkbox"
class="sr-only"
/>
<!-- line -->
@@ -268,12 +271,15 @@
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
>
<div v-if="loadingProfile" class="text-center mb-2">
<fa icon="spinner" class="fa-spin text-slate-400"></fa> Loading
profile...
<font-awesome
icon="spinner"
class="fa-spin text-slate-400"
></font-awesome>
Loading profile...
</div>
<div v-else class="flex items-center mb-2">
<span class="font-bold">Public Profile</span>
<fa
<font-awesome
icon="circle-info"
class="text-slate-400 fa-fw ml-2 cursor-pointer"
@click="showProfileInfo"
@@ -289,9 +295,9 @@
<div class="flex items-center mb-4" @click="toggleUserProfileLocation">
<input
v-model="includeUserProfileLocation"
type="checkbox"
class="mr-2"
v-model="includeUserProfileLocation"
/>
<label for="includeUserProfileLocation">Include Location</label>
</div>
@@ -327,17 +333,16 @@
<div v-if="!loadingProfile && !savingProfile">
<div class="flex justify-between items-center">
<button
@click="saveProfile"
class="mt-2 px-4 py-2 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
:disabled="loadingProfile || savingProfile"
:class="{
'opacity-50 cursor-not-allowed': loadingProfile || savingProfile,
}"
@click="saveProfile"
>
Save Profile
</button>
<button
@click="confirmDeleteProfile"
class="mt-2 px-4 py-2 bg-gradient-to-b from-red-400 to-red-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
:disabled="loadingProfile || savingProfile"
:class="{
@@ -346,6 +351,7 @@
savingProfile ||
(!userProfileDesc && !includeUserProfileLocation),
}"
@click="confirmDeleteProfile"
>
Delete Profile
</button>
@@ -363,7 +369,8 @@
<div class="mb-2 font-bold">Usage Limits</div>
<!-- show spinner if loading limits -->
<div v-if="loadingLimits" class="text-center">
Checking&hellip; <fa icon="spinner" class="fa-spin"></fa>
Checking&hellip;
<font-awesome icon="spinner" class="fa-spin"></font-awesome>
</div>
<div class="mb-4 text-center">
{{ limitsMessage }}
@@ -419,15 +426,15 @@
>
<div class="mb-2 font-bold">Data Export</div>
<router-link
:to="{ name: 'seed-backup' }"
v-if="activeDid"
:to="{ name: 'seed-backup' }"
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-2"
>
Backup Identifier Seed
</router-link>
<button
v-bind:class="computedStartDownloadLinkClassNames()"
:class="computedStartDownloadLinkClassNames()"
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="exportDatabase()"
>
@@ -437,7 +444,7 @@
</button>
<a
ref="downloadLink"
v-bind:class="computedDownloadLinkClassNames()"
:class="computedDownloadLinkClassNames()"
class="block w-full text-center text-md bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
>
If no download happened yet, click again here to download now.
@@ -454,7 +461,7 @@
</li>
<li class="list-disc list-outside ml-4">
On Android: Choose "Open" and then share
<fa icon="share-nodes" class="fa-fw" />
<font-awesome icon="share-nodes" class="fa-fw" />
to your prefered place.
</li>
</ul>
@@ -469,7 +476,7 @@
>
Advanced
</h3>
<div id="sectionAdvanced" v-if="showAdvanced || showGeneralAdvanced">
<div v-if="showAdvanced || showGeneralAdvanced" id="sectionAdvanced">
<p class="text-rose-600 mb-8">
Beware: the features here can be confusing and even change data in ways
you do not expect. But we support your freedom!
@@ -489,12 +496,15 @@
>
<code class="truncate">{{ publicBase64 }}</code>
<button
class="ml-2"
@click="
doCopyTwoSecRedo(publicBase64, () => (showB64Copy = !showB64Copy))
"
class="ml-2"
>
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
<font-awesome
icon="copy"
class="text-slate-400 fa-fw"
></font-awesome>
</button>
<span v-show="showB64Copy">Copied</span>
</div>
@@ -505,12 +515,15 @@
>
<code class="truncate">{{ publicHex }}</code>
<button
class="ml-2"
@click="
doCopyTwoSecRedo(publicHex, () => (showPubCopy = !showPubCopy))
"
class="ml-2"
>
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
<font-awesome
icon="copy"
class="text-slate-400 fa-fw"
></font-awesome>
</button>
<span v-show="showPubCopy">Copied</span>
</div>
@@ -522,15 +535,18 @@
>
<code class="truncate">{{ derivationPath }}</code>
<button
class="ml-2"
@click="
doCopyTwoSecRedo(
derivationPath,
() => (showDerCopy = !showDerCopy),
)
"
class="ml-2"
>
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
<font-awesome
icon="copy"
class="text-slate-400 fa-fw"
></font-awesome>
</button>
<span v-show="showDerCopy">Copied</span>
</div>
@@ -557,7 +573,7 @@
</h2>
<div class="ml-4 mt-2">
<input type="file" @change="uploadImportFile" class="ml-2" />
<input type="file" class="ml-2" @change="uploadImportFile" />
<transition
enter-active-class="transform ease-out duration-300 transition"
enter-from-class="translate-y-2 opacity-0 sm:translate-y-4"
@@ -604,8 +620,8 @@
<div class="relative ml-2">
<!-- input -->
<input
type="checkbox"
v-model="showContactGives"
type="checkbox"
name="showContactGives"
class="sr-only"
/>
@@ -622,16 +638,20 @@
<h2 class="text-slate-500 text-sm font-bold mt-4">Claim Server</h2>
<div class="px-4 py-4">
<input
v-model="apiServerInput"
type="text"
class="block w-full rounded border border-slate-400 px-4 py-2"
v-model="apiServerInput"
/>
<button
v-if="apiServerInput != apiServer"
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
@click="onClickSaveApiServer()"
>
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
<font-awesome
icon="floppy-disk"
class="fa-fw"
color="white"
></font-awesome>
</button>
<button
class="px-3 rounded bg-slate-200 border border-slate-400"
@@ -663,7 +683,7 @@
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input type="checkbox" v-model="warnIfProdServer" class="sr-only" />
<input v-model="warnIfProdServer" type="checkbox" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
@@ -683,7 +703,7 @@
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input type="checkbox" v-model="warnIfTestServer" class="sr-only" />
<input v-model="warnIfTestServer" type="checkbox" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
@@ -699,16 +719,20 @@
</h2>
<div class="px-3 py-4">
<input
v-model="webPushServerInput"
type="text"
class="block w-full rounded border border-slate-400 px-3 py-2"
v-model="webPushServerInput"
/>
<button
v-if="webPushServerInput != webPushServer"
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
@click="onClickSavePushServer()"
>
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
<font-awesome
icon="floppy-disk"
class="fa-fw"
color="white"
></font-awesome>
</button>
<button
class="px-3 rounded bg-slate-200 border border-slate-400"
@@ -729,7 +753,7 @@
Use Test 2
</button>
</div>
<span class="px-4 text-sm" v-if="!webPushServerInput">
<span v-if="!webPushServerInput" class="px-4 text-sm">
When that setting is blank, this app will use the default web push
server URL:
{{ DEFAULT_PUSH_SERVER }}
@@ -738,16 +762,20 @@
<h2 class="text-slate-500 text-sm font-bold mb-2">Partner Server URL</h2>
<div class="px-3 py-4">
<input
v-model="partnerApiServerInput"
type="text"
class="block w-full rounded border border-slate-400 px-3 py-2"
v-model="partnerApiServerInput"
/>
<button
v-if="partnerApiServerInput != partnerApiServer"
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
@click="onClickSavePartnerServer()"
>
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
<font-awesome
icon="floppy-disk"
class="fa-fw"
color="white"
></font-awesome>
</button>
<button
class="px-3 rounded bg-slate-200 border border-slate-400"
@@ -768,7 +796,7 @@
Use Local
</button>
</div>
<span class="px-4 text-sm" v-if="!partnerApiServerInput">
<span v-if="!partnerApiServerInput" class="px-4 text-sm">
When that setting is blank, this app will use the default partner server
URL:
{{ DEFAULT_PARTNER_API_SERVER }}
@@ -793,8 +821,8 @@
<div class="relative ml-2">
<!-- input -->
<input
type="checkbox"
v-model="hideRegisterPromptOnNewContact"
type="checkbox"
class="sr-only"
/>
<!-- line -->
@@ -818,7 +846,7 @@
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input type="checkbox" v-model="showShortcutBvc" class="sr-only" />
<input v-model="showShortcutBvc" type="checkbox" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full" />
<!-- dot -->
@@ -851,9 +879,9 @@
</span>
<div class="relative ml-2">
<input
v-model="passkeyExpirationMinutes"
type="number"
class="border border-slate-400 rounded px-2 py-2 text-center w-20"
v-model="passkeyExpirationMinutes"
@change="updatePasskeyExpiration"
/>
</div>
@@ -872,8 +900,8 @@
<div class="relative ml-2">
<!-- input -->
<input
type="checkbox"
v-model="showGeneralAdvanced"
type="checkbox"
class="sr-only"
/>
<!-- line -->
@@ -895,13 +923,13 @@ import { AxiosError } from "axios";
import { Buffer } from "buffer/";
import Dexie from "dexie";
import "dexie-export-import";
import { ImportProgress } from "dexie-export-import/dist/import";
import { ImportProgress } from "dexie-export-import";
import { LeafletMouseEvent } from "leaflet";
import * as R from "ramda";
import { IIdentifier } from "@veramo/core";
import { ref } from "vue";
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import { useClipboard } from "@vueuse/core";
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
@@ -947,6 +975,8 @@ import {
DIRECT_PUSH_TITLE,
retrieveAccountMetadata,
} from "../libs/util";
import { UserProfile } from "@/libs/partnerServer";
import { logger } from "../utils/logger";
const inputImportFileNameRef = ref<Blob>();
@@ -966,6 +996,8 @@ const inputImportFileNameRef = ref<Blob>();
})
export default class AccountViewView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
$route!: RouteLocationNormalizedLoaded;
$router!: Router;
AppConstants = AppString;
DEFAULT_PUSH_SERVER = DEFAULT_PUSH_SERVER;
@@ -1078,12 +1110,12 @@ export default class AccountViewView extends Vue {
}
} catch (error) {
// this can happen when running automated tests in dev mode because notifications don't work
console.error(
logger.error(
"Telling user to clear cache at page create because:",
error,
);
// this sometimes gives different information on the error
console.error(
logger.error(
"To repeat with concatenated error: telling user to clear cache at page create because: " +
error,
);
@@ -1490,7 +1522,7 @@ export default class AccountViewView extends Vue {
* @param {Error} error - The error object.
*/
private handleExportError(error: unknown) {
console.error("Export Error:", error);
logger.error("Export Error:", error);
this.$notify(
{
group: "alert",
@@ -1560,7 +1592,7 @@ export default class AccountViewView extends Vue {
query: { contacts: JSON.stringify(contactRows) },
});
} catch (error) {
console.error("Error checking contact imports:", error);
logger.error("Error checking contact imports:", error);
this.$notify(
{
group: "alert",
@@ -1576,7 +1608,7 @@ export default class AccountViewView extends Vue {
}
private progressCallback(progress: ImportProgress) {
console.log(
logger.log(
`Import progress: ${progress.completedRows} of ${progress.totalRows} rows completed.`,
);
if (progress.done) {
@@ -1628,7 +1660,7 @@ export default class AccountViewView extends Vue {
await updateAccountSettings(did, { isRegistered: true });
this.isRegistered = true;
} catch (err) {
console.error("Got an error updating settings:", err);
logger.error("Got an error updating settings:", err);
this.$notify(
{
group: "alert",
@@ -1667,7 +1699,7 @@ export default class AccountViewView extends Vue {
if (error instanceof AxiosError) {
if (error.status == 400 || error.status == 404) {
// no worries: they probably just aren't registered and don't have any limits
console.log(
logger.log(
"Got 400 or 404 response retrieving limits which probably means they're not registered:",
error,
);
@@ -1676,11 +1708,11 @@ export default class AccountViewView extends Vue {
const data = error.response?.data as ErrorResponse;
this.limitsMessage =
(data?.error?.message as string) || "Bad server response.";
console.error("Got bad response retrieving limits:", error);
logger.error("Got bad response retrieving limits:", error);
}
} else {
this.limitsMessage = "Got an error retrieving limits.";
console.error("Got some error retrieving limits:", error);
logger.error("Got some error retrieving limits:", error);
}
}
@@ -1757,7 +1789,7 @@ export default class AccountViewView extends Vue {
window.location.hostname === "localhost" &&
!DEFAULT_IMAGE_API_SERVER.includes("localhost")
) {
console.log(
logger.log(
"Using shared image API server, so only users on that server can play with images.",
);
}
@@ -1771,7 +1803,7 @@ export default class AccountViewView extends Vue {
// don't bother with a notification
// (either they'll simply continue or they're canceling and going back)
} else {
console.error("Non-success deleting image:", response);
logger.error("Non-success deleting image:", response);
this.$notify(
{
group: "alert",
@@ -1791,10 +1823,10 @@ export default class AccountViewView extends Vue {
this.profileImageUrl = undefined;
} catch (error) {
console.error("Error deleting image:", error);
logger.error("Error deleting image:", error);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((error as any).response.status === 404) {
console.error("The image was already deleted:", error);
logger.error("The image was already deleted:", error);
await updateAccountSettings(this.activeDid, {
profileImageUrl: undefined,