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(() => {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -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`);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user