Launcher: Resolved session selection hang and improved reactivity pattern.

This commit is contained in:
Scott Idem
2026-02-11 17:18:55 -05:00
parent cda7a5421c
commit 7549749d14
4 changed files with 95 additions and 189 deletions

View File

@@ -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(() => {
if (log_lvl) {
console.log(`event_id: ${data.params.event_id}`);
console.log(`event_location_id: ${data.params.event_location_id}`);
console.log(
`event_session_id: ${data.url.searchParams.get('session_id')}`
);
const url_session_id = data.url.searchParams.get('session_id');
const path_location_id = data.params.event_location_id;
const path_event_id = data.params.event_id;
if (log_lvl > 1) {
console.log(`[Launcher Sync] URL Change: event=${path_event_id}, loc=${path_location_id}, sess=${url_session_id}`);
}
untrack(() => {
$events_slct.event_id = data.params.event_id;
$events_slct.event_location_id = data.params.event_location_id;
$events_slct.event_session_id = data.url.searchParams.get('session_id');
if ($events_slct.event_id !== path_event_id) {
$events_slct.event_id = path_event_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>
{/if}
{#if $events_slct.event_session_id && $lq__event_session_obj}
{#if $events_slct.event_session_id}
<Launcher_session_view
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>
{:else if $events_slct.event_session_id}
<div

View File

@@ -42,168 +42,63 @@
import { events_func } from '$lib/ae_events_functions';
// Event Session (Main View Trigger)
// WHY: We use a simple derived observable. The template handles the $ prefix.
let lq__event_session_obj = $derived(
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)
// 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(
liveQuery(async () => {
if (log_lvl) {
console.log(
`lq__event_file_obj: event_file_id = ${$events_slct?.event_file_id}`
);
if (!slct__event_session_id) return [];
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')
.equals(slct__event_session_id)
.reverse() // Need reverse for created_on newest first.
.sortBy('created_on'); // or filename
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;
.reverse()
.sortBy('created_on');
})
);
// 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
// let lq__event_presentation_obj = liveQuery(
// () => db_events.presentation
// .get($events_slct.event_presentation_id)
// );
let lq__event_presentation_obj_li = $derived(
liveQuery(async () => {
if (log_lvl) {
console.log(
`lq__event_presentation_obj_li: slct__event_session_id = ${slct__event_session_id}`
);
if (!slct__event_session_id) return [];
if (log_lvl > 1) {
console.log(`[LQ] Fetching presentations for session: ${slct__event_session_id}`);
}
let sort_by = 'start_datetime';
if (type_code == 'poster') {
sort_by = 'name';
}
let results = await db_events.presentation
return await db_events.presentation
.where('event_session_id')
.equals(slct__event_session_id)
.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.
// let lq_get__event_presentation_obj_li = liveQuery(
// () => db_events.presentation
// .bulkGet($events_slct.id_li__event_presentation)
// );
// Event Presenter
let lq__event_presenter_obj_li = $derived(
liveQuery(async () => {
if (log_lvl) {
console.log(
`lq__event_presenter_obj_li: slct__event_session_id = ${slct__event_session_id}`
);
if (!slct__event_session_id) return [];
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')
.equals(slct__event_session_id)
.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;
})
);

View File

@@ -1,6 +1,5 @@
<script lang="ts">
interface Props {
data_url: any;
slct__event_session_id?: null | boolean | string;
loading__session_id_status?: null | boolean | string;
// export let lq__event_session_obj: any;
@@ -13,7 +12,6 @@
}
let {
data_url,
slct__event_session_id = $bindable(null),
loading__session_id_status = $bindable(null),
lq__event_session_obj_li,
@@ -25,6 +23,7 @@
}: Props = $props();
// *** Import Svelte specific
import { untrack } from 'svelte';
import { page } from '$app/state';
import { goto } from '$app/navigation';
// import { liveQuery } from "dexie";
@@ -61,59 +60,54 @@
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);
// 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(() => {
if (trigger_reload__event_session_obj_id) {
const start = performance.now();
const event_session_id = String(trigger_reload__event_session_obj_id);
if (log_lvl) {
console.log(
`[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;
console.log(`[UI Trace] trigger_reload changed to: ${event_session_id}`);
}
let new_url_obj = new URL(page.url);
new_url_obj.searchParams.set('session_id', event_session_id);
untrack(() => {
// 1. Reset trigger immediately
trigger_reload__event_session_obj_id = false;
let new_url = new_url_obj.toString();
if (log_lvl)
console.log(
`[UI Trace] Initiating SvelteKit goto... (+${(performance.now() - start).toFixed(2)}ms)`
);
// 2. Local State Updates (Sync store for instant LiveQuery reaction)
if (slct__event_session_id !== event_session_id) {
slct__event_session_id = event_session_id;
}
// Use noscroll and keepfocus to speed up the transition
goto(new_url, {
replaceState: false,
noScroll: true,
keepFocus: true
}).then(() => {
if (log_lvl)
console.log(
`🏁 [Trace] Total Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`
);
// 3. Background Data Fetch
handle_load_ae_obj_id__event_session(event_session_id);
// 4. Remote Control Sync
if ($events_loc.launcher.controller == 'local_push') {
$events_sess.launcher.controller_cmd = `ae_load:event_session=${event_session_id}`;
$events_sess.launcher.controller_trigger_send = true;
}
// 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`);
});
});
}
});

View File

@@ -3,7 +3,7 @@
* src/routes/events/ae_comp__event_files_upload.svelte
* 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 storage, functions, and libraries
@@ -67,6 +67,13 @@
let ae_promises: key_val = $state({});
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) {
return function (event: T) {
event.preventDefault();
@@ -193,7 +200,7 @@
>
{#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" />
<LoaderCircle class="animate-spin m-1" />
<span class="">
Uploading
{#if $ae_sess.api_upload_kv[task_id]}
@@ -222,7 +229,7 @@
>
{#if label}{@render label()}{:else}
<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"
>Select Files</strong
>