Last round of prettier: npx prettier --write src/

This commit is contained in:
Scott Idem
2026-03-24 13:27:40 -04:00
parent 23d25bf65a
commit a8f3c29b9f
146 changed files with 13201 additions and 9277 deletions

View File

@@ -1,188 +1,191 @@
<script lang="ts">
// Imports
// Import components and elements
// import Element_input_files_tbl from '$lib/element_input_files_tbl.svelte';
// Imports
// Import components and elements
// import Element_input_files_tbl from '$lib/element_input_files_tbl.svelte';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
import { Check, Download, LoaderCircle, MinusCircle, Scissors } from '@lucide/svelte';
// Exports
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
import {
Check,
Download,
LoaderCircle,
MinusCircle,
Scissors
} from '@lucide/svelte';
// Exports
// export let input_name = 'file_list';
// export let multiple: boolean = true;
// export let required: boolean = true;
// export let input_name = 'file_list';
// export let multiple: boolean = true;
// export let required: boolean = true;
// export let input_class_li: string[] = ['file_drop_area'];
// export let input_class_li: string[] = ['file_drop_area'];
interface Props {
log_lvl?: number;
// Expecting these for link_to_type: 'event', 'event_location', 'archive_content', etc
link_to_type: string;
link_to_id: string;
// export let accept: string = 'audio/*, image/*, video/*, .bak, .cfg, .css, .csv, .doc, .docx, .gz, .htm, .html, .ini, .iso, .j2, .json, .key, .keynote, .md, .pdf, .ppt, .pptx, .rar, .rtf, .sql, .svelte, ttf, .txt, .xls, .xlsx, .xz, .zip, .bin, .dmg, .exe, .js, .msi, .php, .py, .sh';
class_li_default?: string;
class_li?: string;
// export let table_class_li: string[] = ['table', 'table-sm', 'table-striped', 'table-hover' , 'text-sm'];
clip_complete?: boolean;
// export let upload_complete: boolean = false;
submit_status?: null | string;
// hosted_file_id_li?: string[];
// hosted_file_obj_li?: any[];
hosted_file_obj_kv?: key_val;
video_clip_file_kv?: key_val;
}
interface Props {
log_lvl?: number;
// Expecting these for link_to_type: 'event', 'event_location', 'archive_content', etc
link_to_type: string;
link_to_id: string;
// export let accept: string = 'audio/*, image/*, video/*, .bak, .cfg, .css, .csv, .doc, .docx, .gz, .htm, .html, .ini, .iso, .j2, .json, .key, .keynote, .md, .pdf, .ppt, .pptx, .rar, .rtf, .sql, .svelte, ttf, .txt, .xls, .xlsx, .xz, .zip, .bin, .dmg, .exe, .js, .msi, .php, .py, .sh';
class_li_default?: string;
class_li?: string;
// export let table_class_li: string[] = ['table', 'table-sm', 'table-striped', 'table-hover' , 'text-sm'];
clip_complete?: boolean;
// export let upload_complete: boolean = false;
submit_status?: null | string;
// hosted_file_id_li?: string[];
// hosted_file_obj_li?: any[];
hosted_file_obj_kv?: key_val;
video_clip_file_kv?: key_val;
}
let {
log_lvl = $bindable(0),
link_to_type = $bindable(),
link_to_id = $bindable(),
class_li_default = 'flex flex-col gap-1 items-center justify-center w-full max-w-2xl mx-auto my-1',
class_li = $bindable(''),
clip_complete = $bindable(false),
submit_status = $bindable(null),
// hosted_file_id_li = [],
// hosted_file_obj_li = [],
hosted_file_obj_kv = $bindable({}),
video_clip_file_kv = $bindable({})
}: Props = $props();
let {
log_lvl = $bindable(0),
link_to_type = $bindable(),
link_to_id = $bindable(),
class_li_default = 'flex flex-col gap-1 items-center justify-center w-full max-w-2xl mx-auto my-1',
class_li = $bindable(''),
clip_complete = $bindable(false),
submit_status = $bindable(null),
// hosted_file_id_li = [],
// hosted_file_obj_li = [],
hosted_file_obj_kv = $bindable({}),
video_clip_file_kv = $bindable({})
}: Props = $props();
// Local Variables
let task_id = link_to_id;
// let input_file_list: any = null;
let ae_promises: key_val = $state({});
// let ae_promises_clipping: key_val = {};
// let ae_triggers: key_val = {};
// Local Variables
let task_id = link_to_id;
// let input_file_list: any = null;
let ae_promises: key_val = $state({});
// let ae_promises_clipping: key_val = {};
// let ae_triggers: key_val = {};
// let input_element_id = 'ae_comp__hosted_files_upload__input';
// let input_element_id = 'ae_comp__hosted_files_upload__input';
// let form_kv: key_val = {
// start_time: null,
// end_time: null,
// reencode: null,
// video_file: null,
// };
// let download_clip_src: string;
// let download_clip_filename: string;
// let form_kv: key_val = {
// start_time: null,
// end_time: null,
// reencode: null,
// video_file: null,
// };
// let download_clip_src: string;
// let download_clip_filename: string;
$ae_sess.files.obj = {
obj: null
$ae_sess.files.obj = {
obj: null
};
// *** Functions and Logic
function prevent_default<T extends Event>(fn: (event: T) => void) {
return function (event: T) {
event.preventDefault();
fn(event);
};
}
function handle_clip_video(event: Event) {
console.log('*** handle_clip_video() ***');
submit_status = 'clipping';
clip_complete = false;
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
let hosted_file_id = formData.get('hosted_file_id') as string;
let start_time = formData.get('start_time') as string;
let end_time = formData.get('end_time') as string;
let reencode = formData.get('reencode') as string;
let scale_down = formData.get('scale_down') as string;
let new_filename = formData.get('new_filename') as string;
$ae_sess.files.processed_file_kv[hosted_file_id] = {};
$ae_sess.files.processed_file_kv[hosted_file_id].submit_status = 'clipping';
$ae_sess.files.processed_file_kv[hosted_file_id].clip_complete = false;
// $ae_sess.files.disable_submit__hosted_file_obj = true;
$ae_loc.files.processed_file_kv[hosted_file_id] = {};
$ae_loc.files.processed_file_kv[hosted_file_id].submit_status = 'clipping';
$ae_loc.files.processed_file_kv[hosted_file_id].start_time = start_time;
$ae_loc.files.processed_file_kv[hosted_file_id].end_time = end_time;
$ae_loc.files.processed_file_kv[hosted_file_id].reencode = reencode;
$ae_loc.files.processed_file_kv[hosted_file_id].scale_down = scale_down;
$ae_loc.files.processed_file_kv[hosted_file_id].new_filename = new_filename;
$ae_loc.files.processed_file_kv[hosted_file_id].clip_complete = false;
let endpoint = `/hosted_file/${hosted_file_id}/clip_video`;
let params = {
link_to_type: link_to_type,
link_to_id: link_to_id,
filename_no_ext: new_filename.replace('.mp4', ''),
from_type: 'mp4', // Video file type being converted
to_type: 'mp4', // Video file type to convert to
start_time: start_time,
end_time: end_time,
reencode: reencode,
scale_down: scale_down
};
// *** Functions and Logic
function prevent_default<T extends Event>(fn: (event: T) => void) {
return function (event: T) {
event.preventDefault();
fn(event);
};
}
ae_promises[hosted_file_id] = {};
// .convert__hosted_file_obj
ae_promises[hosted_file_id] = api
.get_object({
api_cfg: $ae_api,
endpoint: endpoint,
params: params,
timeout: 300000, // 5 minutes
// return_blob: true,
// filename: event.target.new_filename.value,
// auto_download: false,
task_id: task_id,
log_lvl: log_lvl
})
.then(function (result) {
console.log(result);
function handle_clip_video(event: Event) {
console.log('*** handle_clip_video() ***');
video_clip_file_kv[result.hosted_file_id] = {};
video_clip_file_kv[result.hosted_file_id] = result;
submit_status = 'clipping';
clip_complete = false;
// $ae_loc.files.video_clip_file_kv[result.hosted_file_id] = {};
// $ae_loc.files.video_clip_file_kv[result.hosted_file_id] = result;
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
$ae_sess.files.processed_file_kv[hosted_file_id].submit_status =
'clipped';
$ae_sess.files.processed_file_kv[hosted_file_id].clip_complete =
true;
let hosted_file_id = formData.get('hosted_file_id') as string;
let start_time = formData.get('start_time') as string;
let end_time = formData.get('end_time') as string;
let reencode = formData.get('reencode') as string;
let scale_down = formData.get('scale_down') as string;
let new_filename = formData.get('new_filename') as string;
$ae_loc.files.processed_file_kv[hosted_file_id].submit_status =
'clipped';
$ae_loc.files.processed_file_kv[hosted_file_id].clip_complete =
true;
$ae_sess.files.processed_file_kv[hosted_file_id] = {};
$ae_sess.files.processed_file_kv[hosted_file_id].submit_status =
'clipping';
$ae_sess.files.processed_file_kv[hosted_file_id].clip_complete = false;
submit_status = 'clipped';
clip_complete = true;
// $ae_sess.files.disable_submit__hosted_file_obj = true;
$ae_loc.files.processed_file_kv[hosted_file_id] = {};
$ae_loc.files.processed_file_kv[hosted_file_id].submit_status =
'clipping';
$ae_loc.files.processed_file_kv[hosted_file_id].start_time = start_time;
$ae_loc.files.processed_file_kv[hosted_file_id].end_time = end_time;
$ae_loc.files.processed_file_kv[hosted_file_id].reencode = reencode;
$ae_loc.files.processed_file_kv[hosted_file_id].scale_down = scale_down;
$ae_loc.files.processed_file_kv[hosted_file_id].new_filename =
new_filename;
$ae_loc.files.processed_file_kv[hosted_file_id].clip_complete = false;
// let file_blob = new Blob([result.data]);
// // console.log(file_blob);
// let file_obj_url = window.URL.createObjectURL(file_blob); // The img src
// // const url = window.URL.createObjectURL(new Blob([result.data]));
// download_clip_src = file_obj_url;
// // download_filename = file_obj_url;
let endpoint = `/hosted_file/${hosted_file_id}/clip_video`;
let params = {
link_to_type: link_to_type,
link_to_id: link_to_id,
filename_no_ext: new_filename.replace('.mp4', ''),
from_type: 'mp4', // Video file type being converted
to_type: 'mp4', // Video file type to convert to
start_time: start_time,
end_time: end_time,
reencode: reencode,
scale_down: scale_down
};
ae_promises[hosted_file_id] = {};
// .convert__hosted_file_obj
ae_promises[hosted_file_id] = api
.get_object({
api_cfg: $ae_api,
endpoint: endpoint,
params: params,
timeout: 300000, // 5 minutes
// return_blob: true,
// filename: event.target.new_filename.value,
// auto_download: false,
task_id: task_id,
log_lvl: log_lvl
})
.then(function (result) {
console.log(result);
video_clip_file_kv[result.hosted_file_id] = {};
video_clip_file_kv[result.hosted_file_id] = result;
// $ae_loc.files.video_clip_file_kv[result.hosted_file_id] = {};
// $ae_loc.files.video_clip_file_kv[result.hosted_file_id] = result;
$ae_sess.files.processed_file_kv[hosted_file_id].submit_status =
'clipped';
$ae_sess.files.processed_file_kv[hosted_file_id].clip_complete =
true;
$ae_loc.files.processed_file_kv[hosted_file_id].submit_status =
'clipped';
$ae_loc.files.processed_file_kv[hosted_file_id].clip_complete =
true;
submit_status = 'clipped';
clip_complete = true;
// let file_blob = new Blob([result.data]);
// // console.log(file_blob);
// let file_obj_url = window.URL.createObjectURL(file_blob); // The img src
// // const url = window.URL.createObjectURL(new Blob([result.data]));
// download_clip_src = file_obj_url;
// // download_filename = file_obj_url;
return true;
});
}
return true;
});
}
</script>
<section class="{class_li_default} {class_li}">
@@ -191,11 +194,11 @@
</h3>
{#each Object.entries(hosted_file_obj_kv) as [hosted_file_id, hosted_file_obj] (hosted_file_id)}
<div class="border border-surface-500/20 rounded-lg p-2 m-2 preset-tonal-surface">
<div
class="border-surface-500/20 preset-tonal-surface m-2 rounded-lg border p-2">
<!-- Download Button (Standardized) -->
<div
class="flex flex-row flex-wrap gap-1 justify-center items-center w-full"
>
class="flex w-full flex-row flex-wrap items-center justify-center gap-1">
<!-- Remove from uploaded file kv list -->
<button
type="button"
@@ -219,8 +222,7 @@
);
}}
class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500"
title={`Remove this file from list of videos:\n${hosted_file_obj.filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}... Hosted ID: ${hosted_file_obj.hosted_file_id}`}
>
title={`Remove this file from list of videos:\n${hosted_file_obj.filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}... Hosted ID: ${hosted_file_obj.hosted_file_id}`}>
<MinusCircle size="1em" class="m-1" />
<span class="">Remove</span>
</button>
@@ -234,60 +236,49 @@
variant="tonal"
classes="novi_btn btn-sm lg:btn-md min-w-72 lg:min-w-96 !justify-start"
show_divider={true}
max_filename={30}
/>
max_filename={30} />
</div>
<span
>{ae_util.shorten_filename({
filename: hosted_file_obj?.filename,
max_length: 30
})}</span
>
})}</span>
<span>
<span class="text-sm font-bold"> File ID: </span>
{hosted_file_obj.hosted_file_id}</span
>
{hosted_file_obj.hosted_file_id}</span>
<span>
<span class="text-sm font-bold"> Type: </span>
{hosted_file_obj.extension}</span
>
{hosted_file_obj.extension}</span>
<!-- <span>{hosted_file_obj.filename}</span> -->
</div>
<form
onsubmit={prevent_default(handle_clip_video)}
class="{class_li_default} {class_li}"
>
class="{class_li_default} {class_li}">
<!-- {$ae_sess?.files[hosted_file_obj?.hosted_file_id ?? 'obj'].submit_status ?? 'not set'} -->
<input
type="hidden"
name="hosted_file_id"
value={hosted_file_obj.hosted_file_id}
/>
value={hosted_file_obj.hosted_file_id} />
<div
class="flex flex-row gap-1 justify-center items-center w-full"
>
<span class="text-xs font-bold w-32">New Filename:</span>
class="flex w-full flex-row items-center justify-center gap-1">
<span class="w-32 text-xs font-bold">New Filename:</span>
<input
type="text"
class="input w-full text-sm variant-filled-surface"
class="input variant-filled-surface w-full text-sm"
name="new_filename"
value={hosted_file_obj.filename}
/>
value={hosted_file_obj.filename} />
</div>
<div
class="max-w-(--breakpoint-sm) flex flex-row gap-1 justify-center items-center w-full"
>
class="flex w-full max-w-(--breakpoint-sm) flex-row items-center justify-center gap-1">
<label
class="label w-48"
title="The start time of the clip. This is the time in the video where the clip will start. You may need to subtract a few seconds to get the exact start time."
>
title="The start time of the clip. This is the time in the video where the clip will start. You may need to subtract a few seconds to get the exact start time.">
<span class="text-xs font-bold"
>Start time (HH:MM:SS)</span
>
>Start time (HH:MM:SS)</span>
<input
type="text"
name="start_time"
@@ -300,17 +291,14 @@
].start_time
: '00:00:00'}
placeholder="HH:MM:SS (00:01:30)"
class="input w-32 variant-filled-surface"
/>
class="input variant-filled-surface w-32" />
</label>
<label
class="label w-48"
title="The end time of the clip. This is the time in the video where the clip will end. You may need to add a few seconds to get the exact end time."
>
title="The end time of the clip. This is the time in the video where the clip will end. You may need to add a few seconds to get the exact end time.">
<span class="text-xs font-bold"
>End time (HH:MM:SS)</span
>
>End time (HH:MM:SS)</span>
<input
type="text"
name="end_time"
@@ -323,14 +311,12 @@
].end_time
: '00:45:59'}
placeholder="HH:MM:SS (01:05:25)"
class="input w-32 variant-filled-surface"
/>
class="input variant-filled-surface w-32" />
</label>
<span
class="flex flex-col gap-1 items-center justify-center"
title="Re-encode the video file? This does cause some minor quality loss. Re-encoding is useful if the audio or video seems to be chopped off at the beginning or end of the clip. It can also help with partially corrupted files."
>
class="flex flex-col items-center justify-center gap-1"
title="Re-encode the video file? This does cause some minor quality loss. Re-encoding is useful if the audio or video seems to be chopped off at the beginning or end of the clip. It can also help with partially corrupted files.">
<span class="text-xs font-bold"> Re-encode? </span>
<label class="inline-block">
<input
@@ -338,8 +324,7 @@
name="reencode"
value="true"
class="radio"
checked
/>
checked />
True
</label>
<label class="inline-block">
@@ -347,16 +332,14 @@
type="radio"
name="reencode"
value="false"
class="radio"
/>
class="radio" />
False
</label>
</span>
<span
class="flex flex-col gap-1 items-center justify-center"
title="Scale the video file down to 1920x1080? This does cause some minor quality loss. Re-encoding is useful if the audio or video seems to be chopped off at the beginning or end of the clip. It can also help with partially corrupted files."
>
class="flex flex-col items-center justify-center gap-1"
title="Scale the video file down to 1920x1080? This does cause some minor quality loss. Re-encoding is useful if the audio or video seems to be chopped off at the beginning or end of the clip. It can also help with partially corrupted files.">
<span class="text-xs font-bold"> Scale down? </span>
<label class="inline-block">
<input
@@ -364,8 +347,7 @@
name="scale_down"
value="true"
class="radio"
checked
/>
checked />
True
</label>
<label class="inline-block">
@@ -373,8 +355,7 @@
type="radio"
name="scale_down"
value="false"
class="radio"
/>
class="radio" />
False
</label>
</span>
@@ -382,9 +363,8 @@
<button
type="submit"
class="btn btn-lg btn-primary preset-tonal-primary border border-primary-500 hover:preset-filled-primary-500 transition-colors"
disabled={submit_status == 'clipping'}
>
class="btn btn-lg btn-primary preset-tonal-primary border-primary-500 hover:preset-filled-primary-500 border transition-colors"
disabled={submit_status == 'clipping'}>
<!-- {#await ae_promises[hosted_file_id]} -->
{#if $ae_loc.files.processed_file_kv[hosted_file_id] && $ae_loc.files.processed_file_kv[hosted_file_id].submit_status == 'clipping'}
<LoaderCircle size="1em" class="m-1 animate-spin" />
@@ -407,8 +387,7 @@
{#await ae_promises[hosted_file_id]}
<LoaderCircle size="1em" class="m-1 animate-spin" />
<span class="highlight"
>Processing... This may take a few minutes.</span
>
>Processing... This may take a few minutes.</span>
{:then}
{#if ae_promises[hosted_file_id]}
<Download size="1em" /> Ready to download below!

View File

@@ -1,48 +1,48 @@
<script lang="ts">
// Imports
// Import components and elements
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
// Imports
// Import components and elements
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
// Exports
// Exports
// export let hosted_file_id_li: string[] = [];
// export let hosted_file_obj_li: any[] = [];
// export let hosted_file_id_li: string[] = [];
// export let hosted_file_obj_li: any[] = [];
interface Props {
log_lvl?: number;
// export let hosted_file_obj_kv: key_val = {};
video_clip_file_kv?: key_val;
class_li_default?: string;
class_li?: string;
link_to_type: string;
link_to_id: string;
}
interface Props {
log_lvl?: number;
// export let hosted_file_obj_kv: key_val = {};
video_clip_file_kv?: key_val;
class_li_default?: string;
class_li?: string;
link_to_type: string;
link_to_id: string;
}
let {
log_lvl = 0,
video_clip_file_kv = $bindable({}),
class_li_default = 'flex flex-row flex-wrap gap-2 items-center justify-center w-full max-w-2xl p-2 mx-auto my-1 border border-surface-500/20 rounded-lg preset-tonal-surface',
class_li = '',
link_to_type,
link_to_id
}: Props = $props();
let {
log_lvl = 0,
video_clip_file_kv = $bindable({}),
class_li_default = 'flex flex-row flex-wrap gap-2 items-center justify-center w-full max-w-2xl p-2 mx-auto my-1 border border-surface-500/20 rounded-lg preset-tonal-surface',
class_li = '',
link_to_type,
link_to_id
}: Props = $props();
let ae_promises: key_val = $state({});
let ae_promises: key_val = $state({});
</script>
<h3 class="h3">{Object.entries(video_clip_file_kv).length}× files clipped</h3>
@@ -54,7 +54,6 @@
max_filename={30}
classes="btn btn-sm lg:btn-md preset-tonal-primary hover:preset-filled-primary-500 min-w-72 lg:min-w-96"
linked_to_type={link_to_type}
linked_to_id={link_to_id}
/>
linked_to_id={link_to_id} />
{/each}
</div>

View File

@@ -1,243 +1,275 @@
<script lang="ts">
// *** Import Svelte specific
import * as Lucide from 'lucide-svelte';
import { fade } from 'svelte/transition';
// *** Import Svelte specific
import * as Lucide from 'lucide-svelte';
import { fade } from 'svelte/transition';
// *** Import Aether specific variables and functions
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { download_ae_obj_id__hosted_file } from '$lib/ae_core/core__hosted_files';
import {
ae_loc,
ae_sess,
ae_api
} from '$lib/stores/ae_stores';
// *** Import Aether specific variables and functions
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { download_ae_obj_id__hosted_file } from '$lib/ae_core/core__hosted_files';
import { ae_loc, ae_sess, ae_api } from '$lib/stores/ae_stores';
interface Props {
log_lvl?: number;
hosted_file_id: null | string;
hosted_file_obj: null | key_val;
filename?: null | string;
max_filename?: number;
auto_download?: boolean;
linked_to_type?: null | string;
linked_to_id?: null | string;
download_complete?: null | boolean;
download_percent?: number;
download_status_msg?: string;
variant?: 'tonal' | 'filled' | 'outline' | 'ghost';
color?: 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';
show_divider?: boolean;
show_direct_download?: boolean;
require_auth?: boolean;
classes?: string;
click?: () => void | Promise<any>;
label?: import('svelte').Snippet;
interface Props {
log_lvl?: number;
hosted_file_id: null | string;
hosted_file_obj: null | key_val;
filename?: null | string;
max_filename?: number;
auto_download?: boolean;
linked_to_type?: null | string;
linked_to_id?: null | string;
download_complete?: null | boolean;
download_percent?: number;
download_status_msg?: string;
variant?: 'tonal' | 'filled' | 'outline' | 'ghost';
color?:
| 'primary'
| 'secondary'
| 'tertiary'
| 'success'
| 'warning'
| 'error'
| 'surface';
show_divider?: boolean;
show_direct_download?: boolean;
require_auth?: boolean;
classes?: string;
click?: () => void | Promise<any>;
label?: import('svelte').Snippet;
}
let {
log_lvl = 0,
hosted_file_id,
hosted_file_obj,
filename = $bindable(null),
max_filename = $bindable(30),
auto_download = true,
linked_to_type = $bindable(null),
linked_to_id = $bindable(null),
download_complete = $bindable(),
download_percent = $bindable(),
download_status_msg = $bindable('Not started'),
variant = 'tonal',
color = 'primary',
show_divider = true,
show_direct_download = false,
require_auth = true,
classes = '',
click,
label
}: Props = $props();
// Map variant/color to classes using literal strings so Tailwind can find them
const color_map: Record<string, Record<string, string>> = {
primary: {
tonal: 'preset-tonal-primary border border-primary-500/30 hover:preset-filled-primary-500',
filled: 'preset-filled-primary-500 hover:preset-filled-primary-600',
outline: 'border border-primary-500 hover:preset-tonal-primary',
ghost: 'hover:preset-tonal-primary'
},
secondary: {
tonal: 'preset-tonal-secondary border border-secondary-500/30 hover:preset-filled-secondary-500',
filled: 'preset-filled-secondary-500 hover:preset-filled-secondary-600',
outline: 'border border-secondary-500 hover:preset-tonal-secondary',
ghost: 'hover:preset-tonal-secondary'
},
tertiary: {
tonal: 'preset-tonal-tertiary border border-tertiary-500/30 hover:preset-filled-tertiary-500',
filled: 'preset-filled-tertiary-500 hover:preset-filled-tertiary-600',
outline: 'border border-tertiary-500 hover:preset-tonal-tertiary',
ghost: 'hover:preset-tonal-tertiary'
},
success: {
tonal: 'preset-tonal-success border border-success-500/30 hover:preset-filled-success-500',
filled: 'preset-filled-success-500 hover:preset-filled-success-600',
outline: 'border border-success-500 hover:preset-tonal-success',
ghost: 'hover:preset-tonal-success'
},
warning: {
tonal: 'preset-tonal-warning border border-warning-500/30 hover:preset-filled-warning-500',
filled: 'preset-filled-warning-500 hover:preset-filled-warning-600',
outline: 'border border-warning-500 hover:preset-tonal-warning',
ghost: 'hover:preset-tonal-warning'
},
error: {
tonal: 'preset-tonal-error border border-error-500/30 hover:preset-filled-error-500',
filled: 'preset-filled-error-500 hover:preset-filled-error-600',
outline: 'border border-error-500 hover:preset-tonal-error',
ghost: 'hover:preset-tonal-error'
},
surface: {
tonal: 'preset-tonal-surface border border-surface-500/30 hover:preset-filled-surface-500',
filled: 'preset-filled-surface-500 hover:preset-filled-surface-600',
outline: 'border border-surface-500 hover:preset-tonal-surface',
ghost: 'hover:preset-tonal-surface'
}
};
let variant_classes = $derived.by(() => {
const base =
'btn btn-sm lg:btn-md min-w-48 transition-all overflow-hidden px-3';
const style = color_map[color]?.[variant] || color_map.primary.tonal;
return `${base} ${style} ${classes}`.trim();
});
let show_filename_view = $state(true);
let status_interval: any;
$effect(() => {
if (log_lvl) {
console.log(
`ae_comp__hosted_files_download_button.svelte hosted_file_id=${hosted_file_id}`,
hosted_file_obj
);
}
});
let ae_promises: key_val = $state({});
$effect(() => {
const file_id =
hosted_file_obj?.id ||
hosted_file_obj?.hosted_file_id ||
hosted_file_id;
if (file_id && $ae_sess?.api_download_kv[file_id]?.percent_completed) {
download_percent = $ae_sess.api_download_kv[file_id].percent_completed;
}
});
// Reactive timer to alternate views during active download
$effect(() => {
const file_id =
hosted_file_obj?.id ||
hosted_file_obj?.hosted_file_id ||
hosted_file_id;
const is_actively_downloading =
ae_promises[file_id] && download_complete === undefined;
if (is_actively_downloading) {
if (!status_interval) {
status_interval = setInterval(() => {
show_filename_view = !show_filename_view;
}, 3000);
}
} else {
if (status_interval) {
clearInterval(status_interval);
status_interval = null;
}
show_filename_view = true; // Default view when not downloading
}
let {
log_lvl = 0,
hosted_file_id,
hosted_file_obj,
filename = $bindable(null),
max_filename = $bindable(30),
auto_download = true,
linked_to_type = $bindable(null),
linked_to_id = $bindable(null),
download_complete = $bindable(),
download_percent = $bindable(),
download_status_msg = $bindable('Not started'),
variant = 'tonal',
color = 'primary',
show_divider = true,
show_direct_download = false,
require_auth = true,
classes = '',
click,
label
}: Props = $props();
// Map variant/color to classes using literal strings so Tailwind can find them
const color_map: Record<string, Record<string, string>> = {
primary: {
tonal: 'preset-tonal-primary border border-primary-500/30 hover:preset-filled-primary-500',
filled: 'preset-filled-primary-500 hover:preset-filled-primary-600',
outline: 'border border-primary-500 hover:preset-tonal-primary',
ghost: 'hover:preset-tonal-primary'
},
secondary: {
tonal: 'preset-tonal-secondary border border-secondary-500/30 hover:preset-filled-secondary-500',
filled: 'preset-filled-secondary-500 hover:preset-filled-secondary-600',
outline: 'border border-secondary-500 hover:preset-tonal-secondary',
ghost: 'hover:preset-tonal-secondary'
},
tertiary: {
tonal: 'preset-tonal-tertiary border border-tertiary-500/30 hover:preset-filled-tertiary-500',
filled: 'preset-filled-tertiary-500 hover:preset-filled-tertiary-600',
outline: 'border border-tertiary-500 hover:preset-tonal-tertiary',
ghost: 'hover:preset-tonal-tertiary'
},
success: {
tonal: 'preset-tonal-success border border-success-500/30 hover:preset-filled-success-500',
filled: 'preset-filled-success-500 hover:preset-filled-success-600',
outline: 'border border-success-500 hover:preset-tonal-success',
ghost: 'hover:preset-tonal-success'
},
warning: {
tonal: 'preset-tonal-warning border border-warning-500/30 hover:preset-filled-warning-500',
filled: 'preset-filled-warning-500 hover:preset-filled-warning-600',
outline: 'border border-warning-500 hover:preset-tonal-warning',
ghost: 'hover:preset-tonal-warning'
},
error: {
tonal: 'preset-tonal-error border border-error-500/30 hover:preset-filled-error-500',
filled: 'preset-filled-error-500 hover:preset-filled-error-600',
outline: 'border border-error-500 hover:preset-tonal-error',
ghost: 'hover:preset-tonal-error'
},
surface: {
tonal: 'preset-tonal-surface border border-surface-500/30 hover:preset-filled-surface-500',
filled: 'preset-filled-surface-500 hover:preset-filled-surface-600',
outline: 'border border-surface-500 hover:preset-tonal-surface',
ghost: 'hover:preset-tonal-surface'
return () => {
if (status_interval) {
clearInterval(status_interval);
status_interval = null;
}
};
});
let variant_classes = $derived.by(() => {
const base = 'btn btn-sm lg:btn-md min-w-48 transition-all overflow-hidden px-3';
const style = color_map[color]?.[variant] || color_map.primary.tonal;
return `${base} ${style} ${classes}`.trim();
});
let show_filename_view = $state(true);
let status_interval: any;
$effect(() => {
if (log_lvl) {
console.log(
`ae_comp__hosted_files_download_button.svelte hosted_file_id=${hosted_file_id}`,
hosted_file_obj
);
}
});
let ae_promises: key_val = $state({});
$effect(() => {
const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id;
if (file_id && $ae_sess?.api_download_kv[file_id]?.percent_completed) {
download_percent =
$ae_sess.api_download_kv[file_id].percent_completed;
}
});
// Reactive timer to alternate views during active download
$effect(() => {
const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id;
const is_actively_downloading = ae_promises[file_id] && download_complete === undefined;
if (is_actively_downloading) {
if (!status_interval) {
status_interval = setInterval(() => {
show_filename_view = !show_filename_view;
}, 3000);
}
} else {
if (status_interval) {
clearInterval(status_interval);
status_interval = null;
}
show_filename_view = true; // Default view when not downloading
}
return () => {
if (status_interval) {
clearInterval(status_interval);
status_interval = null;
}
};
});
let final_filename = $derived(filename ?? hosted_file_obj?.filename ?? 'unknown');
let shortened_filename = $derived(ae_util.shorten_filename({
let final_filename = $derived(
filename ?? hosted_file_obj?.filename ?? 'unknown'
);
let shortened_filename = $derived(
ae_util.shorten_filename({
filename: final_filename,
max_length: max_filename
}));
})
);
let direct_download_url = $derived.by(() => {
if (!show_direct_download || !hosted_file_obj) return '';
// IMPORTANT: For Direct Link Mode, we MUST use the V3 Action endpoint to support Random String IDs.
// Legacy endpoints often expect integer IDs and will return 404 for string IDs.
const file_id = hosted_file_obj.event_file_id || hosted_file_obj.hosted_file_id || hosted_file_id;
const obj_type_path = hosted_file_obj.event_file_id ? 'event_file' : 'hosted_file';
return `${$ae_api.base_url}/v3/action/${obj_type_path}/${file_id}/download?filename=${ae_util.clean_filename(final_filename)}&key=${$ae_api.account_id}`;
});
let direct_download_url = $derived.by(() => {
if (!show_direct_download || !hosted_file_obj) return '';
// IMPORTANT: For Direct Link Mode, we MUST use the V3 Action endpoint to support Random String IDs.
// Legacy endpoints often expect integer IDs and will return 404 for string IDs.
const file_id =
hosted_file_obj.event_file_id ||
hosted_file_obj.hosted_file_id ||
hosted_file_id;
const obj_type_path = hosted_file_obj.event_file_id
? 'event_file'
: 'hosted_file';
return `${$ae_api.base_url}/v3/action/${obj_type_path}/${file_id}/download?filename=${ae_util.clean_filename(final_filename)}&key=${$ae_api.account_id}`;
});
async function handle_click() {
const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id;
download_complete = undefined;
download_status_msg = 'Downloading...';
async function handle_click() {
const file_id =
hosted_file_obj?.id ||
hosted_file_obj?.hosted_file_id ||
hosted_file_id;
download_complete = undefined;
download_status_msg = 'Downloading...';
if (click) {
const result = click();
// If the override returns a promise, track it so the UI shows progress
if (result instanceof Promise) {
ae_promises[file_id] = result;
}
return;
if (click) {
const result = click();
// If the override returns a promise, track it so the UI shows progress
if (result instanceof Promise) {
ae_promises[file_id] = result;
}
ae_promises[file_id] = download_ae_obj_id__hosted_file({
api_cfg: $ae_api,
hosted_file_id: file_id,
return_file: true,
filename: final_filename,
auto_download: auto_download,
log_lvl: log_lvl
})
.then((result) => {
if (result === null) {
console.log('File not found (404)');
download_complete = null;
download_status_msg = 'File not found';
} else if (result === false) {
console.log(
'Possible error with API server (check network and server status)'
);
download_complete = false;
download_status_msg = 'Failed to download';
} else {
// console.log('File found and downloaded');
download_complete = true;
download_status_msg = 'File downloaded';
}
return result;
});
return;
}
ae_promises[file_id] = download_ae_obj_id__hosted_file({
api_cfg: $ae_api,
hosted_file_id: file_id,
return_file: true,
filename: final_filename,
auto_download: auto_download,
log_lvl: log_lvl
}).then((result) => {
if (result === null) {
console.log('File not found (404)');
download_complete = null;
download_status_msg = 'File not found';
} else if (result === false) {
console.log(
'Possible error with API server (check network and server status)'
);
download_complete = false;
download_status_msg = 'Failed to download';
} else {
// console.log('File found and downloaded');
download_complete = true;
download_status_msg = 'File downloaded';
}
return result;
});
}
</script>
{#snippet content()}
{@const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id}
{@const file_id =
hosted_file_obj?.id ||
hosted_file_obj?.hosted_file_id ||
hosted_file_id}
{#await ae_promises[file_id]}
<div class="flex items-center w-full min-h-[1.5rem]">
<div class="flex min-h-[1.5rem] w-full items-center">
<div
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
>
class="flex shrink-0 items-center pr-2 {show_divider
? 'border-surface-500/30 mr-2 border-r'
: ''}">
<Lucide.LoaderCircle class="animate-spin" size={18} />
</div>
<div class="grow relative text-left h-full">
<div class="relative h-full grow text-left">
{#if show_filename_view}
<div in:fade={{ duration: 250 }} out:fade={{ duration: 250 }} class="flex items-center h-full">
<div
in:fade={{ duration: 250 }}
out:fade={{ duration: 250 }}
class="flex h-full items-center">
<span class="truncate">
{shortened_filename}
</span>
</div>
{:else}
<div in:fade={{ duration: 250 }} out:fade={{ duration: 250 }} class="absolute inset-0 flex items-center h-full">
<div
in:fade={{ duration: 250 }}
out:fade={{ duration: 250 }}
class="absolute inset-0 flex h-full items-center">
<span class="font-bold whitespace-nowrap">
Downloading:
{#if $ae_sess.api_download_kv[file_id]}
{$ae_sess.api_download_kv[file_id].percent_completed}%
{$ae_sess.api_download_kv[file_id]
.percent_completed}%
{:else}
...
{/if}
@@ -250,18 +282,22 @@
{#if label}
{@render label()}
{:else}
{@const IconComp = ae_util.file_extension_icon_lucide(hosted_file_obj?.extension)}
<div class="flex items-center w-full">
{@const IconComp = ae_util.file_extension_icon_lucide(
hosted_file_obj?.extension
)}
<div class="flex w-full items-center">
<div
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
>
class="flex shrink-0 items-center pr-2 {show_divider
? 'border-surface-500/30 mr-2 border-r'
: ''}">
<IconComp size={18} />
</div>
<span class="grow truncate text-left">
{shortened_filename}
</span>
{#if hosted_file_obj?.file_purpose || hosted_file_obj?.group}
<span class="badge preset-tonal-success ml-2 text-[10px] uppercase font-bold shrink-0">
<span
class="badge preset-tonal-success ml-2 shrink-0 text-[10px] font-bold uppercase">
{hosted_file_obj.file_purpose || hosted_file_obj.group}
</span>
{/if}
@@ -270,22 +306,25 @@
{/await}
{#if download_complete === null}
<span class="text-red-800 dark:text-red-200 ml-2 whitespace-nowrap">File not found</span>
<span class="ml-2 whitespace-nowrap text-red-800 dark:text-red-200"
>File not found</span>
{:else if download_complete === false}
<span class="text-red-800 dark:text-red-200 ml-2 whitespace-nowrap text-xs">Failed!</span>
<span
class="ml-2 text-xs whitespace-nowrap text-red-800 dark:text-red-200"
>Failed!</span>
{/if}
{/snippet}
{#if hosted_file_id && hosted_file_obj}
{@const file_id = hosted_file_obj.id || hosted_file_obj.hosted_file_id || hosted_file_id}
{@const file_id =
hosted_file_obj.id || hosted_file_obj.hosted_file_id || hosted_file_id}
{#if show_direct_download}
<a
href={direct_download_url}
download={ae_util.clean_filename(final_filename)}
class={variant_classes}
title={`Direct download (V3 Action):\n${final_filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...\nHosted ID: ${file_id}`}
>
title={`Direct download (V3 Action):\n${final_filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...\nHosted ID: ${file_id}`}>
{@render content()}
</a>
{:else}
@@ -294,20 +333,24 @@
disabled={require_auth && !$ae_loc.authenticated_access}
class={variant_classes}
onclick={handle_click}
title={`Download this file:\n${final_filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...\nHosted ID: ${file_id}\n Linked to: ${linked_to_type} ID: ${linked_to_id}`}
>
title={`Download this file:\n${final_filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...\nHosted ID: ${file_id}\n Linked to: ${linked_to_type} ID: ${linked_to_id}`}>
{@render content()}
</button>
{/if}
{:else}
<button type="button" disabled class={variant_classes} title="No file selected">
<div class="flex items-center w-full">
<button
type="button"
disabled
class={variant_classes}
title="No file selected">
<div class="flex w-full items-center">
<div
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
>
class="flex shrink-0 items-center pr-2 {show_divider
? 'border-surface-500/30 mr-2 border-r'
: ''}">
<Lucide.FileX size={18} />
</div>
<span class="grow text-left"> No file info </span>
</div>
</button>
{/if}
{/if}

View File

@@ -1,281 +1,286 @@
<script lang="ts">
// untrack import removed — task_id sync now uses direct $effect (no untrack needed)
// Imports
// Import components and elements
import * as Lucide from 'lucide-svelte';
import Element_input_files_tbl from '$lib/elements/element_input_files_tbl.svelte';
// untrack import removed — task_id sync now uses direct $effect (no untrack needed)
// Imports
// Import components and elements
import * as Lucide from 'lucide-svelte';
import Element_input_files_tbl from '$lib/elements/element_input_files_tbl.svelte';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
// Import storage, functions, and libraries
import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
// Exports
// Exports
interface Props {
log_lvl?: number;
// Expecting these for link_to_type: 'event', 'event_location', 'archive_content', etc
link_to_type: string;
link_to_id: string;
input_name?: string;
multiple?: boolean;
required?: boolean;
accept?: string;
class_li_default?: string;
class_li?: string;
input_class_li?: string[];
table_class_li?: string[];
upload_complete?: boolean;
submit_status?: null | string;
hosted_file_id_li?: string[];
hosted_file_obj_li?: any[];
hosted_file_obj_kv?: key_val;
label?: import('svelte').Snippet;
interface Props {
log_lvl?: number;
// Expecting these for link_to_type: 'event', 'event_location', 'archive_content', etc
link_to_type: string;
link_to_id: string;
input_name?: string;
multiple?: boolean;
required?: boolean;
accept?: string;
class_li_default?: string;
class_li?: string;
input_class_li?: string[];
table_class_li?: string[];
upload_complete?: boolean;
submit_status?: null | string;
hosted_file_id_li?: string[];
hosted_file_obj_li?: any[];
hosted_file_obj_kv?: key_val;
label?: import('svelte').Snippet;
}
let {
log_lvl = 0,
link_to_type,
link_to_id,
input_name = 'file_list',
multiple = true,
required = true,
accept = 'audio/*, image/*, video/*, .bak, .cfg, .css, .csv, .doc, .docx, .gz, .htm, .html, .ini, .iso, .j2, .json, .key, .keynote, .md, .pdf, .ppt, .pptx, .rar, .rtf, .sql, .svelte, ttf, .txt, .xls, .xlsx, .xz, .zip, .bin, .dmg, .exe, .js, .msi, .php, .py, .sh',
class_li_default = 'flex flex-col gap-1 items-center justify-center w-full max-w-2xl mx-auto my-1',
class_li = '',
input_class_li = ['file_drop_area'],
table_class_li = ['table', 'table-sm', 'table-striped', '', 'text-sm'],
upload_complete = $bindable(false),
submit_status = $bindable(null),
hosted_file_id_li = $bindable([]),
hosted_file_obj_li = $bindable([]),
hosted_file_obj_kv = $bindable({}),
label
}: Props = $props();
// Local Variables
let task_id: string = $state('');
let input_file_list: any = $state(null);
let ae_promises: key_val = $state({}); // Promise<any>;
let ae_triggers: key_val = {};
let input_element_id = 'ae_comp__hosted_files_upload__input';
$effect(() => {
if (log_lvl) {
console.log(`*** ae_comp__hosted_files_upload.svelte ***`);
console.log(`link_to_type: ${link_to_type} link_to_id: ${link_to_id}`);
}
});
$effect(() => {
// Sync task_id with link_to_id prop so it resets when navigating to a different object.
task_id = link_to_id;
});
// *** Functions and Logic
async function handle_submit_form_files(event: SubmitEvent) {
console.log('*** handle_submit_form() ***');
event.preventDefault();
if (!event) {
return;
}
let {
log_lvl = 0,
link_to_type,
link_to_id,
input_name = 'file_list',
multiple = true,
required = true,
accept = 'audio/*, image/*, video/*, .bak, .cfg, .css, .csv, .doc, .docx, .gz, .htm, .html, .ini, .iso, .j2, .json, .key, .keynote, .md, .pdf, .ppt, .pptx, .rar, .rtf, .sql, .svelte, ttf, .txt, .xls, .xlsx, .xz, .zip, .bin, .dmg, .exe, .js, .msi, .php, .py, .sh',
class_li_default = 'flex flex-col gap-1 items-center justify-center w-full max-w-2xl mx-auto my-1',
class_li = '',
input_class_li = ['file_drop_area'],
table_class_li = ['table', 'table-sm', 'table-striped', '', 'text-sm'],
upload_complete = $bindable(false),
submit_status = $bindable(null),
hosted_file_id_li = $bindable([]),
hosted_file_obj_li = $bindable([]),
hosted_file_obj_kv = $bindable({}),
label
}: Props = $props();
$ae_sess.files.disable_submit__hosted_file_obj = true;
$ae_sess.files.submit_status = 'saving';
submit_status = 'saving';
upload_complete = false;
// Local Variables
let task_id: string = $state('');
let input_file_list: any = $state(null);
let ae_promises: key_val = $state({}); // Promise<any>;
let ae_triggers: key_val = {};
hosted_file_id_li = [];
hosted_file_obj_li = [];
hosted_file_obj_kv = {};
let input_element_id = 'ae_comp__hosted_files_upload__input';
let hosted_file_results;
$effect(() => {
if (log_lvl) {
console.log(`*** ae_comp__hosted_files_upload.svelte ***`);
console.log(`link_to_type: ${link_to_type} link_to_id: ${link_to_id}`);
}
});
const target = event.currentTarget as HTMLFormElement;
const file_input = target
? (target[input_element_id] as HTMLInputElement)
: null;
$effect(() => {
// Sync task_id with link_to_id prop so it resets when navigating to a different object.
task_id = link_to_id;
});
if (
target &&
file_input &&
file_input.files &&
file_input.files.length > 0
) {
task_id = link_to_id; // Ideally this should be the file hash, but we may be uploading multiple files at once. This should be done with a loop instead?
// *** Functions and Logic
async function handle_submit_form_files(event: SubmitEvent) {
console.log('*** handle_submit_form() ***');
event.preventDefault();
// Loop through each file and upload them individually in event.target[input_element_id].files
// The task_id should be the file hash.
// processed_file_list[i] has the file hash_sha256, hash_sha256_match, warnings, uploaded, uploaded_bytes, filename, and file_size_bytes.
for (let i = 0; i < file_input.files.length; i++) {
let tmp_file = file_input.files[i];
if (!event) {
return;
}
task_id = $ae_sess.files.processed_file_list[i].hash_sha256;
$ae_sess.files.disable_submit__hosted_file_obj = true;
$ae_sess.files.submit_status = 'saving';
submit_status = 'saving';
upload_complete = false;
hosted_file_id_li = [];
hosted_file_obj_li = [];
hosted_file_obj_kv = {};
let hosted_file_results;
const target = event.currentTarget as HTMLFormElement;
const file_input = target ? (target[input_element_id] as HTMLInputElement) : null;
if (
target &&
file_input &&
file_input.files &&
file_input.files.length > 0
) {
task_id = link_to_id; // Ideally this should be the file hash, but we may be uploading multiple files at once. This should be done with a loop instead?
// Loop through each file and upload them individually in event.target[input_element_id].files
// The task_id should be the file hash.
// processed_file_list[i] has the file hash_sha256, hash_sha256_match, warnings, uploaded, uploaded_bytes, filename, and file_size_bytes.
for (let i = 0; i < file_input.files.length; i++) {
let tmp_file = file_input.files[i];
task_id = $ae_sess.files.processed_file_list[i].hash_sha256;
// hosted_file_results = await handle_input_upload_files([tmp_file], task_id);
hosted_file_results = await handle_input_upload_files({
input_upload_files: [tmp_file],
task_id: task_id
});
if (hosted_file_results) {
console.log(`hosted_file_results:`, hosted_file_results);
} else {
console.log(`hosted_file_results:`, hosted_file_results);
}
}
// hosted_file_results = await handle_input_upload_files(event.target[input_element_id].files, task_id);
$ae_sess.files.processed_file_list = [];
$ae_sess = $ae_sess; // Is this needed? 2025-03-17
target.reset();
// await tick();
if (log_lvl) {
console.log(`hosted_file_id_li: ${hosted_file_id_li}`, hosted_file_id_li);
} else if (log_lvl > 1) {
console.log('hosted_file_results:', hosted_file_results);
}
}
$ae_sess.files.disable_submit__hosted_file_obj = false;
$ae_sess.files.submit_status = 'saved';
submit_status = 'saved';
upload_complete = true;
}
async function handle_input_upload_files({
input_upload_files,
task_id
}: {
input_upload_files: any[];
task_id: string;
}) {
console.log('*** handle_input_upload_files() ***');
const form_data = new FormData();
form_data.append('account_id', $ae_loc.account_id);
form_data.append('link_to_type', link_to_type);
form_data.append('link_to_id', link_to_id);
for (let i = 0; i < input_upload_files.length; i++) {
form_data.append(`file_list`, input_upload_files[i]);
}
// hash_sha256, uploaded, uploaded_bytes
// $ae_sess.files.processed_file_list[i] = {
// ...$ae_sess.files.processed_file_list[i],
// uploaded: $ae_sess.api_upload_kv[link_to_id].percent_completed,
// uploaded_bytes: $ae_sess.api_upload_kv[link_to_id].uploaded_bytes,
// };
let params = null;
let endpoint = '/hosted_file/upload_files';
console.log(form_data);
params = null;
// 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 = api
.post_object({
api_cfg: $ae_api,
endpoint: endpoint,
// params: params,
form_data: form_data,
task_id: task_id,
log_lvl: log_lvl
// retry_count: 1,
})
.then(async function (result) {
// WARNING!!!! ONLY ONE FILE IS EXPECTED TO BE UPLOADED AT A TIME!!!
// 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;
console.log(result[x]);
let hosted_file_obj = result[x];
let hosted_file_id = hosted_file_obj.hosted_file_id;
hosted_file_id_li.push(hosted_file_id);
hosted_file_obj_li.push(hosted_file_obj);
let hosted_file_data: key_val = {};
hosted_file_data['id'] = hosted_file_id; // Same as the hosted_file_id
hosted_file_data['hosted_file_id'] = hosted_file_id;
hosted_file_data['for_type'] = link_to_type;
hosted_file_data['for_id'] = link_to_id;
hosted_file_data['hash_sha256'] = hosted_file_obj.hash_sha256;
hosted_file_data['filename'] = hosted_file_obj.filename;
hosted_file_data['extension'] = hosted_file_obj.extension;
hosted_file_data['content_type'] = hosted_file_obj.content_type;
hosted_file_data['size'] = hosted_file_obj.size;
hosted_file_data['enable'] = true;
hosted_file_data['created_on'] = hosted_file_obj.created_on;
hosted_file_data['updated_on'] = hosted_file_obj.updated_on;
console.log(hosted_file_data);
hosted_file_obj_kv[hosted_file_id] = hosted_file_data;
if (log_lvl) {
console.log(`hosted_file_data:`, hosted_file_data);
}
return hosted_file_data;
// $ae_sess.files.new_upload_list[i].uploaded_bytes = 10; // fake 10 bytes at least...
// let event_file_id = await events_func.create_hosted_file_obj_from_hosted_file_async({
// api_cfg: $ae_api,
// hosted_file_id: hosted_file_id,
// data: event_file_data,
// log_lvl: log_lvl
// })
// .then(function (create_result) {
// console.log(create_result); // NOTE: This should be the event_file_id string
// // let event_file_id = create_result;
// return create_result;
// });
// return event_file_id;
})
// .then(function (hosted_file_data) {
// return hosted_file_data;
// })
.catch(function (error: any) {
console.log('Something went wrong.');
console.log(error);
return false;
})
.finally(function () {
$slct_trigger = 'load__hosted_file_obj_li';
// hosted_file_results = await handle_input_upload_files([tmp_file], task_id);
hosted_file_results = await handle_input_upload_files({
input_upload_files: [tmp_file],
task_id: task_id
});
if (log_lvl) {
console.log(`Waiting for upload__hosted_file_obj promise...`);
if (hosted_file_results) {
console.log(`hosted_file_results:`, hosted_file_results);
} else {
console.log(`hosted_file_results:`, hosted_file_results);
}
}
let hosted_file_result = ae_promises.upload__hosted_file_obj;
// hosted_file_results = await handle_input_upload_files(event.target[input_element_id].files, task_id);
$ae_sess.files.processed_file_list = [];
$ae_sess = $ae_sess; // Is this needed? 2025-03-17
target.reset();
// await tick();
return hosted_file_result;
if (log_lvl) {
console.log(
`hosted_file_id_li: ${hosted_file_id_li}`,
hosted_file_id_li
);
} else if (log_lvl > 1) {
console.log('hosted_file_results:', hosted_file_results);
}
}
$ae_sess.files.disable_submit__hosted_file_obj = false;
$ae_sess.files.submit_status = 'saved';
submit_status = 'saved';
upload_complete = true;
}
async function handle_input_upload_files({
input_upload_files,
task_id
}: {
input_upload_files: any[];
task_id: string;
}) {
console.log('*** handle_input_upload_files() ***');
const form_data = new FormData();
form_data.append('account_id', $ae_loc.account_id);
form_data.append('link_to_type', link_to_type);
form_data.append('link_to_id', link_to_id);
for (let i = 0; i < input_upload_files.length; i++) {
form_data.append(`file_list`, input_upload_files[i]);
}
// hash_sha256, uploaded, uploaded_bytes
// $ae_sess.files.processed_file_list[i] = {
// ...$ae_sess.files.processed_file_list[i],
// uploaded: $ae_sess.api_upload_kv[link_to_id].percent_completed,
// uploaded_bytes: $ae_sess.api_upload_kv[link_to_id].uploaded_bytes,
// };
let params = null;
let endpoint = '/hosted_file/upload_files';
console.log(form_data);
params = null;
// 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 = api
.post_object({
api_cfg: $ae_api,
endpoint: endpoint,
// params: params,
form_data: form_data,
task_id: task_id,
log_lvl: log_lvl
// retry_count: 1,
})
.then(async function (result) {
// WARNING!!!! ONLY ONE FILE IS EXPECTED TO BE UPLOADED AT A TIME!!!
// 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;
console.log(result[x]);
let hosted_file_obj = result[x];
let hosted_file_id = hosted_file_obj.hosted_file_id;
hosted_file_id_li.push(hosted_file_id);
hosted_file_obj_li.push(hosted_file_obj);
let hosted_file_data: key_val = {};
hosted_file_data['id'] = hosted_file_id; // Same as the hosted_file_id
hosted_file_data['hosted_file_id'] = hosted_file_id;
hosted_file_data['for_type'] = link_to_type;
hosted_file_data['for_id'] = link_to_id;
hosted_file_data['hash_sha256'] = hosted_file_obj.hash_sha256;
hosted_file_data['filename'] = hosted_file_obj.filename;
hosted_file_data['extension'] = hosted_file_obj.extension;
hosted_file_data['content_type'] = hosted_file_obj.content_type;
hosted_file_data['size'] = hosted_file_obj.size;
hosted_file_data['enable'] = true;
hosted_file_data['created_on'] = hosted_file_obj.created_on;
hosted_file_data['updated_on'] = hosted_file_obj.updated_on;
console.log(hosted_file_data);
hosted_file_obj_kv[hosted_file_id] = hosted_file_data;
if (log_lvl) {
console.log(`hosted_file_data:`, hosted_file_data);
}
return hosted_file_data;
// $ae_sess.files.new_upload_list[i].uploaded_bytes = 10; // fake 10 bytes at least...
// let event_file_id = await events_func.create_hosted_file_obj_from_hosted_file_async({
// api_cfg: $ae_api,
// hosted_file_id: hosted_file_id,
// data: event_file_data,
// log_lvl: log_lvl
// })
// .then(function (create_result) {
// console.log(create_result); // NOTE: This should be the event_file_id string
// // let event_file_id = create_result;
// return create_result;
// });
// return event_file_id;
})
// .then(function (hosted_file_data) {
// return hosted_file_data;
// })
.catch(function (error: any) {
console.log('Something went wrong.');
console.log(error);
return false;
})
.finally(function () {
$slct_trigger = 'load__hosted_file_obj_li';
});
if (log_lvl) {
console.log(`Waiting for upload__hosted_file_obj promise...`);
}
let hosted_file_result = ae_promises.upload__hosted_file_obj;
return hosted_file_result;
}
</script>
<!-- class:hidden={!$ae_loc.trusted_access} -->
<form onsubmit={handle_submit_form_files} class="{class_li_default} {class_li}">
{#await ae_promises.upload__hosted_file_obj}
<div class="text-lg flex flex-row gap-1 items-center justify-center">
<Lucide.LoaderCircle class="animate-spin m-1" />
<div class="flex flex-row items-center justify-center gap-1 text-lg">
<Lucide.LoaderCircle class="m-1 animate-spin" />
<span class="">
Uploading
{#if $ae_sess.api_upload_kv[task_id]}
@@ -288,14 +293,14 @@
<label
for="ae_comp__hosted_files_upload__input"
class="svelte_input_file_label text-center"
class:hidden={$ae_sess.files.disable_submit__hosted_file_obj}
>
class:hidden={$ae_sess.files.disable_submit__hosted_file_obj}>
{#if label}{@render label()}{:else}
<div class="flex items-center justify-center gap-2 mb-2">
<div class="mb-2 flex items-center justify-center gap-2">
<Lucide.Upload class="text-primary-500" />
<strong class="preset-tonal-primary px-3 py-1 rounded-full">Select Files</strong>
<strong class="preset-tonal-primary rounded-full px-3 py-1"
>Select Files</strong>
</div>
<span class="text-sm text-gray-600 dark:text-gray-400 italic">
<span class="text-sm text-gray-600 italic dark:text-gray-400">
<strong>Supported formats</strong><br />
(PowerPoint, Keynote, PDF, Media, etc)
</span>
@@ -313,33 +318,30 @@
class="
svelte_input_file_element
file-dropzone-input
px-1
block w-full text-lg
preset-filled-surface-50-950
text-surface-900 dark:text-surface-100
border border-surface-300 dark:border-surface-700 rounded-lg
cursor-pointer
focus:outline-hidden focus:ring-2 focus:ring-primary-500
text-surface-900 dark:text-surface-100 border-surface-300
dark:border-surface-700
focus:ring-primary-500 block
w-full cursor-pointer rounded-lg border
px-1
text-lg focus:ring-2 focus:outline-hidden
{input_class_li.join(' ')}
"
class:hidden={$ae_sess.files.disable_submit__hosted_file_obj}
/>
class:hidden={$ae_sess.files.disable_submit__hosted_file_obj} />
<Element_input_files_tbl
bind:input_file_list
bind:file_list_status={$ae_sess.files.status__file_list}
bind:processed_file_list={$ae_sess.files.processed_file_list}
{table_class_li}
/>
{table_class_li} />
<button
type="submit"
class="btn btn-lg btn-primary preset-tonal-primary border border-primary-500 hover:preset-tonal-success hover:border-success-500 w-54"
class="btn btn-lg btn-primary preset-tonal-primary border-primary-500 hover:preset-tonal-success hover:border-success-500 w-54 border"
disabled={$ae_sess.files.disable_submit__hosted_file_obj ||
$ae_sess.files.status__file_list != 'ready'}
>
$ae_sess.files.status__file_list != 'ready'}>
{#await ae_promises.upload__hosted_file_obj}
<Lucide.LoaderCircle class="animate-spin m-1" />
<Lucide.LoaderCircle class="m-1 animate-spin" />
<span class="">
{#if $ae_sess.api_upload_kv[task_id]}
{$ae_sess.api_upload_kv[task_id].percent_completed}%
@@ -350,9 +352,12 @@
{:then}
<Lucide.UploadCloud class="m-1" size={20} />
<span class="text-sm"> Upload </span>
<span class="grow font-bold ml-2">
<span class="ml-2 grow font-bold">
{#if $ae_sess.files.processed_file_list?.length > 0}
{$ae_sess.files.processed_file_list.length} { $ae_sess.files.processed_file_list.length === 1 ? 'file' : 'files' }
{$ae_sess.files.processed_file_list.length}
{$ae_sess.files.processed_file_list.length === 1
? 'file'
: 'files'}
{:else}
<span class="text-xs"> 0 </span>
{/if}

View File

@@ -1,235 +1,358 @@
<script lang="ts">
import { untrack } from 'svelte';
/**
* AE_Comp_Site_Config_Editor.svelte
* Specialized UI for managing site.cfg_json settings.
* Supports General, AI, Performance, and IDAA-specific configurations.
*/
import { Modal } from 'flowbite-svelte';
import { Brain, CodeXml, ExternalLink, Globe, Mail, Minus, Palette, Plus, Save, ShieldCheck, Timer } from '@lucide/svelte';
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
import { ae_loc } from '$lib/stores/ae_stores';
import { untrack } from 'svelte';
/**
* AE_Comp_Site_Config_Editor.svelte
* Specialized UI for managing site.cfg_json settings.
* Supports General, AI, Performance, and IDAA-specific configurations.
*/
import { Modal } from 'flowbite-svelte';
import {
Brain,
CodeXml,
ExternalLink,
Globe,
Mail,
Minus,
Palette,
Plus,
Save,
ShieldCheck,
Timer
} from '@lucide/svelte';
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
import { ae_loc } from '$lib/stores/ae_stores';
interface Props {
cfg_json: any;
on_save?: () => void;
}
interface Props {
cfg_json: any;
on_save?: () => void;
}
let { cfg_json = $bindable({}), on_save }: Props = $props();
let { cfg_json = $bindable({}), on_save }: Props = $props();
// Ensure we have a valid object (handle strings/nulls)
$effect(() => {
if (typeof cfg_json === 'string') {
try {
cfg_json = JSON.parse(cfg_json);
} catch (e) {
cfg_json = {};
}
// Ensure we have a valid object (handle strings/nulls)
$effect(() => {
if (typeof cfg_json === 'string') {
try {
cfg_json = JSON.parse(cfg_json);
} catch (e) {
cfg_json = {};
}
if (!cfg_json) cfg_json = {};
});
// Internal State
let active_tab: 'visuals' | 'email' | 'ai' | 'refresh' | 'idaa' | 'raw' = $state('visuals');
let raw_json_str = $state('');
// Ensure we have a valid object
}
if (!cfg_json) cfg_json = {};
});
function add_to_list(key: string) {
if (!cfg_json[key]) cfg_json[key] = [];
const val = prompt('Enter Novi UUID:');
if (val) cfg_json[key].push(val);
// Internal State
let active_tab: 'visuals' | 'email' | 'ai' | 'refresh' | 'idaa' | 'raw' =
$state('visuals');
let raw_json_str = $state('');
// Ensure we have a valid object
if (!cfg_json) cfg_json = {};
function add_to_list(key: string) {
if (!cfg_json[key]) cfg_json[key] = [];
const val = prompt('Enter Novi UUID:');
if (val) cfg_json[key].push(val);
}
function remove_from_list(key: string, index: number) {
cfg_json[key].splice(index, 1);
}
// Sync Raw JSON string when entering the tab
$effect(() => {
if (active_tab === 'raw') {
untrack(() => {
raw_json_str = JSON.stringify(cfg_json, null, 2);
});
}
});
function remove_from_list(key: string, index: number) {
cfg_json[key].splice(index, 1);
// Update cfg_json when raw string changes
$effect(() => {
if (active_tab === 'raw' && raw_json_str) {
try {
const parsed = JSON.parse(raw_json_str);
cfg_json = parsed;
} catch (e) {
// Ignore invalid JSON while typing
}
}
// Sync Raw JSON string when entering the tab
$effect(() => {
if (active_tab === 'raw') {
untrack(() => {
raw_json_str = JSON.stringify(cfg_json, null, 2);
});
}
});
// Update cfg_json when raw string changes
$effect(() => {
if (active_tab === 'raw' && raw_json_str) {
try {
const parsed = JSON.parse(raw_json_str);
cfg_json = parsed;
} catch (e) {
// Ignore invalid JSON while typing
}
}
});
});
</script>
<div class="ae-site-config-editor flex flex-col h-full space-y-4">
<div class="ae-site-config-editor flex h-full flex-col space-y-4">
<!-- Tab Navigation -->
<div class="flex flex-wrap gap-1 p-1 bg-surface-500/10 rounded-lg max-w-fit">
<button class="btn btn-sm transition-all {active_tab === 'visuals' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'visuals'}>
<div
class="bg-surface-500/10 flex max-w-fit flex-wrap gap-1 rounded-lg p-1">
<button
class="btn btn-sm transition-all {active_tab === 'visuals'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'visuals')}>
<Palette size="1.1em" class="mr-1" /> Visuals
</button>
<button class="btn btn-sm transition-all {active_tab === 'email' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'email'}>
<button
class="btn btn-sm transition-all {active_tab === 'email'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'email')}>
<Mail size="1.1em" class="mr-1" /> Email
</button>
<button class="btn btn-sm transition-all {active_tab === 'ai' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'ai'}>
<button
class="btn btn-sm transition-all {active_tab === 'ai'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'ai')}>
<Brain size="1.1em" class="mr-1" /> AI/LLM
</button>
<button class="btn btn-sm transition-all {active_tab === 'refresh' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'refresh'}>
<button
class="btn btn-sm transition-all {active_tab === 'refresh'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'refresh')}>
<Timer size="1.1em" class="mr-1" /> Refresh
</button>
<button class="btn btn-sm transition-all {active_tab === 'idaa' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'idaa'}>
<button
class="btn btn-sm transition-all {active_tab === 'idaa'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'idaa')}>
<ShieldCheck size="1.1em" class="mr-1" /> IDAA
</button>
<button class="btn btn-sm transition-all {active_tab === 'raw' ? 'variant-filled-primary' : 'variant-soft-surface'}" onclick={() => active_tab = 'raw'}>
<button
class="btn btn-sm transition-all {active_tab === 'raw'
? 'variant-filled-primary'
: 'variant-soft-surface'}"
onclick={() => (active_tab = 'raw')}>
<CodeXml size="1.1em" class="mr-1" /> Raw JSON
</button>
</div>
<!-- Scrollable Content Area -->
<div class="grow overflow-y-auto p-1 pr-2 space-y-6 max-h-[60vh]">
<div class="max-h-[60vh] grow space-y-6 overflow-y-auto p-1 pr-2">
{#if active_tab === 'visuals'}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 animate-in fade-in duration-200">
<div
class="animate-in fade-in grid grid-cols-1 gap-4 duration-200 md:grid-cols-2">
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Theme Name</span>
<input type="text" bind:value={cfg_json.theme_name} class="input variant-form-material" placeholder="e.g. AE_OSIT_default" />
<span class="text-xs font-bold uppercase opacity-50"
>Theme Name</span>
<input
type="text"
bind:value={cfg_json.theme_name}
class="input variant-form-material"
placeholder="e.g. AE_OSIT_default" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Theme Mode</span>
<select bind:value={cfg_json.theme_mode} class="select variant-form-material">
<span class="text-xs font-bold uppercase opacity-50"
>Theme Mode</span>
<select
bind:value={cfg_json.theme_mode}
class="select variant-form-material">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="auto">Auto (System)</option>
</select>
</label>
<label class="label md:col-span-2">
<span class="text-xs font-bold uppercase opacity-50">Header Image Path (URL)</span>
<span class="text-xs font-bold uppercase opacity-50"
>Header Image Path (URL)</span>
<div class="flex gap-2">
<input type="text" bind:value={cfg_json.header_image_path} class="input variant-form-material grow" placeholder="https://..." />
<input
type="text"
bind:value={cfg_json.header_image_path}
class="input variant-form-material grow"
placeholder="https://..." />
{#if cfg_json.header_image_path}
<a href={cfg_json.header_image_path} target="_blank" rel="noopener noreferrer" class="btn-icon variant-soft-surface"><ExternalLink size="1.2em" /></a>
<a
href={cfg_json.header_image_path}
target="_blank"
rel="noopener noreferrer"
class="btn-icon variant-soft-surface"
><ExternalLink size="1.2em" /></a>
{/if}
</div>
</label>
</div>
{:else if active_tab === 'email'}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 animate-in fade-in duration-200">
<section class="space-y-4 border-r border-surface-500/10 pr-4">
<h4 class="text-sm font-black text-primary-500">Admin Contact</h4>
<div
class="animate-in fade-in grid grid-cols-1 gap-4 duration-200 md:grid-cols-2">
<section class="border-surface-500/10 space-y-4 border-r pr-4">
<h4 class="text-primary-500 text-sm font-black">
Admin Contact
</h4>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Admin Name</span>
<input type="text" bind:value={cfg_json.admin_name} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Admin Name</span>
<input
type="text"
bind:value={cfg_json.admin_name}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Admin Email</span>
<input type="email" bind:value={cfg_json.admin_email} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Admin Email</span>
<input
type="email"
bind:value={cfg_json.admin_email}
class="input variant-form-material" />
</label>
</section>
<section class="space-y-4">
<h4 class="text-sm font-black text-secondary-500">System (No-Reply)</h4>
<h4 class="text-secondary-500 text-sm font-black">
System (No-Reply)
</h4>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">No-Reply Name</span>
<input type="text" bind:value={cfg_json.noreply_name} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>No-Reply Name</span>
<input
type="text"
bind:value={cfg_json.noreply_name}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">No-Reply Email</span>
<input type="email" bind:value={cfg_json.noreply_email} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>No-Reply Email</span>
<input
type="email"
bind:value={cfg_json.noreply_email}
class="input variant-form-material" />
</label>
</section>
</div>
{:else if active_tab === 'ai'}
<div class="space-y-4 animate-in fade-in duration-200">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="animate-in fade-in space-y-4 duration-200">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">LLM API Base URL</span>
<input type="text" bind:value={cfg_json.llm__api_base_url} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>LLM API Base URL</span>
<input
type="text"
bind:value={cfg_json.llm__api_base_url}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">LLM Model</span>
<input type="text" bind:value={cfg_json.llm__api_model} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>LLM Model</span>
<input
type="text"
bind:value={cfg_json.llm__api_model}
class="input variant-form-material" />
</label>
</div>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">API Token</span>
<input type="password" bind:value={cfg_json.llm__api_token} class="input variant-form-material font-mono" />
<span class="text-xs font-bold uppercase opacity-50"
>API Token</span>
<input
type="password"
bind:value={cfg_json.llm__api_token}
class="input variant-form-material font-mono" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">System Prompt</span>
<textarea bind:value={cfg_json.llm__system_prompt} class="textarea variant-form-material h-24 text-sm"></textarea>
<span class="text-xs font-bold uppercase opacity-50"
>System Prompt</span>
<textarea
bind:value={cfg_json.llm__system_prompt}
class="textarea variant-form-material h-24 text-sm"
></textarea>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" bind:checked={cfg_json.llm__api_dangerous_browser} class="checkbox" />
<span class="text-xs font-bold uppercase opacity-50">Allow Browser Fetch (Dangerously)</span>
<input
type="checkbox"
bind:checked={cfg_json.llm__api_dangerous_browser}
class="checkbox" />
<span class="text-xs font-bold uppercase opacity-50"
>Allow Browser Fetch (Dangerously)</span>
</label>
</div>
{:else if active_tab === 'refresh'}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 animate-in fade-in duration-200">
<div
class="animate-in fade-in grid grid-cols-1 gap-4 duration-200 md:grid-cols-2">
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Default (Minutes)</span>
<input type="number" bind:value={cfg_json.default_refresh_minutes} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Default (Minutes)</span>
<input
type="number"
bind:value={cfg_json.default_refresh_minutes}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Authenticated (Minutes)</span>
<input type="number" bind:value={cfg_json.authenticated_refresh_time} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Authenticated (Minutes)</span>
<input
type="number"
bind:value={cfg_json.authenticated_refresh_time}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Trusted (Minutes)</span>
<input type="number" bind:value={cfg_json.trusted_refresh_minutes} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Trusted (Minutes)</span>
<input
type="number"
bind:value={cfg_json.trusted_refresh_minutes}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Manager (Minutes)</span>
<input type="number" bind:value={cfg_json.manager_refresh_minutes} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Manager (Minutes)</span>
<input
type="number"
bind:value={cfg_json.manager_refresh_minutes}
class="input variant-form-material" />
</label>
</div>
{:else if active_tab === 'idaa'}
<div class="space-y-6 animate-in fade-in duration-200">
<div class="animate-in fade-in space-y-6 duration-200">
<!-- Novi API -->
<section class="space-y-4 p-4 bg-surface-500/5 rounded-xl border border-surface-500/10">
<h4 class="text-sm font-black flex items-center gap-2">
<section
class="bg-surface-500/5 border-surface-500/10 space-y-4 rounded-xl border p-4">
<h4 class="flex items-center gap-2 text-sm font-black">
<Globe size="1.1em" class="text-primary-500" /> Novi API Connection
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">Root URL</span>
<input type="text" bind:value={cfg_json.novi_api_root_url} class="input variant-form-material" />
<span class="text-xs font-bold uppercase opacity-50"
>Root URL</span>
<input
type="text"
bind:value={cfg_json.novi_api_root_url}
class="input variant-form-material" />
</label>
<label class="label">
<span class="text-xs font-bold uppercase opacity-50">API Key</span>
<input type="password" bind:value={cfg_json.novi_idaa_api_key} class="input variant-form-material font-mono" />
<span class="text-xs font-bold uppercase opacity-50"
>API Key</span>
<input
type="password"
bind:value={cfg_json.novi_idaa_api_key}
class="input variant-form-material font-mono" />
</label>
</div>
</section>
<!-- UUID Lists -->
<section class="grid grid-cols-1 md:grid-cols-2 gap-4">
{#each [
{ key: 'novi_admin_li', label: 'Novi Admins', color: 'text-error-500' },
{ key: 'novi_trusted_li', label: 'Novi Trusted', color: 'text-warning-500' },
{ key: 'novi_jitsi_mod_li', label: 'Jitsi Moderators', color: 'text-primary-500' },
{ key: 'novi_idaa_group_guid_li', label: 'Member Group GUIDs', color: 'text-secondary-500' }
] as list (list.key)}
<div class="space-y-2 p-3 bg-surface-500/5 rounded-lg">
<header class="flex justify-between items-center">
<span class="text-[10px] font-black uppercase tracking-wider {list.color}">{list.label}</span>
<button class="btn btn-icon btn-icon-sm variant-soft-primary" onclick={() => add_to_list(list.key)}>
<section class="grid grid-cols-1 gap-4 md:grid-cols-2">
{#each [{ key: 'novi_admin_li', label: 'Novi Admins', color: 'text-error-500' }, { key: 'novi_trusted_li', label: 'Novi Trusted', color: 'text-warning-500' }, { key: 'novi_jitsi_mod_li', label: 'Jitsi Moderators', color: 'text-primary-500' }, { key: 'novi_idaa_group_guid_li', label: 'Member Group GUIDs', color: 'text-secondary-500' }] as list (list.key)}
<div class="bg-surface-500/5 space-y-2 rounded-lg p-3">
<header class="flex items-center justify-between">
<span
class="text-[10px] font-black tracking-wider uppercase {list.color}"
>{list.label}</span>
<button
class="btn btn-icon btn-icon-sm variant-soft-primary"
onclick={() => add_to_list(list.key)}>
<Plus size="12" />
</button>
</header>
<div class="space-y-1">
{#each cfg_json[list.key] ?? [] as uuid, i (uuid)}
<div class="flex gap-1 items-center bg-surface-500/10 p-1 rounded font-mono text-[10px]">
<span class="grow truncate">{uuid}</span>
<button class="text-error-500 hover:scale-110 transition-transform" onclick={() => remove_from_list(list.key, i)}>
<div
class="bg-surface-500/10 flex items-center gap-1 rounded p-1 font-mono text-[10px]">
<span class="grow truncate"
>{uuid}</span>
<button
class="text-error-500 transition-transform hover:scale-110"
onclick={() =>
remove_from_list(list.key, i)}>
<Minus size="12" />
</button>
</div>
@@ -240,62 +363,96 @@
</section>
<!-- Notifications -->
<section class="grid grid-cols-1 md:grid-cols-2 gap-4 p-4 bg-surface-500/5 rounded-xl">
<section
class="bg-surface-500/5 grid grid-cols-1 gap-4 rounded-xl p-4 md:grid-cols-2">
<div class="space-y-2">
<h4 class="text-[10px] font-black uppercase opacity-50">Bulletin Board</h4>
<h4 class="text-[10px] font-black uppercase opacity-50">
Bulletin Board
</h4>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.bb_send_staff_new_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={cfg_json.bb_send_staff_new_email}
class="checkbox checkbox-sm" />
<span>Notify Staff (New)</span>
</label>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.bb_send_staff_update_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={
cfg_json.bb_send_staff_update_email
}
class="checkbox checkbox-sm" />
<span>Notify Staff (Update)</span>
</label>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.bb_send_poster_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={cfg_json.bb_send_poster_email}
class="checkbox checkbox-sm" />
<span>Notify Poster</span>
</label>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.bb_send_commenter_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={cfg_json.bb_send_commenter_email}
class="checkbox checkbox-sm" />
<span>Notify Commenters</span>
</label>
</div>
<div class="space-y-2">
<h4 class="text-[10px] font-black uppercase opacity-50">Recovery Meetings</h4>
<h4 class="text-[10px] font-black uppercase opacity-50">
Recovery Meetings
</h4>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.recovery_mtg_send_staff_new_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={
cfg_json.recovery_mtg_send_staff_new_email
}
class="checkbox checkbox-sm" />
<span>Notify Staff (New)</span>
</label>
<label class="flex items-center space-x-2 text-xs">
<input type="checkbox" bind:checked={cfg_json.recovery_mtg_send_staff_update_email} class="checkbox checkbox-sm" />
<input
type="checkbox"
bind:checked={
cfg_json.recovery_mtg_send_staff_update_email
}
class="checkbox checkbox-sm" />
<span>Notify Staff (Update)</span>
</label>
</div>
</section>
</div>
{:else if active_tab === 'raw'}
<div class="h-[50vh] animate-in fade-in duration-200">
<div class="animate-in fade-in h-[50vh] duration-200">
<AE_Comp_Editor_CodeMirror
content={raw_json_str}
bind:new_content={raw_json_str}
language="json"
theme_mode={$ae_loc.theme_mode}
class_li="h-full border border-surface-500/20 rounded-lg shadow-inner"
/>
class_li="h-full border border-surface-500/20 rounded-lg shadow-inner" />
</div>
{/if}
</div>
<!-- Action Bar -->
<div class="flex justify-between items-center pt-4 border-t border-surface-500/10">
<div
class="border-surface-500/10 flex items-center justify-between border-t pt-4">
<div class="flex items-center gap-2">
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" bind:checked={cfg_json.test} class="checkbox" />
<span class="text-xs font-bold uppercase text-warning-500">Test Mode</span>
<label class="flex cursor-pointer items-center space-x-2">
<input
type="checkbox"
bind:checked={cfg_json.test}
class="checkbox" />
<span class="text-warning-500 text-xs font-bold uppercase"
>Test Mode</span>
</label>
</div>
<button class="btn btn-sm variant-filled-primary font-bold shadow-lg" onclick={on_save}>
<button
class="btn btn-sm variant-filled-primary font-bold shadow-lg"
onclick={on_save}>
<Save size="1.1em" class="mr-2" /> Save Config
</button>
</div>

View File

@@ -24,7 +24,9 @@ export async function load_ae_obj_id__account({
log_lvl?: number;
}): Promise<ae_Account | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__account() *** account_id=${account_id}`);
console.log(
`*** load_ae_obj_id__account() *** account_id=${account_id}`
);
}
ae_promises.load__account_obj = await api
@@ -39,10 +41,11 @@ export async function load_ae_obj_id__account({
.then(async function (account_obj_get_result) {
if (account_obj_get_result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__account_props({
obj_li: [account_obj_get_result],
log_lvl: log_lvl
});
const processed_obj_li =
await process_ae_obj__account_props({
obj_li: [account_obj_get_result],
log_lvl: log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'account',
@@ -114,10 +117,11 @@ export async function load_ae_obj_li__account({
.then(async function (account_obj_li_get_result) {
if (account_obj_li_get_result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__account_props({
obj_li: account_obj_li_get_result,
log_lvl: log_lvl
});
const processed_obj_li =
await process_ae_obj__account_props({
obj_li: account_obj_li_get_result,
log_lvl: log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'account',
@@ -164,10 +168,11 @@ export async function create_ae_obj__account({
.then(async function (account_obj_create_result) {
if (account_obj_create_result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__account_props({
obj_li: [account_obj_create_result],
log_lvl: log_lvl
});
const processed_obj_li =
await process_ae_obj__account_props({
obj_li: [account_obj_create_result],
log_lvl: log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'account',
@@ -206,7 +211,10 @@ export async function update_ae_obj__account({
log_lvl?: number;
}): Promise<ae_Account | null> {
if (log_lvl) {
console.log(`*** update_ae_obj__account() *** account_id=${account_id}`, data_kv);
console.log(
`*** update_ae_obj__account() *** account_id=${account_id}`,
data_kv
);
}
const result = await api.update_ae_obj({
@@ -256,7 +264,9 @@ export async function delete_ae_obj_id__account({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** delete_ae_obj_id__account() *** account_id=${account_id}`);
console.log(
`*** delete_ae_obj_id__account() *** account_id=${account_id}`
);
}
ae_promises.delete__account_obj = await api
@@ -337,11 +347,15 @@ async function _process_generic_props<T extends Record<string, any>>({
const updated = processed_obj.updated_on ?? processed_obj.created_on;
const name = processed_obj.name ?? '';
(processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);

View File

@@ -19,7 +19,9 @@ export async function load_ae_obj_id__activity_log({
log_lvl?: number;
}): Promise<ae_ActivityLog | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__activity_log() *** activity_log_id=${activity_log_id}`);
console.log(
`*** load_ae_obj_id__activity_log() *** activity_log_id=${activity_log_id}`
);
}
ae_promises.load__activity_log_obj = await api.get_ae_obj({
@@ -61,7 +63,9 @@ export async function load_ae_obj_li__activity_log({
log_lvl?: number;
}): Promise<ae_ActivityLog[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__activity_log() *** for_obj_id=${for_obj_id}`);
console.log(
`*** load_ae_obj_li__activity_log() *** for_obj_id=${for_obj_id}`
);
}
ae_promises.load__activity_log_obj_li = await api.get_ae_obj_li({
@@ -96,11 +100,15 @@ export async function create_ae_obj__activity_log({
log_lvl?: number;
}): Promise<ae_ActivityLog | null> {
if (log_lvl) {
console.log(`*** create_ae_obj__activity_log() *** account_id=${account_id}`);
console.log(
`*** create_ae_obj__activity_log() *** account_id=${account_id}`
);
}
if (!account_id) {
console.log(`ERROR: Core - Activity Log - account_id required to create`);
console.log(
`ERROR: Core - Activity Log - account_id required to create`
);
return null;
}
@@ -133,7 +141,9 @@ export async function update_ae_obj__activity_log({
log_lvl?: number;
}): Promise<ae_ActivityLog | null> {
if (log_lvl) {
console.log(`*** update_ae_obj__activity_log() *** activity_log_id=${activity_log_id}`);
console.log(
`*** update_ae_obj__activity_log() *** activity_log_id=${activity_log_id}`
);
}
ae_promises.update__activity_log_obj = await api.update_ae_obj({
@@ -151,7 +161,6 @@ export async function update_ae_obj__activity_log({
// Updated 2026-01-07
export async function qry__activity_log({
api_cfg,
account_id,
@@ -173,9 +182,7 @@ export async function qry__activity_log({
order_by_li = { created_on: 'DESC' },
log_lvl = 0
}: {
api_cfg: any;
account_id: string;
@@ -197,49 +204,36 @@ export async function qry__activity_log({
order_by_li?: Record<string, 'ASC' | 'DESC'>;
log_lvl?: number;
}): Promise<ae_ActivityLog[]> {
const search_query: any = {};
const filters: any[] = [];
if (account_id) {
filters.push({ field: 'account_id_random', op: 'eq', value: account_id });
filters.push({
field: 'account_id_random',
op: 'eq',
value: account_id
});
}
if (qry_person_id) {
filters.push({ field: 'person_id_random', op: 'eq', value: qry_person_id });
filters.push({
field: 'person_id_random',
op: 'eq',
value: qry_person_id
});
}
if (filters.length > 0) {
search_query.and = filters;
}
if (qry_str) {
search_query.q = qry_str;
}
ae_promises.load__activity_log_obj_li = await api.search_ae_obj({
api_cfg,
obj_type: 'activity_log',
@@ -259,13 +253,9 @@ export async function qry__activity_log({
order_by_li,
log_lvl
});
return ae_promises.load__activity_log_obj_li;
}
// Updated 2026-02-16
@@ -338,14 +328,21 @@ async function _process_generic_props<T extends Record<string, any>>({
const group = processed_obj.group ?? '0';
const priority = processed_obj.priority ? 1 : 0;
const sort = processed_obj.sort ?? '0';
const updated = processed_obj.updated_on ?? processed_obj.created_on ?? new Date(0).toISOString();
const updated =
processed_obj.updated_on ??
processed_obj.created_on ??
new Date(0).toISOString();
const name = processed_obj.name ?? '';
(processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -367,5 +364,3 @@ export async function process_ae_obj__activity_log_props({
log_lvl
});
}

View File

@@ -23,29 +23,34 @@ export async function load_ae_obj_id__address({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Address | null> {
ae_promises.load__address_obj = await api.get_ae_obj({
api_cfg,
obj_type: 'address',
obj_id: address_id,
view,
params,
log_lvl
}).then(async (result) => {
if (result) {
if (try_cache) {
const processed = await process_ae_obj__address_props({ obj_li: [result], log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
obj_li: processed,
properties_to_save,
log_lvl
});
ae_promises.load__address_obj = await api
.get_ae_obj({
api_cfg,
obj_type: 'address',
obj_id: address_id,
view,
params,
log_lvl
})
.then(async (result) => {
if (result) {
if (try_cache) {
const processed = await process_ae_obj__address_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return result;
}
return result;
}
return null;
});
return null;
});
return ae_promises.load__address_obj;
}
@@ -77,34 +82,39 @@ export async function load_ae_obj_li__address({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Address[]> {
ae_promises.load__address_obj_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'address',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
}).then(async (result) => {
if (result && Array.isArray(result)) {
if (try_cache) {
const processed = await process_ae_obj__address_props({ obj_li: result, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
obj_li: processed,
properties_to_save,
log_lvl
});
ae_promises.load__address_obj_li = await api
.get_ae_obj_li({
api_cfg,
obj_type: 'address',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
})
.then(async (result) => {
if (result && Array.isArray(result)) {
if (try_cache) {
const processed = await process_ae_obj__address_props({
obj_li: result,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return result;
}
return result;
}
return [];
});
return [];
});
return ae_promises.load__address_obj_li;
}
@@ -136,7 +146,10 @@ export async function create_ae_obj__address({
});
if (result && try_cache) {
const processed = await process_ae_obj__address_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__address_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
@@ -174,7 +187,10 @@ export async function update_ae_obj__address({
});
if (result && try_cache) {
const processed = await process_ae_obj__address_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__address_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'address',
@@ -269,7 +285,9 @@ async function _process_generic_props<T extends Record<string, any>>({
}
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -290,4 +308,4 @@ export async function process_ae_obj__address_props({
obj_type: 'address',
log_lvl
});
}
}

View File

@@ -23,29 +23,34 @@ export async function load_ae_obj_id__contact({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Contact | null> {
ae_promises.load__contact_obj = await api.get_ae_obj({
api_cfg,
obj_type: 'contact',
obj_id: contact_id,
view,
params,
log_lvl
}).then(async (result) => {
if (result) {
if (try_cache) {
const processed = await process_ae_obj__contact_props({ obj_li: [result], log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
obj_li: processed,
properties_to_save,
log_lvl
});
ae_promises.load__contact_obj = await api
.get_ae_obj({
api_cfg,
obj_type: 'contact',
obj_id: contact_id,
view,
params,
log_lvl
})
.then(async (result) => {
if (result) {
if (try_cache) {
const processed = await process_ae_obj__contact_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return result;
}
return result;
}
return null;
});
return null;
});
return ae_promises.load__contact_obj;
}
@@ -75,34 +80,39 @@ export async function load_ae_obj_li__contact({
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_Contact[]> {
ae_promises.load__contact_obj_li = await api.get_ae_obj_li({
api_cfg,
obj_type: 'contact',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
}).then(async (result) => {
if (result && Array.isArray(result)) {
if (try_cache) {
const processed = await process_ae_obj__contact_props({ obj_li: result, log_lvl });
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
obj_li: processed,
properties_to_save,
log_lvl
});
ae_promises.load__contact_obj_li = await api
.get_ae_obj_li({
api_cfg,
obj_type: 'contact',
for_obj_type,
for_obj_id,
enabled,
hidden,
view,
limit,
offset,
order_by_li,
log_lvl
})
.then(async (result) => {
if (result && Array.isArray(result)) {
if (try_cache) {
const processed = await process_ae_obj__contact_props({
obj_li: result,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
obj_li: processed,
properties_to_save,
log_lvl
});
}
return result;
}
return result;
}
return [];
});
return [];
});
return ae_promises.load__contact_obj_li;
}
@@ -134,7 +144,10 @@ export async function create_ae_obj__contact({
});
if (result && try_cache) {
const processed = await process_ae_obj__contact_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__contact_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
@@ -172,7 +185,10 @@ export async function update_ae_obj__contact({
});
if (result && try_cache) {
const processed = await process_ae_obj__contact_props({ obj_li: [result], log_lvl });
const processed = await process_ae_obj__contact_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'contact',
@@ -267,7 +283,9 @@ async function _process_generic_props<T extends Record<string, any>>({
}
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -288,4 +306,4 @@ export async function process_ae_obj__contact_props({
obj_type: 'contact',
log_lvl
});
}
}

View File

@@ -39,10 +39,12 @@ export async function load_ae_obj_id__person({
.then(async function (result) {
if (result) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__person_props({
obj_li: [result],
log_lvl
});
const processed_obj_li = await process_ae_obj__person_props(
{
obj_li: [result],
log_lvl
}
);
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'person',
@@ -98,7 +100,9 @@ export async function load_ae_obj_li__person({
log_lvl?: number;
}): Promise<ae_Person[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__person() *** for_obj_id=${for_obj_id}`);
console.log(
`*** load_ae_obj_li__person() *** for_obj_id=${for_obj_id}`
);
}
let promise;
@@ -109,11 +113,19 @@ export async function load_ae_obj_li__person({
};
if (qry_user_id) {
search_query.and.push({ field: 'user_id_random', op: 'eq', value: qry_user_id });
search_query.and.push({
field: 'user_id_random',
op: 'eq',
value: qry_user_id
});
}
if (qry_email) {
search_query.and.push({ field: 'primary_email', op: 'eq', value: qry_email });
search_query.and.push({
field: 'primary_email',
op: 'eq',
value: qry_email
});
}
if (for_obj_id) {
@@ -161,26 +173,30 @@ export async function load_ae_obj_li__person({
});
}
ae_promises.load__person_obj_li = await promise.then(async function (result_li) {
if (result_li) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__person_props({
obj_li: result_li,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'person',
obj_li: processed_obj_li,
properties_to_save: properties_to_save,
log_lvl
});
ae_promises.load__person_obj_li = await promise.then(
async function (result_li) {
if (result_li) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__person_props(
{
obj_li: result_li,
log_lvl
}
);
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'person',
obj_li: processed_obj_li,
properties_to_save: properties_to_save,
log_lvl
});
}
return result_li;
} else {
return [];
}
return result_li;
} else {
return [];
}
});
);
return ae_promises.load__person_obj_li;
}
@@ -411,11 +427,15 @@ async function _process_generic_props<T extends Record<string, any>>({
const updated = processed_obj.updated_on ?? processed_obj.created_on;
const name = processed_obj.full_name ?? processed_obj.name ?? '';
(processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);

View File

@@ -113,10 +113,18 @@ export async function lookup_site_domain({
try {
cached = await db_core.site_domain.where('fqdn').equals(fqdn).first();
if (cached) {
if (log_lvl) console.log('BOOTSTRAP: Cache hit. Returning cached site domain immediately.');
if (log_lvl)
console.log(
'BOOTSTRAP: Cache hit. Returning cached site domain immediately.'
);
// Trigger background refresh to keep cache fresh, but don't await it
_refresh_site_domain_background({ api_cfg, fqdn, view, log_lvl: 0 });
_refresh_site_domain_background({
api_cfg,
fqdn,
view,
log_lvl: 0
});
return cached as any;
}
@@ -125,13 +133,23 @@ export async function lookup_site_domain({
}
// 2. SLOW PATH: Wait for API if cache is empty
return await _refresh_site_domain_background({ api_cfg, fqdn, view, log_lvl });
return await _refresh_site_domain_background({
api_cfg,
fqdn,
view,
log_lvl
});
}
/**
* Internal helper to perform the actual API fetch and cache update
*/
async function _refresh_site_domain_background({ api_cfg, fqdn, view, log_lvl }: any) {
async function _refresh_site_domain_background({
api_cfg,
fqdn,
view,
log_lvl
}: any) {
try {
const guest_api_cfg = { ...api_cfg };
guest_api_cfg.headers = { ...api_cfg.headers };
@@ -144,7 +162,7 @@ async function _refresh_site_domain_background({ api_cfg, fqdn, view, log_lvl }:
'JWT'
];
auth_props.forEach(prop => {
auth_props.forEach((prop) => {
delete guest_api_cfg.headers[prop];
delete guest_api_cfg.headers[prop.toLowerCase()];
delete guest_api_cfg.headers[prop.replaceAll('-', '_')];
@@ -481,11 +499,12 @@ export async function load_ae_obj_li__site_domain({
.then(async function (domain_li) {
if (domain_li) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__site_domain_props({
obj_li: domain_li,
site_id,
log_lvl
});
const processed_obj_li =
await process_ae_obj__site_domain_props({
obj_li: domain_li,
site_id,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'site_domain',
@@ -729,11 +748,15 @@ async function _process_generic_props<T extends Record<string, any>>({
const updated = processed_obj.updated_on ?? processed_obj.created_on;
const name = processed_obj.name ?? processed_obj.fqdn ?? '';
(processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);

View File

@@ -93,7 +93,9 @@ export async function load_ae_obj_li__user({
log_lvl?: number;
}): Promise<ae_User[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__user() *** for_obj_id=${for_obj_id} include_global=${include_global} qry_str=${qry_str}`);
console.log(
`*** load_ae_obj_li__user() *** for_obj_id=${for_obj_id} include_global=${include_global} qry_str=${qry_str}`
);
}
// SCENARIO A: Text Search
@@ -107,9 +109,17 @@ export async function load_ae_obj_li__user({
]
});
} else if (for_obj_id) {
search_query.and.push({ field: `account_id_random`, op: 'eq', value: for_obj_id });
search_query.and.push({
field: `account_id_random`,
op: 'eq',
value: for_obj_id
});
} else if (include_global) {
search_query.and.push({ field: `account_id_random`, op: 'eq', value: null });
search_query.and.push({
field: `account_id_random`,
op: 'eq',
value: null
});
}
return await api.search_ae_obj({
@@ -130,13 +140,33 @@ export async function load_ae_obj_li__user({
if (for_obj_id && include_global) {
if (log_lvl) console.log('Strategy: Multi-call (Account + Global)');
const [acct_users, global_users] = await Promise.all([
load_ae_obj_li__user({ api_cfg, for_obj_id, include_global: false, enabled, hidden, view, limit, log_lvl }),
load_ae_obj_li__user({ api_cfg, for_obj_id: null, include_global: true, enabled, hidden, view, limit, log_lvl })
load_ae_obj_li__user({
api_cfg,
for_obj_id,
include_global: false,
enabled,
hidden,
view,
limit,
log_lvl
}),
load_ae_obj_li__user({
api_cfg,
for_obj_id: null,
include_global: true,
enabled,
hidden,
view,
limit,
log_lvl
})
]);
// Merge and unique-ify by ID
const merged = [...acct_users, ...global_users];
const unique = Array.from(new Map(merged.map(u => [u.user_id_random, u])).values());
const unique = Array.from(
new Map(merged.map((u) => [u.user_id_random, u])).values()
);
return unique;
}
@@ -162,7 +192,8 @@ export async function load_ae_obj_li__user({
}
// SCENARIO D: Account Only or Everything (confirmed working List API)
if (log_lvl) console.log(`Strategy: Standard List API (for_obj_id=${for_obj_id})`);
if (log_lvl)
console.log(`Strategy: Standard List API (for_obj_id=${for_obj_id})`);
return await api.get_ae_obj_li({
api_cfg,
obj_type: 'user',
@@ -383,7 +414,10 @@ export async function auth_ae_obj__username_password({
});
if (log_lvl) {
console.log('ae_promises.auth__username_password:', ae_promises.auth__username_password);
console.log(
'ae_promises.auth__username_password:',
ae_promises.auth__username_password
);
}
return ae_promises.auth__username_password;
}
@@ -450,7 +484,10 @@ export async function auth_ae_obj__user_id_user_auth_key({
});
if (log_lvl) {
console.log('ae_promises.auth__user_id_user_key:', ae_promises.auth__user_id_user_key);
console.log(
'ae_promises.auth__user_id_user_key:',
ae_promises.auth__user_id_user_key
);
}
return ae_promises.auth__user_id_user_key;
}
@@ -525,7 +562,9 @@ export async function qry_ae_obj_li__user_email({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** qry_ae_obj_li__user_email() *** account_id=${account_id} email=${email}`);
console.log(
`*** qry_ae_obj_li__user_email() *** account_id=${account_id} email=${email}`
);
}
const endpoint = '/user/lookup_email';
@@ -676,11 +715,15 @@ async function _process_generic_props<T extends Record<string, any>>({
const updated = processed_obj.updated_on ?? processed_obj.created_on;
const name = processed_obj.username ?? processed_obj.name ?? '';
(processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -701,4 +744,4 @@ export async function process_ae_obj__user_props({
obj_type: 'user',
log_lvl
});
}
}

View File

@@ -25,7 +25,10 @@ import {
auth_ae_obj__user_id_change_password
} from '$lib/ae_core/ae_core__user';
import { generate_qr_code, js_generate_qr_code } from '$lib/ae_core/core__qr_code';
import {
generate_qr_code,
js_generate_qr_code
} from '$lib/ae_core/core__qr_code';
import { check_hosted_file_obj_w_hash } from '$lib/ae_core/core__check_hosted_file_obj_w_hash';
@@ -161,7 +164,9 @@ async function load_ae_obj_code__data_store({
}
if (!get_ds_result.data_store_id_random) {
console.log('*ae_func* Something went wrong? No data store ID found.');
console.log(
'*ae_func* Something went wrong? No data store ID found.'
);
return false;
}
@@ -240,7 +245,10 @@ async function load_ae_obj_code__data_store({
get_ds_result
);
}
localStorage.setItem(`${key_prefix}${code}`, JSON.stringify(get_ds_result));
localStorage.setItem(
`${key_prefix}${code}`,
JSON.stringify(get_ds_result)
);
} else {
if (log_lvl) {
console.log(
@@ -491,7 +499,10 @@ async function download_export__obj_type({
log_lvl: log_lvl
});
console.log('ae_promises.download__export_file:', ae_promises.download__export_file);
console.log(
'ae_promises.download__export_file:',
ae_promises.download__export_file
);
return ae_promises.download__export_file;
}

View File

@@ -13,12 +13,17 @@ export function add_url_params({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** add_url_params() *** base_url=${base_url} endpoint=${endpoint}`, params);
console.log(
`*** add_url_params() *** base_url=${base_url} endpoint=${endpoint}`,
params
);
}
const url_obj = new URL(endpoint, base_url);
Object.keys(params).forEach((key) => url_obj.searchParams.append(key, params[key]));
Object.keys(params).forEach((key) =>
url_obj.searchParams.append(key, params[key])
);
if (log_lvl) {
console.log('New URL:', url_obj.toString());
@@ -30,7 +35,13 @@ export function add_url_params({
// This is used to clean the header property names. Not underscores allowed in the header names.
// Updated 2025-01-28
export function clean_headers({ headers, log_lvl = 0 }: { headers: any; log_lvl?: number }) {
export function clean_headers({
headers,
log_lvl = 0
}: {
headers: any;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** clean_headers() ***`, headers);
}

View File

@@ -33,8 +33,14 @@ async function _refresh_lu_country_background({
if (result?.length) {
await db_lookups.lu_country.clear();
await db_lookups.lu_country.bulkPut(result);
await db_lookups.lu_cache_meta.put({ lu_type: 'country', refreshed_at: Date.now() });
if (log_lvl) console.log(`lu_country: saved ${result.length} records to IDB`);
await db_lookups.lu_cache_meta.put({
lu_type: 'country',
refreshed_at: Date.now()
});
if (log_lvl)
console.log(
`lu_country: saved ${result.length} records to IDB`
);
}
} catch (error) {
console.error('lu_country refresh failed:', error);
@@ -59,6 +65,8 @@ export async function load_ae_obj_li__country({
// Fire-and-forget — liveQuery subscribers receive updates when IDB is written
_refresh_lu_country_background({ api_cfg, log_lvl });
} else if (log_lvl) {
console.log(`lu_country: IDB fresh (${count} records), skipping refresh`);
console.log(
`lu_country: IDB fresh (${count} records), skipping refresh`
);
}
}

View File

@@ -19,7 +19,8 @@ async function _refresh_lu_country_subdivision_background({
api_cfg: any;
log_lvl?: number;
}) {
if (log_lvl) console.log('*** _refresh_lu_country_subdivision_background() ***');
if (log_lvl)
console.log('*** _refresh_lu_country_subdivision_background() ***');
try {
const result = await api.get_ae_obj_li_for_lu({
api_cfg,
@@ -37,7 +38,9 @@ async function _refresh_lu_country_subdivision_background({
refreshed_at: Date.now()
});
if (log_lvl)
console.log(`lu_country_subdivision: saved ${result.length} records to IDB`);
console.log(
`lu_country_subdivision: saved ${result.length} records to IDB`
);
}
} catch (error) {
console.error('lu_country_subdivision refresh failed:', error);
@@ -61,6 +64,8 @@ export async function load_ae_obj_li__country_subdivision({
if (count === 0 || is_stale) {
_refresh_lu_country_subdivision_background({ api_cfg, log_lvl });
} else if (log_lvl) {
console.log(`lu_country_subdivision: IDB fresh (${count} records), skipping refresh`);
console.log(
`lu_country_subdivision: IDB fresh (${count} records), skipping refresh`
);
}
}

View File

@@ -50,16 +50,24 @@ export async function load_ae_obj_by_code__data_store({
return null;
}
const ds_id = get_ds_result.data_store_id_random || get_ds_result.id_random;
const ds_id =
get_ds_result.data_store_id_random || get_ds_result.id_random;
if (!ds_id) {
if (log_lvl) console.log('*ae_func* Something went wrong? No data store ID found.');
if (log_lvl)
console.log(
'*ae_func* Something went wrong? No data store ID found.'
);
return null;
}
// Map content fields for convenience
const text_val = get_ds_result.text || '';
const json_val = get_ds_result.json || (get_ds_result.json_str ? JSON.parse(get_ds_result.json_str) : null);
const json_val =
get_ds_result.json ||
(get_ds_result.json_str
? JSON.parse(get_ds_result.json_str)
: null);
const mapped_ds: ae_DataStore = {
...get_ds_result,
@@ -77,7 +85,6 @@ export async function load_ae_obj_by_code__data_store({
if (data_type === 'html') return mapped_ds.html;
if (data_type === 'json') return mapped_ds.json;
return mapped_ds.text;
} catch (error) {
if (log_lvl) console.error('*ae_func* Fetch failed.', error);
return null;

View File

@@ -22,7 +22,9 @@ export async function load_ae_obj_id__hosted_file({
log_lvl?: number;
}): Promise<ae_HostedFile | null> {
if (log_lvl) {
console.log(`*** load_ae_obj_id__hosted_file() *** [V3] id=${hosted_file_id}`);
console.log(
`*** load_ae_obj_id__hosted_file() *** [V3] id=${hosted_file_id}`
);
}
try {
@@ -36,10 +38,11 @@ export async function load_ae_obj_id__hosted_file({
if (ae_promises.load__hosted_file_obj) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__hosted_file_props({
obj_li: [ae_promises.load__hosted_file_obj],
log_lvl
});
const processed_obj_li =
await process_ae_obj__hosted_file_props({
obj_li: [ae_promises.load__hosted_file_obj],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'file',
@@ -49,12 +52,14 @@ export async function load_ae_obj_id__hosted_file({
});
}
} else if (try_cache) {
ae_promises.load__hosted_file_obj = await db_core.file.get(hosted_file_id);
ae_promises.load__hosted_file_obj =
await db_core.file.get(hosted_file_id);
}
} catch (error: any) {
console.log('V3 Request failed.', error);
if (try_cache) {
ae_promises.load__hosted_file_obj = await db_core.file.get(hosted_file_id);
ae_promises.load__hosted_file_obj =
await db_core.file.get(hosted_file_id);
}
}
@@ -90,7 +95,9 @@ export async function load_ae_obj_li__hosted_file({
log_lvl?: number;
}): Promise<ae_HostedFile[]> {
if (log_lvl) {
console.log(`*** load_ae_obj_li__hosted_file() *** [V3] for=${for_obj_type}:${for_obj_id}`);
console.log(
`*** load_ae_obj_li__hosted_file() *** [V3] for=${for_obj_type}:${for_obj_id}`
);
}
try {
@@ -109,10 +116,11 @@ export async function load_ae_obj_li__hosted_file({
if (ae_promises.load__hosted_file_obj_li) {
if (try_cache) {
const processed_obj_li = await process_ae_obj__hosted_file_props({
obj_li: ae_promises.load__hosted_file_obj_li,
log_lvl
});
const processed_obj_li =
await process_ae_obj__hosted_file_props({
obj_li: ae_promises.load__hosted_file_obj_li,
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_core,
table_name: 'file',
@@ -123,14 +131,16 @@ export async function load_ae_obj_li__hosted_file({
}
} else if (try_cache) {
ae_promises.load__hosted_file_obj_li = await db_core.file
.where('for_id').equals(for_obj_id)
.where('for_id')
.equals(for_obj_id)
.toArray();
}
} catch (error: any) {
console.log('V3 List Request failed.', error);
if (try_cache) {
ae_promises.load__hosted_file_obj_li = await db_core.file
.where('for_id').equals(for_obj_id)
.where('for_id')
.equals(for_obj_id)
.toArray();
}
}
@@ -159,7 +169,9 @@ export async function delete_ae_obj_id__hosted_file({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** delete_ae_obj_id__hosted_file() *** [Special] id=${hosted_file_id}`);
console.log(
`*** delete_ae_obj_id__hosted_file() *** [Special] id=${hosted_file_id}`
);
}
// Use the specialized hosted file delete endpoint
@@ -203,7 +215,9 @@ export async function download_ae_obj_id__hosted_file({
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** download_ae_obj_id__hosted_file() *** id=${hosted_file_id}`);
console.log(
`*** download_ae_obj_id__hosted_file() *** id=${hosted_file_id}`
);
}
const task_id = hosted_file_id;
@@ -290,11 +304,15 @@ async function _process_generic_props<T extends Record<string, any>>({
const updated = processed_obj.updated_on ?? processed_obj.created_on;
const name = processed_obj.name ?? '';
(processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`;
(processed_obj as any).tmp_sort_1 =
`${group}_${priority}_${sort}_${updated}`;
(processed_obj as any).tmp_sort_2 =
`${group}_${priority}_${sort}_${name}_${updated}`;
if (specific_processor) {
processed_obj = await Promise.resolve(specific_processor(processed_obj));
processed_obj = await Promise.resolve(
specific_processor(processed_obj)
);
}
processed_obj_li.push(processed_obj as T);
@@ -315,4 +333,4 @@ export async function process_ae_obj__hosted_file_props({
obj_type: 'hosted_file',
log_lvl
});
}
}

View File

@@ -14,9 +14,9 @@ function find_object_id(
log_lvl: number
): string | number | undefined {
const potential_keys = [
'id',
'id_random',
`${table_name}_id`,
'id',
'id_random',
`${table_name}_id`,
`${table_name}_id_random`,
`event_${table_name}_id`,
`event_${table_name}_id_random`
@@ -115,7 +115,9 @@ export async function db_save_ae_obj_li__ae_obj<T extends Record<string, any>>({
if (data_to_save.length === 0) {
if (log_lvl > 0) {
console.warn('All objects were skipped, likely due to missing IDs.');
console.warn(
'All objects were skipped, likely due to missing IDs.'
);
}
return [];
}
@@ -124,7 +126,9 @@ export async function db_save_ae_obj_li__ae_obj<T extends Record<string, any>>({
// bulkPut efficiently handles both inserts and updates.
const keys = await db_table.bulkPut(data_to_save);
if (log_lvl > 0) {
console.log(`Successfully saved ${data_to_save.length} objects to "${table_name}".`);
console.log(
`Successfully saved ${data_to_save.length} objects to "${table_name}".`
);
}
return keys;
} catch (error) {

View File

@@ -49,7 +49,8 @@ export async function generate_qr_code({
if (qr_type == 'vcard') {
if (qr_data.informal_name) {
params['n'] = `${qr_data.family_name};${qr_data.given_name};${qr_data.informal_name}`;
params['n'] =
`${qr_data.family_name};${qr_data.given_name};${qr_data.informal_name}`;
} else {
params['n'] = `${qr_data.family_name};${qr_data.given_name}`;
}
@@ -99,7 +100,8 @@ export async function generate_qr_code({
// If return_blob is true, ensure we return an object URL for use in <img src=...>
if (return_blob) {
const data = ae_promises.generate_qr_code.data ?? ae_promises.generate_qr_code;
const data =
ae_promises.generate_qr_code.data ?? ae_promises.generate_qr_code;
// If already a Blob, use it directly
if (data instanceof Blob) {
@@ -133,7 +135,8 @@ export async function generate_qr_code({
const blob = new Blob([data as BlobPart], { type: 'image/png' });
return URL.createObjectURL(blob);
} catch (e) {
if (log_lvl) console.error('Could not create QR code image blob:', e, data);
if (log_lvl)
console.error('Could not create QR code image blob:', e, data);
return null;
}
}
@@ -155,7 +158,10 @@ export async function generate_qr_code({
* @returns {Promise<string>} A promise that resolves to a Base64 data URL of the QR code image.
* @throws {Error} If the qr_type is unknown or data is missing.
*/
export async function js_generate_qr_code(qr_type: string, params: key_val = {}) {
export async function js_generate_qr_code(
qr_type: string,
params: key_val = {}
) {
const {
n = '',
fn = '',
@@ -179,7 +185,8 @@ export async function js_generate_qr_code(qr_type: string, params: key_val = {})
log_lvl = 0
} = params;
if (log_lvl >= 2) console.log(`*** js_generate_qr_code() *** qr_type=${qr_type}`, params);
if (log_lvl >= 2)
console.log(`*** js_generate_qr_code() *** qr_type=${qr_type}`, params);
let qr_data: string | null = null;
@@ -211,13 +218,15 @@ export async function js_generate_qr_code(qr_type: string, params: key_val = {})
case 'obj':
// Custom format: OBJ:ot:obj_type,oi:obj_id
if (!obj_type || !obj_id) throw new Error('Missing obj_type or obj_id for type "obj".');
if (!obj_type || !obj_id)
throw new Error('Missing obj_type or obj_id for type "obj".');
qr_data = `OBJ:ot:${obj_type},oi:${obj_id}`;
break;
case 'kv':
// Custom format: KV:k:"key",v:"val"
if (!key || !val) throw new Error('Missing key or val for type "kv".');
if (!key || !val)
throw new Error('Missing key or val for type "kv".');
qr_data = `KV:k:"${key}",v:"${val}"`;
break;
@@ -229,7 +238,8 @@ export async function js_generate_qr_code(qr_type: string, params: key_val = {})
case 'str':
// Raw string data
if (!str) throw new Error('Missing raw string data for type "str".');
if (!str)
throw new Error('Missing raw string data for type "str".');
qr_data = str;
break;

View File

@@ -27,7 +27,7 @@ async function _refresh_lu_time_zone_background({
for_lu_type: 'time_zone',
enabled: 'enabled',
hidden: 'not_hidden',
only_priority: true, // ~72 priority timezone records
only_priority: true, // ~72 priority timezone records
limit: 1800,
log_lvl
});
@@ -38,7 +38,10 @@ async function _refresh_lu_time_zone_background({
lu_type: 'time_zone',
refreshed_at: Date.now()
});
if (log_lvl) console.log(`lu_time_zone: saved ${result.length} records to IDB`);
if (log_lvl)
console.log(
`lu_time_zone: saved ${result.length} records to IDB`
);
}
} catch (error) {
console.error('lu_time_zone refresh failed:', error);
@@ -62,6 +65,8 @@ export async function load_ae_obj_li__time_zone({
if (count === 0 || is_stale) {
_refresh_lu_time_zone_background({ api_cfg, log_lvl });
} else if (log_lvl) {
console.log(`lu_time_zone: IDB fresh (${count} records), skipping refresh`);
console.log(
`lu_time_zone: IDB fresh (${count} records), skipping refresh`
);
}
}

View File

@@ -12,7 +12,7 @@ import Dexie, { type Table } from 'dexie';
export interface LuCountry {
id: number;
group: string; // dedup key = alpha_2_code (e.g. "US")
group: string; // dedup key = alpha_2_code (e.g. "US")
alpha_2_code: string;
name: string;
english_short_name?: string;
@@ -22,12 +22,12 @@ export interface LuCountry {
priority?: number;
sort?: number;
account_id?: number | null;
[key: string]: unknown; // allow extra fields from API without TS errors
[key: string]: unknown; // allow extra fields from API without TS errors
}
export interface LuCountrySubdivision {
id: number;
group: string; // dedup key = code (e.g. "US-NY")
group: string; // dedup key = code (e.g. "US-NY")
code: string;
name: string;
country_alpha_2_code?: string;
@@ -42,9 +42,9 @@ export interface LuCountrySubdivision {
export interface LuTimeZone {
id: number;
group: string; // dedup key = name (IANA identifier, e.g. "US/Eastern")
group: string; // dedup key = name (IANA identifier, e.g. "US/Eastern")
name: string;
name_override?: string; // display label override; prefer this over name when set
name_override?: string; // display label override; prefer this over name when set
enable?: number;
hide?: number;
priority?: number;
@@ -55,7 +55,7 @@ export interface LuTimeZone {
export interface LuCacheMeta {
lu_type: 'country' | 'country_subdivision' | 'time_zone';
refreshed_at: number; // Unix timestamp ms — used for 24h TTL check
refreshed_at: number; // Unix timestamp ms — used for 24h TTL check
}
class LookupsDexie extends Dexie {
@@ -67,10 +67,10 @@ class LookupsDexie extends Dexie {
constructor() {
super('ae_lookups_db');
this.version(1).stores({
lu_country: 'id, alpha_2_code, group',
lu_country: 'id, alpha_2_code, group',
lu_country_subdivision: 'id, code, country_alpha_2_code, group',
lu_time_zone: 'id, name, group',
lu_cache_meta: 'lu_type'
lu_time_zone: 'id, name, group',
lu_cache_meta: 'lu_type'
});
}
}