|
|
|
@ -1,4 +1,5 @@ |
|
|
|
<template> |
|
|
|
<div> |
|
|
|
<div class="space-y-4"> |
|
|
|
<!-- Loading State --> |
|
|
|
<div |
|
|
|
@ -48,7 +49,7 @@ |
|
|
|
<button |
|
|
|
class="text-sm 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-3 py-1.5 rounded-md" |
|
|
|
title="Refresh members list now" |
|
|
|
@click="manualRefresh" |
|
|
|
@click="refreshData(false)" |
|
|
|
> |
|
|
|
<font-awesome icon="rotate" :class="{ 'fa-spin': isLoading }" /> |
|
|
|
Refresh |
|
|
|
@ -163,7 +164,7 @@ |
|
|
|
<button |
|
|
|
class="text-sm 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-3 py-1.5 rounded-md" |
|
|
|
title="Refresh members list now" |
|
|
|
@click="manualRefresh" |
|
|
|
@click="refreshData(false)" |
|
|
|
> |
|
|
|
<font-awesome icon="rotate" :class="{ 'fa-spin': isLoading }" /> |
|
|
|
Refresh |
|
|
|
@ -177,43 +178,44 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- Admit Pending Members Dialog Component --> |
|
|
|
<!-- This Admit component is for the organizer to admit pending members to the meeting --> |
|
|
|
<AdmitPendingMembersDialog |
|
|
|
ref="admitPendingMembersDialog" |
|
|
|
:active-did="activeDid" |
|
|
|
:api-server="apiServer" |
|
|
|
:pending-members-data="pendingMembersData" |
|
|
|
@close="closeAdmitPendingDialog" |
|
|
|
@close="closeMemberSelectionDialogCallback" |
|
|
|
/> |
|
|
|
<!-- This Bulk Visibility component is for non-organizer members to add other members to their contacts and set their visibility --> |
|
|
|
<SetBulkVisibilityDialog |
|
|
|
:visible="visibleBulkVisibilityDialog" |
|
|
|
ref="setBulkVisibilityDialog" |
|
|
|
:active-did="activeDid" |
|
|
|
:api-server="apiServer" |
|
|
|
:members-data="pendingMembersData" |
|
|
|
@close="closeSetBulkVisibilityDialog" |
|
|
|
@close="closeMemberSelectionDialogCallback" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script lang="ts"> |
|
|
|
import { Component, Vue, Prop, Emit } from "vue-facing-decorator"; |
|
|
|
|
|
|
|
import { NotificationIface } from "@/constants/app"; |
|
|
|
import { |
|
|
|
NOTIFY_ADD_CONTACT_FIRST, |
|
|
|
NOTIFY_CONTINUE_WITHOUT_ADDING, |
|
|
|
} from "@/constants/notifications"; |
|
|
|
import { SOMEONE_UNNAMED } from "@/constants/entities"; |
|
|
|
import { |
|
|
|
errorStringForLog, |
|
|
|
getHeaders, |
|
|
|
register, |
|
|
|
serverMessageForUser, |
|
|
|
} from "../libs/endorserServer"; |
|
|
|
import { decryptMessage } from "../libs/crypto"; |
|
|
|
import { Contact } from "../db/tables/contacts"; |
|
|
|
import * as libsUtil from "../libs/util"; |
|
|
|
import { NotificationIface } from "../constants/app"; |
|
|
|
} from "@/libs/endorserServer"; |
|
|
|
import { decryptMessage } from "@/libs/crypto"; |
|
|
|
import { Contact } from "@/db/tables/contacts"; |
|
|
|
import { MemberData } from "@/interfaces"; |
|
|
|
import * as libsUtil from "@/libs/util"; |
|
|
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; |
|
|
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify"; |
|
|
|
import { |
|
|
|
NOTIFY_ADD_CONTACT_FIRST, |
|
|
|
NOTIFY_CONTINUE_WITHOUT_ADDING, |
|
|
|
} from "@/constants/notifications"; |
|
|
|
import { SOMEONE_UNNAMED } from "@/constants/entities"; |
|
|
|
import AdmitPendingMembersDialog from "./AdmitPendingMembersDialog.vue"; |
|
|
|
import SetBulkVisibilityDialog from "./SetBulkVisibilityDialog.vue"; |
|
|
|
|
|
|
|
@ -263,16 +265,6 @@ export default class MembersList extends Vue { |
|
|
|
activeDid = ""; |
|
|
|
apiServer = ""; |
|
|
|
|
|
|
|
// Admit Pending Members Dialog state |
|
|
|
pendingMembersData: Array<{ |
|
|
|
did: string; |
|
|
|
name: string; |
|
|
|
isContact: boolean; |
|
|
|
member: { memberId: string }; |
|
|
|
}> = []; |
|
|
|
|
|
|
|
visibleBulkVisibilityDialog = false; |
|
|
|
|
|
|
|
// Auto-refresh functionality |
|
|
|
countdownTimer = 10; |
|
|
|
autoRefreshInterval: NodeJS.Timeout | null = null; |
|
|
|
@ -302,16 +294,6 @@ export default class MembersList extends Vue { |
|
|
|
this.refreshData(); |
|
|
|
} |
|
|
|
|
|
|
|
async refreshData(showPendingEvenIfAllWereIgnored = false) { |
|
|
|
// Force refresh both contacts and members |
|
|
|
this.contacts = await this.$getAllContacts(); |
|
|
|
|
|
|
|
await this.fetchMembers(); |
|
|
|
|
|
|
|
// Check if we should show the admit pending members dialog first |
|
|
|
this.checkAndShowAdmitPendingDialog(showPendingEvenIfAllWereIgnored); |
|
|
|
} |
|
|
|
|
|
|
|
async fetchMembers() { |
|
|
|
try { |
|
|
|
this.isLoading = true; |
|
|
|
@ -408,8 +390,22 @@ export default class MembersList extends Vue { |
|
|
|
); |
|
|
|
} |
|
|
|
} else { |
|
|
|
// non-organizers only get visible members from server |
|
|
|
members = this.decryptedMembers; |
|
|
|
// non-organizers only get visible members from server, plus themselves |
|
|
|
|
|
|
|
// this is a stub for this user just in case they are waiting to get in |
|
|
|
// which is especially useful so they can see their own DID |
|
|
|
const currentUser: DecryptedMember = { |
|
|
|
member: { |
|
|
|
admitted: false, |
|
|
|
content: "{}", |
|
|
|
memberId: -1, |
|
|
|
}, |
|
|
|
name: this.firstName, |
|
|
|
did: this.activeDid, |
|
|
|
isRegistered: false, |
|
|
|
}; |
|
|
|
const otherMembersPlusUser = [ currentUser, ...this.decryptedMembers ]; |
|
|
|
members = otherMembersPlusUser; |
|
|
|
} |
|
|
|
|
|
|
|
// Sort members according to priority: |
|
|
|
@ -462,61 +458,51 @@ export default class MembersList extends Vue { |
|
|
|
return this.contacts.find((contact) => contact.did === did); |
|
|
|
} |
|
|
|
|
|
|
|
getPendingMembers(): { |
|
|
|
did: string; |
|
|
|
name: string; |
|
|
|
isContact: boolean; |
|
|
|
member: { memberId: string }; |
|
|
|
}[] { |
|
|
|
getPendingMembersToAdmit(): MemberData[] { |
|
|
|
return this.decryptedMembers |
|
|
|
.filter((member) => { |
|
|
|
// Exclude the current user |
|
|
|
if (member.did === this.activeDid) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
// Only include non-admitted members |
|
|
|
return !member.member.admitted; |
|
|
|
}) |
|
|
|
.map((member) => ({ |
|
|
|
did: member.did, |
|
|
|
name: member.name, |
|
|
|
isContact: !!this.getContactFor(member.did), |
|
|
|
member: { |
|
|
|
memberId: member.member.memberId.toString(), |
|
|
|
}, |
|
|
|
})); |
|
|
|
.filter((member) => |
|
|
|
member.did !== this.activeDid && !member.member.admitted |
|
|
|
) |
|
|
|
.map(this.convertDecryptedMemberToMemberData); |
|
|
|
} |
|
|
|
|
|
|
|
getNonContactMembers(): { |
|
|
|
did: string; |
|
|
|
name: string; |
|
|
|
isContact: boolean; |
|
|
|
member: { memberId: string }; |
|
|
|
}[] { |
|
|
|
getNonContactMembers(): MemberData[] { |
|
|
|
return this.decryptedMembers |
|
|
|
.filter((member) => !this.getContactFor(member.did)) |
|
|
|
.map((member) => ({ |
|
|
|
did: member.did, |
|
|
|
name: member.name, |
|
|
|
isContact: false, |
|
|
|
member: { |
|
|
|
memberId: member.member.memberId.toString(), |
|
|
|
}, |
|
|
|
})); |
|
|
|
.filter((member) => |
|
|
|
member.did !== this.activeDid && !this.getContactFor(member.did) |
|
|
|
) |
|
|
|
.map(this.convertDecryptedMemberToMemberData); |
|
|
|
} |
|
|
|
|
|
|
|
convertDecryptedMemberToMemberData( |
|
|
|
decryptedMember: DecryptedMember, |
|
|
|
): MemberData { |
|
|
|
return { |
|
|
|
did: decryptedMember.did, |
|
|
|
name: decryptedMember.name, |
|
|
|
isContact: !!this.getContactFor(decryptedMember.did), |
|
|
|
member: { |
|
|
|
memberId: decryptedMember.member.memberId.toString(), |
|
|
|
}, |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Show the admit pending members dialog if conditions are met |
|
|
|
*/ |
|
|
|
checkAndShowAdmitPendingDialog(showPendingEvenIfAllWereIgnored = false) { |
|
|
|
async refreshData(bypassPromptIfAllWereIgnored = true) { |
|
|
|
// Force refresh both contacts and members |
|
|
|
this.contacts = await this.$getAllContacts(); |
|
|
|
await this.fetchMembers(); |
|
|
|
|
|
|
|
const pendingMembers = this.isOrganizer |
|
|
|
? this.getPendingMembers() |
|
|
|
? this.getPendingMembersToAdmit() |
|
|
|
: this.getNonContactMembers(); |
|
|
|
if (pendingMembers.length === 0) { |
|
|
|
this.startAutoRefresh(); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (!showPendingEvenIfAllWereIgnored) { |
|
|
|
if (bypassPromptIfAllWereIgnored) { |
|
|
|
// only show if there are pending members that have not been ignored |
|
|
|
const pendingMembersNotIgnored = pendingMembers.filter( |
|
|
|
(member) => !this.previousMemberDidsIgnored.includes(member.did), |
|
|
|
@ -528,31 +514,21 @@ export default class MembersList extends Vue { |
|
|
|
} |
|
|
|
} |
|
|
|
this.stopAutoRefresh(); |
|
|
|
this.pendingMembersData = pendingMembers; |
|
|
|
if (this.isOrganizer) { |
|
|
|
( |
|
|
|
this.$refs.admitPendingMembersDialog as AdmitPendingMembersDialog |
|
|
|
).open(); |
|
|
|
).open(pendingMembers); |
|
|
|
} else { |
|
|
|
this.visibleBulkVisibilityDialog = true; |
|
|
|
( |
|
|
|
this.$refs.setBulkVisibilityDialog as SetBulkVisibilityDialog |
|
|
|
).open(pendingMembers); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Admit Pending Members Dialog methods |
|
|
|
async closeAdmitPendingDialog( |
|
|
|
async closeMemberSelectionDialogCallback( |
|
|
|
result: { notSelectedMemberDids: string[] } | undefined, |
|
|
|
) { |
|
|
|
this.pendingMembersData = []; |
|
|
|
this.previousMemberDidsIgnored = result?.notSelectedMemberDids || []; |
|
|
|
|
|
|
|
await this.refreshData(); |
|
|
|
} |
|
|
|
|
|
|
|
async closeSetBulkVisibilityDialog( |
|
|
|
result: { notSelectedMemberDids: string[] } | undefined, |
|
|
|
) { |
|
|
|
this.visibleBulkVisibilityDialog = false; |
|
|
|
this.pendingMembersData = []; |
|
|
|
this.previousMemberDidsIgnored = result?.notSelectedMemberDids || []; |
|
|
|
|
|
|
|
await this.refreshData(); |
|
|
|
@ -697,6 +673,7 @@ export default class MembersList extends Vue { |
|
|
|
} |
|
|
|
|
|
|
|
startAutoRefresh() { |
|
|
|
this.stopAutoRefresh(); |
|
|
|
this.lastRefreshTime = Date.now(); |
|
|
|
this.countdownTimer = 10; |
|
|
|
|
|
|
|
@ -726,17 +703,6 @@ export default class MembersList extends Vue { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async manualRefresh() { |
|
|
|
// Clear existing auto-refresh interval |
|
|
|
if (this.autoRefreshInterval) { |
|
|
|
clearInterval(this.autoRefreshInterval); |
|
|
|
this.autoRefreshInterval = null; |
|
|
|
} |
|
|
|
|
|
|
|
// Trigger immediate refresh |
|
|
|
await this.refreshData(true); |
|
|
|
} |
|
|
|
|
|
|
|
beforeDestroy() { |
|
|
|
this.stopAutoRefresh(); |
|
|
|
} |
|
|
|
|