Now with upload and download percent! Also better editing for session POC.

This commit is contained in:
Scott Idem
2024-06-21 12:25:36 -04:00
parent fd114bce22
commit 2552e1a839
11 changed files with 433 additions and 64 deletions

View File

@@ -355,33 +355,35 @@ async function handle_update_ae_obj_id_crud(
async function handle_download_export__obj_type(
{
api_cfg,
get_obj_type, // The type of object to return: event_badge, event_presenter, sponsorship, etc.
for_obj_type, // Usually for an account, event, event_exhibit, or sponsorship_cfg
for_obj_id, // The ID of the object
file_type='CSV', // 'CSV' or 'Excel'
return_file=true,
filename='no_filename.csv',
auto_download=false,
limit=5000,
params={}, // key value object is expected
log_lvl=0
api_cfg,
get_obj_type, // The type of object to return: event_badge, event_presenter, sponsorship, etc.
for_obj_type, // Usually for an account, event, event_exhibit, or sponsorship_cfg
for_obj_id, // The ID of the object
file_type='CSV', // 'CSV' or 'Excel'
return_file=true,
filename='no_filename.csv',
auto_download=false,
limit=5000,
params={}, // key value object is expected
log_lvl=0
} : {
api_cfg: any,
get_obj_type: string,
for_obj_type: string,
for_obj_id: string,
file_type?: string,
return_file?: boolean,
filename?: string,
auto_download?: boolean,
limit?: number,
params?: key_val,
log_lvl?: number
api_cfg: any,
get_obj_type: string,
for_obj_type: string,
for_obj_id: string,
file_type?: string,
return_file?: boolean,
filename?: string,
auto_download?: boolean,
limit?: number,
params?: key_val,
log_lvl?: number
}
) {
console.log('*** ae_core_functions.js: handle_download_export__obj_type() ***');
let task_id = for_obj_id;
const endpoint = `/v2/crud/${get_obj_type}/list`;
params['for_obj_type'] = for_obj_type;
params['for_obj_id'] = for_obj_id;
@@ -402,7 +404,16 @@ async function handle_download_export__obj_type(
params['limit'] = limit;
}
ae_promises.download__sponsorship_export_file = await api.get_object({api_cfg: api_cfg, endpoint: endpoint, params: params, return_blob: return_file, filename: clean_filename, auto_download: auto_download, log_lvl: log_lvl});
ae_promises.download__sponsorship_export_file = await api.get_object({
api_cfg: api_cfg,
endpoint: endpoint,
params: params,
return_blob: return_file,
filename: clean_filename,
auto_download: auto_download,
task_id: task_id,
log_lvl: log_lvl
});
console.log('ae_promises.download__sponsorship_export_file:', ae_promises.download__sponsorship_export_file);
return ae_promises.download__sponsorship_export_file;

View File

@@ -198,7 +198,10 @@ export let ae_app_session_data_struct: key_val = {
},
'download': {},
'download_li': {},
// For API download and upload progress status per file.
'api_download_kv': {},
// Example: {example_file_id: {status: 'downloading', endpoint: '/event/file/abc123/download', filename: 'example_file_name.ext', size_total: 0, size_loaded: 0, percent_completed: 0}}
'api_upload_kv': {}, // {example_temp_id: {status: 'uploading', endpoint: '/event/file/abc123/upload', filename: 'example_file_name.ext', size_total: 0, size_loaded: 0, percent_completed: 0}}
};
// console.log(`AE Stores - App Session Storage Data:`, ae_app_session_data_struct);
export let ae_sess = writable(ae_app_session_data_struct);

View File

@@ -841,13 +841,24 @@ export let download_hosted_file = async function download_hosted_file({
}) {
console.log('*** stores_hosted_api.js: download_hosted_file() ***');
let task_id = hosted_file_id;
const endpoint = `/hosted_file/${hosted_file_id}/download`;
if (filename) {
params['filename'] = filename;
}
params['return_file'] = true;
let hosted_file_download_get_promise = await api.get_object({api_cfg: api_cfg, endpoint: endpoint, params: params, return_blob: true, filename: filename, auto_download: auto_download, log_lvl: log_lvl});
let hosted_file_download_get_promise = await api.get_object({
api_cfg: api_cfg,
endpoint: endpoint,
params: params,
return_blob: true,
filename: filename,
auto_download: auto_download,
task_id: task_id,
log_lvl: log_lvl
});
// console.log(hosted_file_download_get_promise);
return hosted_file_download_get_promise;
}

View File

@@ -36,7 +36,7 @@ export let get_object = async function get_object(
timeout?: number,
return_meta?: boolean,
return_blob?: boolean,
filename?: string,
filename?: null|string,
auto_download?: boolean,
as_list?: boolean,
task_id?: string,
@@ -119,9 +119,27 @@ export let get_object = async function get_object(
let percent_completed = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
// console.log('GET Data Timestamp:', progressEvent.timeStamp, 'Total:', progressEvent.total, 'Loaded:', progressEvent.loaded, 'Percent Completed', percent_completed);
console.log('GET Data Progress:', progressEvent.progress, 'Total:', progressEvent.total, 'Loaded:', progressEvent.loaded, 'Percent Completed', percent_completed);
temp_get_object_percent_completed = percent_completed;
// WARNING: This needs to be tied to an object type and ID. This is a temporary solution.
try {
window.postMessage({
type: 'api_download_data',
status: 'downloading',
task_id: task_id,
endpoint: endpoint,
filename: filename,
size_total: progressEvent.total,
size_loaded: progressEvent.loaded,
percent_completed: percent_completed,
},
'*'
);
} catch (error) {
console.log('Error posting message to window:', error);
}
}
}
)
@@ -132,6 +150,25 @@ export let get_object = async function get_object(
if (log_lvl > 1) {
console.log('GET Response:', response);
}
// Post file download message
try {
window.postMessage({
type: 'api_download_data',
status: 'complete',
task_id: task_id,
endpoint: endpoint,
filename: filename,
size_total: 0,
size_loaded: 0,
percent_completed: 100,
},
'*'
);
} catch (error) {
console.log('Error posting message to window:', error);
}
if (!Array.isArray(response.data['data']) && as_list) {
if (log_lvl) {
console.log('Data result is a dictionary/object, not an array/list. Forcing return as an array/list');
@@ -252,11 +289,13 @@ export let get_object = async function get_object(
try {
window.postMessage({
type: 'api_download_blob',
status: 'downloading',
task_id: task_id,
endpoint: endpoint,
filename: filename,
size_total: progressEvent.total,
size_loaded: progressEvent.loaded,
percent_completed: percent_completed
percent_completed: percent_completed,
},
'*'
);
@@ -287,7 +326,14 @@ export let get_object = async function get_object(
// WARNING: This needs to be tied to an object type and ID. This is a temporary solution.
try {
window.postMessage({
type: 'api_download_blob', endpoint: endpoint, filename: filename, size_total: 0, size_loaded: 0, percent_completed: 100
type: 'api_download_blob',
status: 'complete',
task_id: task_id,
endpoint: endpoint,
filename: filename,
size_total: 0,
size_loaded: 0,
percent_completed: 100,
},
'*'
);

View File

@@ -87,6 +87,8 @@ export let post_object = async function post_object(
try {
window.postMessage({
type: 'api_post_json_form',
status: 'uploading',
task_id: task_id,
endpoint: endpoint,
size_total: progressEvent.total,
size_loaded: progressEvent.loaded,
@@ -104,6 +106,24 @@ export let post_object = async function post_object(
)
.then(function (response) {
console.log('POST Response Data:', response.data);
try {
window.postMessage({
type: 'api_post_json_form',
status: 'complete',
task_id: task_id,
endpoint: endpoint,
size_total: 0,
size_loaded: 0,
percent_completed: 100,
progress: 100,
rate: 0,
},
'*'
);
} catch (error) {
console.log('Error posting message to window:', error);
}
if (response.data['data'].result === null) {
// This should mean that the request was successful, but a result of None/null was returned from Aether API.
// console.log('Returning null after POST');

View File

@@ -210,6 +210,9 @@ onMount(() => {
'size_loaded': 0,
'percent_completed': 0,
};
window.addEventListener('message', function(event) {
console.log('Message received in root +layout.svelte:');
console.log(event);
@@ -220,26 +223,48 @@ onMount(() => {
// Get the event_file_id from the event.data.endpoint value.
// Example: /event/file/abc123/download
let endpoint = event.data.endpoint;
// let endpoint = event.data.endpoint;
// let event_file_id = endpoint.split('/')[3];
// ae_downloads[event_file_id] = {
let task_id = event.data.task_id;
// $ae_sess.download = {
$ae_sess.download[endpoint] = {
'endpoint': endpoint,
$ae_sess.download[event.data.endpoint] = {
'status': event.data.status,
'task_id': task_id,
'endpoint': event.data.endpoint,
'filename': event.data.filename,
'size_total': event.data.size_total,
'size_loaded': event.data.size_loaded,
'percent_completed': event.data.percent_completed,
};
// let event_file_id = event.data.event_file_id;
// let filename = event.data.filename;
// let auto_download = event.data.auto_download;
$ae_sess.api_download_kv[task_id] =
{
'status': event.data.status,
'task_id': task_id,
'endpoint': event.data.endpoint,
'filename': event.data.filename,
'size_total': event.data.size_total,
'size_loaded': event.data.size_loaded,
'percent_completed': event.data.percent_completed,
};
} else if (event.data.type == 'api_post_json_form') {
console.log('Post JSON form message received:', event.data);
// ae_promises[event_file_id]
// ae_promises[event_file_id] = download_event_file({ 'event_file_id': event_file_id, 'return_file': true, filename: filename, auto_download: auto_download, log_lvl: 1 });
let task_id = event.data.task_id;
$ae_sess.api_upload_kv[task_id] =
{
'status': event.data.status,
'task_id': task_id,
'endpoint': event.data.endpoint,
'filename': event.data.filename,
'size_total': event.data.size_total,
'size_loaded': event.data.size_loaded,
'percent_completed': event.data.percent_completed,
'progress': event.data.progress,
'rate': event.data.rate,
};
}
});

View File

@@ -120,13 +120,13 @@ onMount(() => {
|| $ae_loc.trusted_access
}
<!-- {#if $events_slct.event_id === event_obj.id_random} -->
<!-- {#if $events_slct.event_id === event_obj.event_id_random} -->
<a
href="/events_pres_mgmt/event/{event_obj.id_random}"
href="/events_pres_mgmt/event/{event_obj.event_id_random}"
class="btn btn-md variant-ghost-primary hover:variant-filled-primary hover:underline"
on:pointerover={() => {
// When the cursor is hovering we want to set the event_id and event_obj
// $events_slct.event_id = event_obj.id_random;
// $events_slct.event_id = event_obj.event_id_random;
// $events_slct.event_obj = event_obj;
}}
>
@@ -148,7 +148,7 @@ onMount(() => {
{#if $ae_loc.trusted_access}
<a
data-sveltekit-reload
href="/event/{event_obj.id_random}"
href="/event/{event_obj.event_id_random}"
class="btn btn-sm variant-ghost-warning hover:variant-filled-warning hover:underline"
>
Manage

View File

@@ -11,7 +11,10 @@ import { clipboard, FileDropzone, getModalStore, localStorageStore, ProgressRadi
import type { key_val } from '$lib/ae_stores';
import { ae_util } from '$lib/ae_utils';
import { api } from '$lib/api';
import Element_ae_crud from '$lib/element_ae_crud.svelte';
import { liveQuery } from "dexie";
import { core_func } from '$lib/ae_core_functions';
import { db_events } from "$lib/db_events";
import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
import { events_loc, events_sess, events_slct, events_trigger } from '$lib/ae_events_stores';
@@ -52,7 +55,7 @@ let event_obj_v2 = db_events.events.get($events_slct.event_id);
let load_obj_li_results: Promise<any>|key_val;
let search_submit_results: Promise<any>|key_val;
// These will likely be used for patch/update triggers. Maybe delete?
let ae_tmp: key_val = {};
let ae_triggers: key_val = {};
let ae_event_session_get_promise: Promise<any>;
@@ -294,7 +297,7 @@ $: if ($events_trigger == 'load__event_session_obj_li' && $events_slct.event_id)
<th>Date</th>
<th>Start/End</th>
<th>Location</th>
<!-- <th>Actions</th> -->
<th>POC</th>
</tr>
</thead>
<tbody>
@@ -330,15 +333,153 @@ $: if ($events_trigger == 'load__event_session_obj_li' && $events_slct.event_id)
{/if}
</td>
<td>{session_obj.event_location_name ?? '-- not set --'}</td>
<!-- <td>
<a
href="/events_pres_mgmt/session/{session_obj.event_session_id_random}"
class="btn btn-md variant-ghost-secondary hover:variant-glass-secondary"
<td>
{#if session_obj.poc_person_full_name}
<span style="break-inside: avoid;">
<span class="fas fa-user mx-1"></span>
{session_obj.poc_person_full_name}
</span>
<!-- <br> -->
{#if $ae_loc.trusted_access}
<span style="break-inside: avoid;">
<a href="mailto:{session_obj.poc_person_primary_email}" class="hover:underline">
<span class="fas fa-envelope mx-1"></span>
{session_obj.poc_person_primary_email}
</a>
</span>
{/if}
{:else}
-- not set --
{/if}
{#if $ae_loc.trusted_access}
{#if session_obj.event_session_id_random == $events_slct.event_session_id && ae_tmp[$events_slct.event_session_id] && ae_tmp[$events_slct.event_session_id].show__edit_poc_person}
<Element_ae_crud
trigger_patch={ae_triggers.update_person_poc}
api_cfg={$ae_api}
object_type={'event_session'}
object_id={$events_slct?.event_session_id}
field_name={'poc_person_id_random'}
field_type={'button'}
field_value={ae_tmp[$events_slct.event_session_id].poc_person_id}
allow_null={false}
hide_edit_btn={true}
outline_element={false}
show_crud={false}
display_inline={true}
class_li={'m-1'}
on:ae_crud_updated={e => {
console.log(`ae_crud_updated:`, e.detail);
events_func.handle_load_ae_obj_id__event_session({api_cfg: $ae_api, event_session_id: $events_slct?.event_session_id, log_lvl: 1})
.then(function (load_results) {
ae_tmp[$events_slct.event_session_id].poc_person_id = null;
ae_tmp[$events_slct.event_session_id].show__edit_poc_person = false;
$events_slct.event_session_id = null;
$events_slct.event_obj = null;
// Careful with the trigger_patch. It will keep firing if not reset.
ae_triggers.update_person_poc = false;
// Maybe reload page?
// window.location.reload();
});
}}
>
<span class="fas fa-eye mx-1"></span>
View
</a>
</td> -->
{#await $slct.person_obj_li}
<span class="fas fa-spinner fa-spin mx-1"></span>
{:then person_obj_li}
{#if person_obj_li && person_obj_li.length > 0}
<label class="text-sm">PoC:
<select
bind:value={ae_tmp[$events_slct.event_session_id].poc_person_id}
class="select min-w-fit max-w-md text-sm"
>
<option value="">-- Select a person --</option>
{#each person_obj_li as person_obj}
<option
value={person_obj.person_id_random}
selected={person_obj.person_id_random == $events_slct?.event_session_obj?.poc_person_id_random}
>
{person_obj.full_name}
({person_obj.primary_email})
<!-- (ID: {person_obj.person_id_random}) -->
</option>
{/each}
</select>
</label>
<button
type="button"
disabled={ae_tmp[$events_slct.event_session_id].poc_person_id == $events_slct?.event_session_obj?.poc_person_id_random}
on:click={() => {
console.log('Save the POC person for the session.');
let person_id = ae_tmp[$events_slct.event_session_id].poc_person_id;
console.log('Selected person ID:', person_id);
ae_triggers.update_person_poc = true;
}}
class="btn btn-sm variant-soft-warning hover:variant-ghost-warning"
>
<span class="fas fa-save mx-1"></span>
Save
</button>
{/if}
{/await}
</Element_ae_crud>
{/if}
{#if ae_tmp[$events_slct.event_session_id] && ae_tmp[$events_slct.event_session_id].show__edit_poc_person}
<button
type="button"
on:click={() => {
console.log('Cancel the POC person for the session.');
ae_tmp[$events_slct.event_session_id].poc_person_id = null;
ae_tmp[$events_slct.event_session_id].show__edit_poc_person = false;
$events_slct.event_session_id = null;
$events_slct.event_obj = null;
}}
class="btn btn-sm variant-soft-warning hover:variant-ghost-warning"
>
<span class="fas fa-times mx-1"></span>
Cancel
</button>
{:else}
<button
type="button"
on:click={() => {
console.log('Edit the POC person for the session.');
let params = {
qry__limit: 300,
}
$slct.person_obj_li = core_func.handle_load_ae_obj_li__person({api_cfg: $ae_api, account_id: $slct.account_id, params: params});
$events_slct.event_session_id = session_obj.event_session_id_random;
ae_tmp[$events_slct.event_session_id] = {
poc_person_id: session_obj?.poc_person_id_random,
show__edit_poc_person: true,
};
// }
// ae_tmp[$events_slct.event_session_id].poc_person_id = $events_slct?.event_session_obj?.poc_person_id_random;
// ae_tmp[$events_slct.event_session_id].show__edit_poc_person = true;
}}
class="btn btn-sm variant-soft-warning hover:variant-ghost-warning"
>
<span class="fas fa-edit mx-1"></span>
Edit
</button>
{/if}
{/if} <!-- $ae_loc.trusted_access -->
</td>
</tr>
{/each}
</tbody>

View File

@@ -436,6 +436,90 @@ function send_sign_in_poc_email(
<span class="fas fa-barcode"></span>
{$lq__event_session_obj.code}
</span>
{#if $ae_loc.trusted_access}
<Element_ae_crud
trigger_patch={ae_triggers.update_event_session}
api_cfg={$ae_api}
object_type={'event_session'}
object_id={$lq__event_session_obj?.event_session_id_random}
field_name={'name'}
field_type={'text'}
field_value={ae_tmp.name}
allow_null={false}
hide_edit_btn={true}
outline_element={false}
show_crud={false}
display_inline={true}
class_li={'m-1'}
on:ae_crud_updated={e => {
console.log(`ae_crud_updated:`, e.detail);
events_func.handle_load_ae_obj_id__event_session({api_cfg: $ae_api, event_session_id: $lq__event_session_obj?.event_session_id_random, log_lvl: 1})
.then(function (load_results) {
ae_tmp.name = null;
ae_tmp.show__edit_name = false;
// Maybe reload page?
// window.location.reload();
});
}}
>
{#if ae_tmp?.show__edit_name}
<input
type="text"
bind:value={ae_tmp.name}
class="input min-w-fit max-w-md text-sm"
/>
<button
type="button"
disabled={ae_tmp.name == $lq__event_session_obj?.name}
on:click={() => {
console.log('Save the session name.');
let name = ae_tmp.name;
console.log('New session name:', name);
ae_triggers.update_event_session = true;
}}
class="btn btn-sm variant-soft-warning hover:variant-ghost-warning"
>
<span class="fas fa-save mx-1"></span>
Save
</button>
{/if}
{#if ae_tmp.show__edit_name}
<button
type="button"
on:click={() => {
console.log('Cancel the session name.');
ae_tmp.name = null;
ae_tmp.show__edit_name = false;
}}
class="btn btn-sm variant-soft-warning hover:variant-ghost-warning"
>
<span class="fas fa-times mx-1"></span>
Cancel
</button>
{:else}
<button
type="button"
on:click={() => {
console.log('Edit the session name.');
ae_tmp.name = $lq__event_session_obj?.name;
ae_tmp.show__edit_name = true;
}}
class="btn btn-sm variant-soft-warning hover:variant-ghost-warning"
>
<span class="fas fa-edit mx-1"></span>
Edit
</button>
{/if}
</Element_ae_crud>
{/if}
</li>
<li>
<strong class="text-sm">Date time:</strong> {ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, 'datetime_long')} - {ae_util.iso_datetime_formatter($lq__event_session_obj.end_datetime, 'datetime_long')}
@@ -548,7 +632,8 @@ function send_sign_in_poc_email(
$slct.person_obj_li = core_func.handle_load_ae_obj_li__person({api_cfg: $ae_api, account_id: $slct.account_id, params: params});
ae_tmp.poc_person_id = $lq__event_session_obj?.poc_person_id_random;ae_tmp.show__edit_poc_person = true;
ae_tmp.poc_person_id = $lq__event_session_obj?.poc_person_id_random;
ae_tmp.show__edit_poc_person = true;
}}
class="btn btn-sm variant-soft-warning hover:variant-ghost-warning"
>
@@ -917,6 +1002,7 @@ function send_sign_in_poc_email(
class="input min-w-96 max-w-96 text-sm"
/>
<!-- disabled={$events_slct.event_presentation_obj.name == event_presentation_obj.name} -->
<button
type="button"
on:click={() => {
@@ -1206,8 +1292,8 @@ function send_sign_in_poc_email(
<span class="fas fa-spinner fa-spin mx-1"></span>
<span class="">
Downloading
{#if $ae_sess.download[`/hosted_file/${event_file_obj.hosted_file_id_random}/download`]}
{$ae_sess.download[`/hosted_file/${event_file_obj.hosted_file_id_random}/download`].percent_completed}%
{#if $ae_sess.api_download_kv[event_file_obj.hosted_file_id_random]}
{$ae_sess.api_download_kv[event_file_obj.hosted_file_id_random].percent_completed}%
{/if}
:
</span>

View File

@@ -191,8 +191,14 @@ async function handle_input_upload_files(input_upload_files) {
// Uncomment and the post_promise is not seen by the "await" below
// post_promise = await api.post_object({api_cfg: $cfg.api, endpoint: endpoint, params: params, data:form_data});
// Uncomment so that the post_promise is not seen by the "await" below
ae_promises.upload__hosted_file_obj = await api.post_object({api_cfg: $ae_api, endpoint: endpoint, params: params, form_data: form_data})
ae_promises.upload__hosted_file_obj = api.post_object({
api_cfg: $ae_api,
endpoint: endpoint,
params: params,
form_data: form_data,
task_id: link_to_id,
log_lvl: 1
})
.then(async function (result) {
// NOTE: The /hosted_file/upload_files endpoint will always return a list of successful files uploaded. In this case we are only uploading one file and expecting a list of one item.
let x = 0;
@@ -442,8 +448,28 @@ WARNING: The file upload and management is a work in progress. You can upload an
// handle_submit_form;
}}
>
<span class="fas fa-save mx-1"></span>
{#await ae_promises.upload__hosted_file_obj}
<span class="fas fa-spinner fa-spin mx-1"></span>
<span class="">
Uploading
{#if $ae_sess.api_upload_kv[$events_slct.event_presenter_id]}
{$ae_sess.api_upload_kv[$events_slct.event_presenter_id].percent_completed}%
{/if}
</span>
{:then}
<span class="fas fa-upload mx-1"></span>
<span class="text-sm">
Upload:
</span>
<!-- <span class="fas fa-save mx-1"></span> -->
<span class="grow">
File
</span>
{/await}
<!-- <span class="fas fa-save mx-1"></span>
<span class="grow">
Upload File
</span> -->
</button>
</form>
@@ -482,8 +508,8 @@ WARNING: The file upload and management is a work in progress. You can upload an
<span class="fas fa-spinner fa-spin mx-1"></span>
<span class="">
Downloading
{#if $ae_sess.download[`/hosted_file/${event_file_obj.hosted_file_id_random}/download`]}
{$ae_sess.download[`/hosted_file/${event_file_obj.hosted_file_id_random}/download`].percent_completed}%
{#if $ae_sess.api_download_kv[event_file_obj.hosted_file_id_random]}
{$ae_sess.api_download_kv[event_file_obj.hosted_file_id_random].percent_completed}%
{/if}
:
</span>

View File

@@ -509,7 +509,7 @@ function generate_guest_list_csv(ae_obj_li) {
</button>
{:else}
<div class="text-center m-4">
The CHOW 2024 sponsor Hub has closed. If you have changes to your gala RSVP list please email Laurie VanBenschoten, <a href="mailto:lvanbenschoten@marinesanctuary.org" class="text-blue-500">lvanbenschoten@marinesanctuary.org</a>, AND Erin Quigg, <a href="mailto:erinq@preconevents.com" class="text-blue-500">erinq@preconevents.com</a>.
The CHOW 2024 sponsor Hub has closed. If you have changes to your gala RSVP list please email Laurie VanBenschoten, <a href="mailto:lvanbenschoten@marinesanctuary.org" class="text-blue-500">lvanbenschoten@marinesanctuary.org</a>, AND Erin Quigg, <a href="mailto:erinq@preconevents.com" class="text-blue-500">erinq@preconevents.com</a>.
</div>
{/if}
@@ -545,8 +545,8 @@ function generate_guest_list_csv(ae_obj_li) {
<!-- Done? -->
{/await}
<span class="fas fa-download mx-1"></span> Export All Data
{#if $ae_sess.download && $ae_sess.download.size_total > $ae_sess.download.size_loaded}
{$ae_sess.download.percent_completed}%
{#if $ae_sess.api_download_kv[$slct.account_id] && $ae_sess.api_download_kv[$slct.account_id].status == 'downloading'}
{$ae_sess.api_download_kv[$slct.account_id].percent_completed}%
{/if}
</button>