Launcher: Resolved session selection hang and improved reactivity pattern.
This commit is contained in:
@@ -78,18 +78,30 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unified Selection Sync (Refactored 2026-02-11)
|
||||||
|
// WHY: We track URL params directly to ensure the UI reacts instantly to navigation.
|
||||||
|
// We use untrack for store writes to prevent circular dependency loops.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (log_lvl) {
|
const url_session_id = data.url.searchParams.get('session_id');
|
||||||
console.log(`event_id: ${data.params.event_id}`);
|
const path_location_id = data.params.event_location_id;
|
||||||
console.log(`event_location_id: ${data.params.event_location_id}`);
|
const path_event_id = data.params.event_id;
|
||||||
console.log(
|
|
||||||
`event_session_id: ${data.url.searchParams.get('session_id')}`
|
if (log_lvl > 1) {
|
||||||
);
|
console.log(`[Launcher Sync] URL Change: event=${path_event_id}, loc=${path_location_id}, sess=${url_session_id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
$events_slct.event_id = data.params.event_id;
|
if ($events_slct.event_id !== path_event_id) {
|
||||||
$events_slct.event_location_id = data.params.event_location_id;
|
$events_slct.event_id = path_event_id;
|
||||||
$events_slct.event_session_id = data.url.searchParams.get('session_id');
|
}
|
||||||
|
if ($events_slct.event_location_id !== path_location_id) {
|
||||||
|
$events_slct.event_location_id = path_location_id;
|
||||||
|
}
|
||||||
|
// CRITICAL: Ensure session_id is synced to store so LiveQueries react
|
||||||
|
if ($events_slct.event_session_id !== url_session_id) {
|
||||||
|
if (log_lvl) console.log(`[Launcher Sync] Updating store session_id: ${url_session_id}`);
|
||||||
|
$events_slct.event_session_id = url_session_id;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -570,11 +582,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $events_slct.event_session_id && $lq__event_session_obj}
|
{#if $events_slct.event_session_id}
|
||||||
<Launcher_session_view
|
<Launcher_session_view
|
||||||
bind:slct__event_session_id={$events_slct.event_session_id}
|
bind:slct__event_session_id={$events_slct.event_session_id}
|
||||||
{lq__event_session_obj}
|
|
||||||
bind:type_code={$lq__event_session_obj.type_code}
|
|
||||||
></Launcher_session_view>
|
></Launcher_session_view>
|
||||||
{:else if $events_slct.event_session_id}
|
{:else if $events_slct.event_session_id}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -42,168 +42,63 @@
|
|||||||
import { events_func } from '$lib/ae_events_functions';
|
import { events_func } from '$lib/ae_events_functions';
|
||||||
|
|
||||||
// Event Session (Main View Trigger)
|
// Event Session (Main View Trigger)
|
||||||
|
// WHY: We use a simple derived observable. The template handles the $ prefix.
|
||||||
let lq__event_session_obj = $derived(
|
let lq__event_session_obj = $derived(
|
||||||
liveQuery(() => db_events.session.get(slct__event_session_id))
|
liveQuery(() => db_events.session.get(slct__event_session_id))
|
||||||
);
|
);
|
||||||
|
|
||||||
// import Event_launcher_file_cont from './launcher_file_cont.svelte';
|
|
||||||
|
|
||||||
// export let hide_description: boolean = true;
|
|
||||||
// export let show_designations: boolean = false;
|
|
||||||
// export let show_email: boolean = false;
|
|
||||||
|
|
||||||
// Event File (for a Session)
|
// Event File (for a Session)
|
||||||
|
// WHY: Pure data retrieval. Side effects (updating global stores) are removed
|
||||||
|
// to prevent circular reactivity loops during rapid navigation.
|
||||||
let lq__event_file_obj_li = $derived(
|
let lq__event_file_obj_li = $derived(
|
||||||
liveQuery(async () => {
|
liveQuery(async () => {
|
||||||
if (log_lvl) {
|
if (!slct__event_session_id) return [];
|
||||||
console.log(
|
|
||||||
`lq__event_file_obj: event_file_id = ${$events_slct?.event_file_id}`
|
if (log_lvl > 1) {
|
||||||
);
|
console.log(`[LQ] Fetching files for session: ${slct__event_session_id}`);
|
||||||
}
|
}
|
||||||
let results = await db_events.file
|
|
||||||
// .where('event_session_id')
|
return await db_events.file
|
||||||
.where('for_id')
|
.where('for_id')
|
||||||
.equals(slct__event_session_id)
|
.equals(slct__event_session_id)
|
||||||
.reverse() // Need reverse for created_on newest first.
|
.reverse()
|
||||||
.sortBy('created_on'); // or filename
|
.sortBy('created_on');
|
||||||
if (log_lvl) {
|
|
||||||
console.log(`lq__event_file_obj_li: results = `, results);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if results are different than the current $events_slct.event_file_obj_li
|
|
||||||
if ($events_slct.event_file_obj_li && results) {
|
|
||||||
if (
|
|
||||||
JSON.stringify($events_slct.event_file_obj_li) !==
|
|
||||||
JSON.stringify(results)
|
|
||||||
) {
|
|
||||||
$events_slct.event_file_obj_li = [...results];
|
|
||||||
if (log_lvl) {
|
|
||||||
console.log(
|
|
||||||
`$events_slct.event_file_obj_li has changed for event_session_id: ${slct__event_session_id}`,
|
|
||||||
$events_slct.event_file_obj_li
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (log_lvl) {
|
|
||||||
console.log(
|
|
||||||
`$events_slct.event_file_obj_li has not changed for event_session_id: ${slct__event_session_id}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (results) {
|
|
||||||
$events_slct.event_file_obj_li = [...results];
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Does not refresh when the event_file_id_li_json changes.
|
|
||||||
// let lq_get__event_file_obj_li = liveQuery(
|
|
||||||
// () => db_events.file
|
|
||||||
// .bulkGet($lq__event_session_obj?.event_file_id_li_json ?? [''])
|
|
||||||
// );
|
|
||||||
// console.log(`$lq__event_session_obj?.event_file_id_li_json = `, $lq__event_session_obj?.event_file_id_li_json);
|
|
||||||
|
|
||||||
// Event Presentation
|
// Event Presentation
|
||||||
// let lq__event_presentation_obj = liveQuery(
|
|
||||||
// () => db_events.presentation
|
|
||||||
// .get($events_slct.event_presentation_id)
|
|
||||||
// );
|
|
||||||
|
|
||||||
let lq__event_presentation_obj_li = $derived(
|
let lq__event_presentation_obj_li = $derived(
|
||||||
liveQuery(async () => {
|
liveQuery(async () => {
|
||||||
if (log_lvl) {
|
if (!slct__event_session_id) return [];
|
||||||
console.log(
|
|
||||||
`lq__event_presentation_obj_li: slct__event_session_id = ${slct__event_session_id}`
|
if (log_lvl > 1) {
|
||||||
);
|
console.log(`[LQ] Fetching presentations for session: ${slct__event_session_id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sort_by = 'start_datetime';
|
let sort_by = 'start_datetime';
|
||||||
if (type_code == 'poster') {
|
if (type_code == 'poster') {
|
||||||
sort_by = 'name';
|
sort_by = 'name';
|
||||||
}
|
}
|
||||||
let results = await db_events.presentation
|
return await db_events.presentation
|
||||||
.where('event_session_id')
|
.where('event_session_id')
|
||||||
.equals(slct__event_session_id)
|
.equals(slct__event_session_id)
|
||||||
.sortBy(sort_by);
|
.sortBy(sort_by);
|
||||||
// .sortBy('name')
|
|
||||||
if (log_lvl) {
|
|
||||||
console.log(
|
|
||||||
`lq__event_presentation_obj_li: results = `,
|
|
||||||
results
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if results are different than the current $events_slct.event_presentation_obj_li
|
|
||||||
if ($events_slct.event_presentation_obj_li && results) {
|
|
||||||
if (
|
|
||||||
JSON.stringify($events_slct.event_presentation_obj_li) !==
|
|
||||||
JSON.stringify(results)
|
|
||||||
) {
|
|
||||||
$events_slct.event_presentation_obj_li = { ...results };
|
|
||||||
if (log_lvl) {
|
|
||||||
console.log(
|
|
||||||
`$events_slct.event_presentation_obj_li has changed for event_session_id: ${slct__event_session_id}`,
|
|
||||||
$events_slct.event_presentation_obj_li
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (log_lvl) {
|
|
||||||
console.log(
|
|
||||||
`$events_slct.event_presentation_obj_li has not changed for event_session_id: ${slct__event_session_id}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// FIX! This id list needs to be updated. It is currently commented out in the menu_session_list.svelte file.
|
// Event Presenter
|
||||||
// let lq_get__event_presentation_obj_li = liveQuery(
|
|
||||||
// () => db_events.presentation
|
|
||||||
// .bulkGet($events_slct.id_li__event_presentation)
|
|
||||||
// );
|
|
||||||
|
|
||||||
let lq__event_presenter_obj_li = $derived(
|
let lq__event_presenter_obj_li = $derived(
|
||||||
liveQuery(async () => {
|
liveQuery(async () => {
|
||||||
if (log_lvl) {
|
if (!slct__event_session_id) return [];
|
||||||
console.log(
|
|
||||||
`lq__event_presenter_obj_li: slct__event_session_id = ${slct__event_session_id}`
|
if (log_lvl > 1) {
|
||||||
);
|
console.log(`[LQ] Fetching presenters for session: ${slct__event_session_id}`);
|
||||||
}
|
}
|
||||||
let results = await db_events.presenter
|
|
||||||
|
return await db_events.presenter
|
||||||
.where('event_session_id')
|
.where('event_session_id')
|
||||||
.equals(slct__event_session_id)
|
.equals(slct__event_session_id)
|
||||||
.sortBy('full_name');
|
.sortBy('full_name');
|
||||||
if (log_lvl) {
|
|
||||||
console.log(`lq__event_presenter_obj_li: results = `, results);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if results are different than the current $events_slct.event_presenter_obj_li
|
|
||||||
if ($events_slct.event_presenter_obj_li && results) {
|
|
||||||
if (
|
|
||||||
JSON.stringify($events_slct.event_presenter_obj_li) !==
|
|
||||||
JSON.stringify(results)
|
|
||||||
) {
|
|
||||||
$events_slct.event_presenter_obj_li = { ...results };
|
|
||||||
if (log_lvl) {
|
|
||||||
console.log(
|
|
||||||
`$events_slct.event_presenter_obj_li has changed for event_session_id: ${slct__event_session_id}`,
|
|
||||||
$events_slct.event_presenter_obj_li
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (log_lvl) {
|
|
||||||
console.log(
|
|
||||||
`$events_slct.event_presenter_obj_li has not changed for event_session_id: ${slct__event_session_id}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
interface Props {
|
interface Props {
|
||||||
data_url: any;
|
|
||||||
slct__event_session_id?: null | boolean | string;
|
slct__event_session_id?: null | boolean | string;
|
||||||
loading__session_id_status?: null | boolean | string;
|
loading__session_id_status?: null | boolean | string;
|
||||||
// export let lq__event_session_obj: any;
|
// export let lq__event_session_obj: any;
|
||||||
@@ -13,7 +12,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
data_url,
|
|
||||||
slct__event_session_id = $bindable(null),
|
slct__event_session_id = $bindable(null),
|
||||||
loading__session_id_status = $bindable(null),
|
loading__session_id_status = $bindable(null),
|
||||||
lq__event_session_obj_li,
|
lq__event_session_obj_li,
|
||||||
@@ -25,6 +23,7 @@
|
|||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
// *** Import Svelte specific
|
// *** Import Svelte specific
|
||||||
|
import { untrack } from 'svelte';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
// import { liveQuery } from "dexie";
|
// import { liveQuery } from "dexie";
|
||||||
@@ -61,59 +60,54 @@
|
|||||||
slct__event_presentation_li: null
|
slct__event_presentation_li: null
|
||||||
});
|
});
|
||||||
|
|
||||||
let hover_timer_wait = 1250;
|
let hover_timer_wait = 750; // Optimized from 1250ms for better responsiveness
|
||||||
let hover_timer: any = $state(null);
|
let hover_timer: any = $state(null);
|
||||||
|
|
||||||
|
// Navigation Shield Pattern (Refactored 2026-02-11)
|
||||||
|
// WHY: We use untrack for store updates to prevent circular reactivity loops
|
||||||
|
// with the layout's sync effect. Standardizing on page.url ensures
|
||||||
|
// the back button and browser history work correctly.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (trigger_reload__event_session_obj_id) {
|
if (trigger_reload__event_session_obj_id) {
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
|
const event_session_id = String(trigger_reload__event_session_obj_id);
|
||||||
|
|
||||||
if (log_lvl) {
|
if (log_lvl) {
|
||||||
console.log(
|
console.log(`[UI Trace] trigger_reload changed to: ${event_session_id}`);
|
||||||
`[UI Trace] trigger_reload__event_session_obj_id changed: ${trigger_reload__event_session_obj_id}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let event_session_id = String(trigger_reload__event_session_obj_id);
|
|
||||||
trigger_reload__event_session_obj_id = false;
|
|
||||||
|
|
||||||
// OPTIMIZATION: Update slct__event_session_id prop (which is bound to the store)
|
|
||||||
// BEFORE calling goto. This triggers the LiveQuery in the layout instantly.
|
|
||||||
slct__event_session_id = event_session_id;
|
|
||||||
|
|
||||||
if (log_lvl)
|
|
||||||
console.log(
|
|
||||||
`[UI Trace] UI setting slct__event_session_id = ${slct__event_session_id} (+${(performance.now() - start).toFixed(2)}ms)`
|
|
||||||
);
|
|
||||||
|
|
||||||
handle_load_ae_obj_id__event_session(event_session_id);
|
|
||||||
|
|
||||||
if ($events_loc.launcher.controller == 'local_push') {
|
|
||||||
if (log_lvl)
|
|
||||||
console.log(
|
|
||||||
`[UI Trace] Local Push Controller Command: ae_load:event_session=${event_session_id}`
|
|
||||||
);
|
|
||||||
$events_sess.launcher.controller_cmd = `ae_load:event_session=${event_session_id}`;
|
|
||||||
$events_sess.launcher.controller_trigger_send = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_url_obj = new URL(page.url);
|
untrack(() => {
|
||||||
new_url_obj.searchParams.set('session_id', event_session_id);
|
// 1. Reset trigger immediately
|
||||||
|
trigger_reload__event_session_obj_id = false;
|
||||||
|
|
||||||
let new_url = new_url_obj.toString();
|
// 2. Local State Updates (Sync store for instant LiveQuery reaction)
|
||||||
if (log_lvl)
|
if (slct__event_session_id !== event_session_id) {
|
||||||
console.log(
|
slct__event_session_id = event_session_id;
|
||||||
`[UI Trace] Initiating SvelteKit goto... (+${(performance.now() - start).toFixed(2)}ms)`
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// Use noscroll and keepfocus to speed up the transition
|
// 3. Background Data Fetch
|
||||||
goto(new_url, {
|
handle_load_ae_obj_id__event_session(event_session_id);
|
||||||
replaceState: false,
|
|
||||||
noScroll: true,
|
// 4. Remote Control Sync
|
||||||
keepFocus: true
|
if ($events_loc.launcher.controller == 'local_push') {
|
||||||
}).then(() => {
|
$events_sess.launcher.controller_cmd = `ae_load:event_session=${event_session_id}`;
|
||||||
if (log_lvl)
|
$events_sess.launcher.controller_trigger_send = true;
|
||||||
console.log(
|
}
|
||||||
`🏁 [Trace] Total Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`
|
|
||||||
);
|
// 5. URL Navigation
|
||||||
|
let new_url_obj = new URL(page.url);
|
||||||
|
new_url_obj.searchParams.set('session_id', event_session_id);
|
||||||
|
|
||||||
|
if (log_lvl) console.log(`[UI Trace] Initiating SvelteKit goto...`);
|
||||||
|
|
||||||
|
goto(new_url_obj.toString(), {
|
||||||
|
replaceState: false,
|
||||||
|
noScroll: true,
|
||||||
|
keepFocus: true
|
||||||
|
}).then(() => {
|
||||||
|
if (log_lvl)
|
||||||
|
console.log(`🏁 [Trace] Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* src/routes/events/ae_comp__event_files_upload.svelte
|
* src/routes/events/ae_comp__event_files_upload.svelte
|
||||||
* Specialized component for uploading files and automatically linking them to Event objects.
|
* Specialized component for uploading files and automatically linking them to Event objects.
|
||||||
*/
|
*/
|
||||||
import { LoaderCircle } from 'lucide-svelte';
|
import { LoaderCircle, Upload, UploadCloud } from 'lucide-svelte';
|
||||||
import Element_input_files_tbl from '$lib/elements/element_input_files_tbl.svelte';
|
import Element_input_files_tbl from '$lib/elements/element_input_files_tbl.svelte';
|
||||||
|
|
||||||
// Import storage, functions, and libraries
|
// Import storage, functions, and libraries
|
||||||
@@ -67,6 +67,13 @@
|
|||||||
let ae_promises: key_val = $state({});
|
let ae_promises: key_val = $state({});
|
||||||
let input_element_id = 'ae_comp__event_files_upload__input';
|
let input_element_id = 'ae_comp__event_files_upload__input';
|
||||||
|
|
||||||
|
// WHY: Keep task_id in sync with prop if it hasn't been set by an upload cycle yet
|
||||||
|
$effect(() => {
|
||||||
|
if (!ae_promises.upload__hosted_file_obj) {
|
||||||
|
task_id = link_to_id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function prevent_default<T extends Event>(fn: (event: T) => void) {
|
function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||||
return function (event: T) {
|
return function (event: T) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -193,7 +200,7 @@
|
|||||||
>
|
>
|
||||||
{#await ae_promises.upload__hosted_file_obj}
|
{#await ae_promises.upload__hosted_file_obj}
|
||||||
<div class="text-lg flex flex-row gap-1 items-center justify-center">
|
<div class="text-lg flex flex-row gap-1 items-center justify-center">
|
||||||
<Lucide.LoaderCircle class="animate-spin m-1" />
|
<LoaderCircle class="animate-spin m-1" />
|
||||||
<span class="">
|
<span class="">
|
||||||
Uploading
|
Uploading
|
||||||
{#if $ae_sess.api_upload_kv[task_id]}
|
{#if $ae_sess.api_upload_kv[task_id]}
|
||||||
@@ -222,7 +229,7 @@
|
|||||||
>
|
>
|
||||||
{#if label}{@render label()}{:else}
|
{#if label}{@render label()}{:else}
|
||||||
<div class="flex items-center justify-center gap-2 mb-2 pt-2">
|
<div class="flex items-center justify-center gap-2 mb-2 pt-2">
|
||||||
<Lucide.Upload class="text-primary-500" />
|
<Upload class="text-primary-500" />
|
||||||
<strong class="preset-tonal-primary px-3 py-1 rounded-full"
|
<strong class="preset-tonal-primary px-3 py-1 rounded-full"
|
||||||
>Select Files</strong
|
>Select Files</strong
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user