3 changed files with 240 additions and 11 deletions
			
			
		| @ -0,0 +1,179 @@ | |||||
|  | <template> | ||||
|  |   <QuickNav selected="Contacts"></QuickNav> | ||||
|  |   <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> | ||||
|  |     <!-- Heading --> | ||||
|  |     <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> | ||||
|  |       Contact Import | ||||
|  |     </h1> | ||||
|  | 
 | ||||
|  |     <div v-if="sameCount > 0"> | ||||
|  |       {{ sameCount }} contact{{ sameCount == 1 ? "" : "s" }} are the same as | ||||
|  |       existing contacts. | ||||
|  |     </div> | ||||
|  | 
 | ||||
|  |     <!-- Results List --> | ||||
|  |     <ul v-if="contactsImporting.length > 0" class="border-t border-slate-300"> | ||||
|  |       <li | ||||
|  |         v-for="(contact, index) in contactsImporting" | ||||
|  |         :key="contact.did" | ||||
|  |       > | ||||
|  |         <div | ||||
|  |           v-if=" | ||||
|  |             !contactsExisting[contact.did] || | ||||
|  |             !R.isEmpty(contactDifferences[contact.did]) | ||||
|  |           " | ||||
|  |           class="grow overflow-hidden border-b border-slate-300 pt-2.5 pb-4" | ||||
|  |         > | ||||
|  |           <h2 class="text-base font-semibold"> | ||||
|  |             <input type="checkbox" v-model="contactsSelected[index]" /> | ||||
|  |             {{ contact.name || AppString.NO_CONTACT_NAME }} | ||||
|  |             - | ||||
|  |             <span v-if="contactsExisting[contact.did]" class="text-orange-500" | ||||
|  |               >Existing</span | ||||
|  |             > | ||||
|  |             <span v-else class="text-green-500">New</span> | ||||
|  |           </h2> | ||||
|  |           <div class="text-sm truncate"> | ||||
|  |             {{ contact.did }} | ||||
|  |           </div> | ||||
|  |           <div v-if="contactDifferences[contact.did]"> | ||||
|  |             <div> | ||||
|  |               <div class="grid grid-cols-3 gap-2"> | ||||
|  |                 <div class="font-bold">Field</div> | ||||
|  |                 <div class="font-bold">Old Value</div> | ||||
|  |                 <div class="font-bold">New Value</div> | ||||
|  |               </div> | ||||
|  |               <div | ||||
|  |                 v-for="(value, contactField) in contactDifferences[contact.did]" | ||||
|  |                 :key="contactField" | ||||
|  |                 class="grid grid-cols-3 border" | ||||
|  |               > | ||||
|  |                 <div class="border p-1">{{ contactField }}</div> | ||||
|  |                 <div class="border p-1">{{ value.old }}</div> | ||||
|  |                 <div class="border p-1">{{ value.new }}</div> | ||||
|  |               </div> | ||||
|  |             </div> | ||||
|  |           </div> | ||||
|  |         </div> | ||||
|  |       </li> | ||||
|  |       <fa icon="spinner" v-if="importing" class="animate-spin" /> | ||||
|  |       <button | ||||
|  |         v-else | ||||
|  |         class="text-sm 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-1.5 rounded-l-md" | ||||
|  |         @click="importContacts" | ||||
|  |       > | ||||
|  |         Import Selected Contacts | ||||
|  |       </button> | ||||
|  |     </ul> | ||||
|  |     <p v-else>There are no contacts to import.</p> | ||||
|  |   </section> | ||||
|  | </template> | ||||
|  | 
 | ||||
|  | <script lang="ts"> | ||||
|  | import * as R from "ramda"; | ||||
|  | import { Component, Vue } from "vue-facing-decorator"; | ||||
|  | import { Router } from "vue-router"; | ||||
|  | 
 | ||||
|  | import { AppString, NotificationIface } from "@/constants/app"; | ||||
|  | import { db } from "@/db/index"; | ||||
|  | import { Contact } from "@/db/tables/contacts"; | ||||
|  | import * as libsUtil from "@/libs/util"; | ||||
|  | import QuickNav from "@/components/QuickNav.vue"; | ||||
|  | import EntityIcon from "@/components/EntityIcon.vue"; | ||||
|  | import GiftedDialog from "@/components/GiftedDialog.vue"; | ||||
|  | import OfferDialog from "@/components/OfferDialog.vue"; | ||||
|  | 
 | ||||
|  | @Component({ | ||||
|  |   components: { GiftedDialog, EntityIcon, OfferDialog, QuickNav }, | ||||
|  | }) | ||||
|  | export default class ContactImportView extends Vue { | ||||
|  |   $notify!: (notification: NotificationIface, timeout?: number) => void; | ||||
|  | 
 | ||||
|  |   AppString = AppString; | ||||
|  |   libsUtil = libsUtil; | ||||
|  |   R = R; | ||||
|  | 
 | ||||
|  |   contactsExisting: Record<string, Contact> = {}; // user's contacts already in the system, keyed by DID | ||||
|  |   contactsImporting: Array<Contact> = []; // contacts from the import | ||||
|  |   contactsSelected: Array<boolean> = []; // whether each contact in contactsImporting is selected | ||||
|  |   contactDifferences: Record< | ||||
|  |     string, | ||||
|  |     Record<string, { new: string; old: string }> | ||||
|  |   > = {}; // for existing contacts, it shows the difference between imported and existing contacts for each key | ||||
|  |   importing = false; | ||||
|  |   sameCount = 0; | ||||
|  | 
 | ||||
|  |   async created() { | ||||
|  |     // Retrieve the imported contacts from the query parameter | ||||
|  |     const importedContacts = | ||||
|  |       ((this.$route as Router).query["contacts"] as string) || "[]"; | ||||
|  |     this.contactsImporting = JSON.parse(importedContacts); | ||||
|  |     this.contactsSelected = new Array(this.contactsImporting.length).fill( | ||||
|  |       false, | ||||
|  |     ); | ||||
|  | 
 | ||||
|  |     await db.open(); | ||||
|  |     const baseContacts = await db.contacts.toArray(); | ||||
|  |     // set the existing contacts, keyed by DID, if they exist in contactsImporting | ||||
|  |     for (let i = 0; i < this.contactsImporting.length; i++) { | ||||
|  |       const contactIn = this.contactsImporting[i]; | ||||
|  |       const existingContact = baseContacts.find( | ||||
|  |         (contact) => contact.did === contactIn.did, | ||||
|  |       ); | ||||
|  |       if (existingContact) { | ||||
|  |         this.contactsExisting[contactIn.did] = existingContact; | ||||
|  | 
 | ||||
|  |         const differences: Record<string, { new: string; old: string }> = {}; | ||||
|  |         Object.keys(contactIn).forEach((key) => { | ||||
|  |           if (contactIn[key] !== existingContact[key]) { | ||||
|  |             differences[key] = { | ||||
|  |               old: existingContact[key], | ||||
|  |               new: contactIn[key], | ||||
|  |             }; | ||||
|  |           } | ||||
|  |         }); | ||||
|  |         this.contactDifferences[contactIn.did] = differences; | ||||
|  |         if (R.isEmpty(differences)) { | ||||
|  |           this.sameCount++; | ||||
|  |         } | ||||
|  |       } else { | ||||
|  |         // automatically import new data | ||||
|  |         this.contactsSelected[i] = true; | ||||
|  |       } | ||||
|  |     } | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   async importContacts() { | ||||
|  |     this.importing = true; | ||||
|  |     let importedCount = 0, | ||||
|  |       updatedCount = 0; | ||||
|  |     for (let i = 0; i < this.contactsImporting.length; i++) { | ||||
|  |       if (this.contactsSelected[i]) { | ||||
|  |         const contact = R.clone(this.contactsImporting[i]); // cloning to avoid the problem with a Proxy object | ||||
|  |         const existingContact = this.contactsExisting[contact.did]; | ||||
|  |         if (existingContact) { | ||||
|  |           await db.contacts.update(contact.did, contact); | ||||
|  |           updatedCount++; | ||||
|  |         } else { | ||||
|  |           await db.contacts.add(contact); | ||||
|  |           importedCount++; | ||||
|  |         } | ||||
|  |       } | ||||
|  |     } | ||||
|  |     this.importing = false; | ||||
|  | 
 | ||||
|  |     this.$notify( | ||||
|  |       { | ||||
|  |         group: "alert", | ||||
|  |         type: "success", | ||||
|  |         title: "Import Success", | ||||
|  |         text: | ||||
|  |           `${importedCount} contact${importedCount == 1 ? "" : "s"} imported.` + | ||||
|  |           (updatedCount ? ` ${updatedCount} updated.` : ""), | ||||
|  |       }, | ||||
|  |       3000, | ||||
|  |     ); | ||||
|  |     (this.$router as Router).push({ name: "contacts" }); | ||||
|  |   } | ||||
|  | } | ||||
|  | </script> | ||||
					Loading…
					
					
				
		Reference in new issue