Compare commits
1 Commits
accountvie
...
gifted-dia
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4eb6f2d1d |
@@ -10,7 +10,7 @@ messages * - Conditional UI based on platform capabilities * * @component *
|
||||
|
||||
<template>
|
||||
<div id="sectionDataExport" :class="containerClasses">
|
||||
<div :class="titleClasses">Data Management</div>
|
||||
<div :class="titleClasses">Data Export</div>
|
||||
<router-link
|
||||
v-if="activeDid"
|
||||
:to="{ name: 'seed-backup' }"
|
||||
@@ -30,7 +30,7 @@ messages * - Conditional UI based on platform capabilities * * @component *
|
||||
:class="exportButtonClasses"
|
||||
@click="exportDatabase()"
|
||||
>
|
||||
{{ isExporting ? "Exporting..." : "Export Contacts" }}
|
||||
{{ isExporting ? "Exporting..." : "Download Contacts" }}
|
||||
</button>
|
||||
|
||||
<div
|
||||
@@ -55,54 +55,11 @@ messages * - Conditional UI based on platform capabilities * * @component *
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Import Contacts -->
|
||||
<div id="sectionImportContactsSettings" class="mt-4">
|
||||
<h2 class="text-slate-500 text-sm font-bold">Import Contacts</h2>
|
||||
|
||||
<div class="mt-2">
|
||||
<input
|
||||
type="file"
|
||||
class="w-full bg-white rounded-md pe-2 file:border-0 file:bg-gradient-to-b file:from-blue-400 file:to-blue-700 file:shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] file:text-white file:px-3 file:py-2 file:me-2 file:rounded-s-md"
|
||||
@change="uploadImportFile"
|
||||
/>
|
||||
<transition
|
||||
enter-active-class="transform ease-out duration-300 transition"
|
||||
enter-from-class="translate-y-2 opacity-0 sm:translate-y-4"
|
||||
enter-to-class="translate-y-0 opacity-100 sm:translate-y-0"
|
||||
leave-active-class="transition ease-in duration-500"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="showContactImport()" class="mt-2">
|
||||
<!-- Bulk import has an error
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
class="block 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-6"
|
||||
@click="confirmSubmitImportFile()"
|
||||
>
|
||||
Overwrite Settings & Contacts
|
||||
<br />
|
||||
(which doesn't include Identifier Data)
|
||||
</button>
|
||||
</div>
|
||||
-->
|
||||
<button
|
||||
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="checkContactImports()"
|
||||
>
|
||||
Import Contacts
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||
import { Router } from "vue-router";
|
||||
import * as R from "ramda";
|
||||
|
||||
import { AppString, NotificationIface } from "../constants/app";
|
||||
@@ -110,10 +67,8 @@ import { Contact } from "../db/tables/contacts";
|
||||
|
||||
import { logger } from "../utils/logger";
|
||||
import { contactsToExportJson } from "../libs/util";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { createNotifyHelpers } from "@/utils/notify";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
|
||||
import { ImportContent } from "@/interfaces/accountView";
|
||||
|
||||
/**
|
||||
* @vue-component
|
||||
@@ -136,12 +91,6 @@ export default class DataExportSection extends Vue {
|
||||
*/
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
|
||||
/**
|
||||
* Router instance injected by Vue
|
||||
* Used for navigation
|
||||
*/
|
||||
$router!: Router;
|
||||
|
||||
/**
|
||||
* Active DID (Decentralized Identifier) of the user
|
||||
* Controls visibility of seed backup option
|
||||
@@ -161,12 +110,6 @@ export default class DataExportSection extends Vue {
|
||||
*/
|
||||
showRedNotificationDot = false;
|
||||
|
||||
/**
|
||||
* Reference to the selected import file
|
||||
* Used to store the file selected by the user for import
|
||||
*/
|
||||
private inputImportFileName: Blob | undefined;
|
||||
|
||||
/**
|
||||
* Notification helper for consistent notification patterns
|
||||
* Created as a getter to ensure $notify is available when called
|
||||
@@ -305,58 +248,5 @@ export default class DataExportSection extends Vue {
|
||||
this.showRedNotificationDot = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles file selection for contact import
|
||||
* Stores the selected file for later processing
|
||||
*/
|
||||
async uploadImportFile(event: Event): Promise<void> {
|
||||
this.inputImportFileName = (event.target as HTMLInputElement).files?.[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a contact import file has been selected
|
||||
* Used to conditionally show the import button
|
||||
*/
|
||||
showContactImport(): boolean {
|
||||
return !!this.inputImportFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the selected import file and navigates to the contact import view
|
||||
* Parses the JSON file and extracts contact data for import
|
||||
*/
|
||||
async checkContactImports(): Promise<void> {
|
||||
if (!this.inputImportFileName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const fileContent: string = (event.target?.result as string) || "{}";
|
||||
try {
|
||||
const contents: ImportContent = JSON.parse(fileContent);
|
||||
const contactTableRows: Array<Contact> = (
|
||||
contents.data?.data as [{ tableName: string; rows: Array<Contact> }]
|
||||
)?.find((table) => table.tableName === "contacts")
|
||||
?.rows as Array<Contact>;
|
||||
const contactRows = contactTableRows.map(
|
||||
// @ts-expect-error for omitting this field that is found in the Dexie format
|
||||
(contact) => R.omit(["$types"], contact) as Contact,
|
||||
);
|
||||
this.$router.push({
|
||||
name: "contact-import",
|
||||
query: { contacts: JSON.stringify(contactRows) },
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error checking contact imports:", error);
|
||||
this.notify.error(
|
||||
ACCOUNT_VIEW_CONSTANTS.ERRORS.IMPORT_ERROR,
|
||||
TIMEOUTS.STANDARD,
|
||||
);
|
||||
}
|
||||
};
|
||||
reader.readAsText(this.inputImportFileName);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -483,10 +483,13 @@ export default class GiftedDialog extends Vue {
|
||||
image: project.image,
|
||||
handleId: project.handleId,
|
||||
};
|
||||
this.receiver = {
|
||||
did: this.activeDid,
|
||||
name: "You",
|
||||
};
|
||||
// Only set receiver to "You" if no receiver has been selected yet
|
||||
if (!this.receiver || !this.receiver.did) {
|
||||
this.receiver = {
|
||||
did: this.activeDid,
|
||||
name: "You",
|
||||
};
|
||||
}
|
||||
this.firstStep = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,12 +57,7 @@ export interface OfferToPlanSummaryRecord extends OfferSummaryRecord {
|
||||
planName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary record
|
||||
* The VC is not currently part of this record.
|
||||
*
|
||||
* If you change this, you may want to update NewActivityView.vue to handle differences correctly.
|
||||
*/
|
||||
// a summary record; the VC is not currently part of this record
|
||||
export interface PlanSummaryRecord {
|
||||
agentDid?: string;
|
||||
description: string;
|
||||
@@ -81,9 +76,7 @@ export interface PlanSummaryRecord {
|
||||
|
||||
export interface PlanSummaryAndPreviousClaim {
|
||||
plan: PlanSummaryRecord;
|
||||
// This can be undefined, eg. if a project is starred after the stored last-seen-change-jwt ID.
|
||||
// The endorser-ch test code shows some cases.
|
||||
wrappedClaimBefore?: GenericCredWrapper<PlanActionClaim>;
|
||||
wrappedClaimBefore: GenericCredWrapper<PlanActionClaim>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -375,6 +375,45 @@
|
||||
Switch Identifier
|
||||
</router-link>
|
||||
|
||||
<div id="sectionImportContactsSettings" class="mt-4">
|
||||
<h2 class="text-slate-500 text-sm font-bold">Import Contacts</h2>
|
||||
|
||||
<div class="ml-4 mt-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"
|
||||
enter-to-class="translate-y-0 opacity-100 sm:translate-y-0"
|
||||
leave-active-class="transition ease-in duration-500"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="showContactImport()" class="mt-4">
|
||||
<!-- Bulk import has an error
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
class="block 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-6"
|
||||
@click="confirmSubmitImportFile()"
|
||||
>
|
||||
Overwrite Settings & Contacts
|
||||
<br />
|
||||
(which doesn't include Identifier Data)
|
||||
</button>
|
||||
</div>
|
||||
-->
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
class="block 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-6"
|
||||
@click="checkContactImports()"
|
||||
>
|
||||
Import Contacts
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label
|
||||
for="toggleShowAmounts"
|
||||
class="flex items-center justify-between cursor-pointer my-4"
|
||||
@@ -731,7 +770,9 @@ import "dexie-export-import";
|
||||
import { ImportProgress } from "dexie-export-import";
|
||||
import { LeafletMouseEvent } from "leaflet";
|
||||
import * as L from "leaflet";
|
||||
import * as R from "ramda";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
import { ref } from "vue";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||
import { copyToClipboard } from "../services/ClipboardService";
|
||||
@@ -758,6 +799,7 @@ import {
|
||||
NotificationIface,
|
||||
PASSKEYS_ENABLED,
|
||||
} from "../constants/app";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import {
|
||||
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
|
||||
BoundingBox,
|
||||
@@ -781,7 +823,11 @@ import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
|
||||
import { showSeedPhraseReminder } from "@/utils/seedPhraseReminder";
|
||||
import { AccountSettings, isApiError } from "@/interfaces/accountView";
|
||||
import {
|
||||
AccountSettings,
|
||||
isApiError,
|
||||
ImportContent,
|
||||
} from "@/interfaces/accountView";
|
||||
// Profile data interface (inlined from ProfileService)
|
||||
interface ProfileData {
|
||||
description: string;
|
||||
@@ -790,6 +836,8 @@ interface ProfileData {
|
||||
includeLocation: boolean;
|
||||
}
|
||||
|
||||
const inputImportFileNameRef = ref<Blob>();
|
||||
|
||||
interface UserNameDialogRef {
|
||||
open: (cb: (name?: string) => void) => void;
|
||||
}
|
||||
@@ -1321,6 +1369,65 @@ export default class AccountViewView extends Vue {
|
||||
);
|
||||
}
|
||||
|
||||
async uploadImportFile(event: Event): Promise<void> {
|
||||
inputImportFileNameRef.value = (
|
||||
event.target as HTMLInputElement
|
||||
).files?.[0];
|
||||
}
|
||||
|
||||
showContactImport(): boolean {
|
||||
return !!inputImportFileNameRef.value;
|
||||
}
|
||||
|
||||
confirmSubmitImportFile(): void {
|
||||
if (inputImportFileNameRef.value != null) {
|
||||
this.notify.confirm(
|
||||
ACCOUNT_VIEW_CONSTANTS.WARNINGS.IMPORT_REPLACE_WARNING,
|
||||
this.submitImportFile,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously imports the database from a downloadable JSON file.
|
||||
*
|
||||
* @throws Will notify the user if there is an export error.
|
||||
*/
|
||||
async submitImportFile(): Promise<void> {
|
||||
if (inputImportFileNameRef.value != null) {
|
||||
// TODO: implement this for SQLite
|
||||
}
|
||||
}
|
||||
|
||||
async checkContactImports(): Promise<void> {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const fileContent: string = (event.target?.result as string) || "{}";
|
||||
try {
|
||||
const contents: ImportContent = JSON.parse(fileContent);
|
||||
const contactTableRows: Array<Contact> = (
|
||||
contents.data?.data as [{ tableName: string; rows: Array<Contact> }]
|
||||
)?.find((table) => table.tableName === "contacts")
|
||||
?.rows as Array<Contact>;
|
||||
const contactRows = contactTableRows.map(
|
||||
// @ts-expect-error for omitting this field that is found in the Dexie format
|
||||
(contact) => R.omit(["$types"], contact) as Contact,
|
||||
);
|
||||
(this.$router as Router).push({
|
||||
name: "contact-import",
|
||||
query: { contacts: JSON.stringify(contactRows) },
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error checking contact imports:", error);
|
||||
this.notify.error(
|
||||
ACCOUNT_VIEW_CONSTANTS.ERRORS.IMPORT_ERROR,
|
||||
TIMEOUTS.STANDARD,
|
||||
);
|
||||
}
|
||||
};
|
||||
reader.readAsText(inputImportFileNameRef.value as Blob);
|
||||
}
|
||||
|
||||
private progressCallback(progress: ImportProgress): boolean {
|
||||
logger.log(
|
||||
`Import progress: ${progress.completedRows} of ${progress.totalRows} rows completed.`,
|
||||
|
||||
@@ -898,13 +898,7 @@ export default class HomeView extends Vue {
|
||||
this.starredPlanHandleIds,
|
||||
this.lastAckedStarredPlanChangesJwtId,
|
||||
);
|
||||
// filter out any data elements where there is no wrappedClaimBefore
|
||||
const filteredNewStarredProjectChanges =
|
||||
starredProjectChanges.data.filter(
|
||||
(change) => change.wrappedClaimBefore !== undefined,
|
||||
);
|
||||
this.numNewStarredProjectChanges =
|
||||
filteredNewStarredProjectChanges.length;
|
||||
this.numNewStarredProjectChanges = starredProjectChanges.data.length;
|
||||
this.newStarredProjectChangesHitLimit = starredProjectChanges.hitLimit;
|
||||
} catch (error) {
|
||||
// Don't show errors for starred project changes as it's a secondary feature
|
||||
|
||||
@@ -284,10 +284,7 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
The changes are not important, like it was saved by accident or
|
||||
you've seen it all before.
|
||||
</div>
|
||||
<div v-else>The changes did not affect essential project data.</div>
|
||||
<!-- New line that appears on hover -->
|
||||
<div
|
||||
class="absolute left-0 w-full text-left text-gray-500 text-sm hidden group-hover:flex cursor-pointer items-center"
|
||||
@@ -592,13 +589,13 @@ export default class NewActivityView extends Vue {
|
||||
|
||||
for (const planChange of planChanges) {
|
||||
const currentPlan: PlanSummaryRecord = planChange.plan;
|
||||
const wrappedClaim: GenericCredWrapper<PlanActionClaim> | undefined =
|
||||
const wrappedClaim: GenericCredWrapper<PlanActionClaim> =
|
||||
planChange.wrappedClaimBefore;
|
||||
|
||||
// Extract the actual claim from the wrapped claim
|
||||
let previousClaim: PlanActionClaim | undefined;
|
||||
let previousClaim: PlanActionClaim;
|
||||
|
||||
const embeddedClaim: PlanActionClaim | undefined = wrappedClaim?.claim;
|
||||
const embeddedClaim: PlanActionClaim = wrappedClaim.claim;
|
||||
if (
|
||||
embeddedClaim &&
|
||||
typeof embeddedClaim === "object" &&
|
||||
@@ -612,9 +609,7 @@ export default class NewActivityView extends Vue {
|
||||
previousClaim = embeddedClaim;
|
||||
}
|
||||
|
||||
if (!previousClaim) {
|
||||
// Can happen when a project is starred after the stored last-seen-change-jwt ID
|
||||
// so we'll just leave the message saying there are no important differences.
|
||||
if (!previousClaim || !currentPlan.handleId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,9 +57,6 @@
|
||||
<button :class="sqlLinkClasses" @click="setAccountsQuery">
|
||||
Accounts
|
||||
</button>
|
||||
<button :class="sqlLinkClasses" @click="setActiveIdentityQuery">
|
||||
Active DID
|
||||
</button>
|
||||
<button :class="sqlLinkClasses" @click="setContactsQuery">
|
||||
Contacts
|
||||
</button>
|
||||
@@ -528,11 +525,6 @@ export default class Help extends Vue {
|
||||
this.executeSql();
|
||||
}
|
||||
|
||||
setActiveIdentityQuery() {
|
||||
this.sqlQuery = "SELECT * FROM active_identity;";
|
||||
this.executeSql();
|
||||
}
|
||||
|
||||
setContactsQuery() {
|
||||
this.sqlQuery = "SELECT * FROM contacts;";
|
||||
this.executeSql();
|
||||
|
||||
Reference in New Issue
Block a user