Files
OSIT-AE-App-Svelte/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte
2026-03-24 12:13:59 -04:00

479 lines
23 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
interface Props {
slct__event_session_id?: string | null;
log_lvl?: number;
}
let {
slct__event_session_id = $bindable(null),
log_lvl = $bindable(1)
}: Props = $props();
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import Event_launcher_file_cont from './launcher_file_cont.svelte';
import Launcher_presentation_view from './launcher_presentation_view.svelte';
import Launcher_presenter_view from './launcher_presenter_view.svelte';
import Launcher_presenter_view_posters from './launcher_presenter_view_posters.svelte';
// WHY: Poster sessions get a dedicated card-grid view optimised for touch/PWA use.
import Launcher_session_view_posters from './launcher_session_view_posters.svelte';
import { liveQuery } from 'dexie';
// import { core_func } from '$lib/ae_core_functions';
// import { db_core } from "$lib/db_core";
import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import { db_events } from '$lib/ae_events/db_events';
import {
events_loc,
events_sess,
events_slct,
events_trigger
} from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events/ae_events_functions';
import {
AlertTriangle,
Archive,
Barcode,
Image,
LoaderCircle,
Monitor,
User,
Users
} from '@lucide/svelte';
// 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))
);
// WHY: type_code drives poster vs. oral UI branching throughout this component.
// It was previously a prop that was never passed by the parent, so all poster
// code paths were silently dead. Deriving it here from the session object
// ensures it always reflects the current session.
let type_code = $derived($lq__event_session_obj?.type_code ?? '');
// 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 (!slct__event_session_id) return [];
if (log_lvl > 1) {
console.log(
`[LQ] Fetching files for session: ${slct__event_session_id}`
);
}
return await db_events.file
.where('for_id')
.equals(slct__event_session_id)
.reverse()
.sortBy('created_on');
})
);
// Event Presentation
let lq__event_presentation_obj_li = $derived(
liveQuery(async () => {
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';
}
return await db_events.presentation
.where('event_session_id')
.equals(slct__event_session_id)
.sortBy(sort_by);
})
);
// Event Presenter
let lq__event_presenter_obj_li = $derived(
liveQuery(async () => {
if (!slct__event_session_id) return [];
if (log_lvl > 1) {
console.log(
`[LQ] Fetching presenters for session: ${slct__event_session_id}`
);
}
return await db_events.presenter
.where('event_session_id')
.equals(slct__event_session_id)
.sortBy('full_name');
})
);
// let show_modal_upload_files: boolean = false;
// let link_to_type: null|string = null;
// let link_to_id: null|string = null;
let ae_promises: key_val = $state({});
// $events_slct.id_li__event_presenter = [];
// await tick();
// ae_promises[slct__event_session_id] = events_func.load_ae_obj_li__event_presenter({
// api_cfg: $ae_api,
// for_obj_type: 'event_session',
// for_obj_id: slct__event_session_id,
// // inc_file_li: false,
// params: {qry__enabled: 'enabled', qry__limit: 550},
// try_cache: true,
// log_lvl: 1,
// })
// .then(async function (load_results) {
// console.log(`load_results = `, load_results);
// // let event_presenter_id_li = [];
// // let tmp_li = []; // This is to prevent the array from constantly updating and triggering the liveQuery.
// // for (let i = 0; i < load_results.length; i++) {
// // let event_presenter_obj = load_results[i];
// // let event_presenter_id = event_presenter_obj.event_presenter_id;
// // tmp_li.push(event_presenter_id);
// // }
// // event_presenter_id_li = tmp_li;
// // console.log(`event_presenter_id_li:`, event_presenter_id_li);
// // $events_slct.id_li__event_presenter = event_presenter_id_li;
// return load_results;
// });
</script>
<div
class="
event_launcher_session_view
relative h-full w-full
grow
space-y-1
">
<!-- <slot name="event_session_message">event session message</slot> -->
{#if $events_sess.launcher.loading__session_id_status}
<span class="absolute top-0 right-0 text-center text-sm text-gray-400">
<LoaderCircle size="1em" class="inline animate-spin" />
Loading session information...
</span>
<!-- {:else}
<span class="absolute top-0 right-0 text-sm text-center text-gray-400">
Session loaded?
</span> -->
{/if}
{#if $lq__event_session_obj && $lq__event_session_obj.event_session_id}
{#if type_code === 'poster'}
<!-- WHY: Poster sessions use a dedicated touch-first card-grid layout. -->
<Launcher_session_view_posters {slct__event_session_id} {log_lvl} />
{:else}
<!--
Session header: flex-col keeps datetime and name on separate rows so
the header height is predictable regardless of session name length.
Long names (300+ chars) are clamped to 2 lines; short names never
collapse the header below that height. Zero layout shift between sessions.
-->
<header
class="event_session_about flex flex-col items-stretch gap-0.5 border-b-2 border-gray-400 dark:border-gray-600">
<h3
class:hidden={!$lq__event_session_obj?.start_datetime ||
$events_loc.launcher.hide__session_datetimes}
class="event_session_datetimes text-center text-sm">
<button
type="button"
onclick={() => {
if (
$events_loc.launcher.time_format ==
'time_12_short'
) {
// $events_loc.launcher.datetime_format = 'datetime_long';
$events_loc.launcher.time_format = 'time_short';
$events_loc.launcher.time_hours = 24;
} else {
$events_loc.launcher.time_format =
'time_12_short';
// $events_loc.launcher.datetime_format = 'datetime_12_long';
$events_loc.launcher.time_hours = 12;
}
}}>
<strong
>{ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime,
'week_long'
)}</strong>
<span class="font-normal">
{ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime,
'date_long_month_day'
)}
</span>
<strong
>{ae_util.iso_datetime_formatter(
$lq__event_session_obj.start_datetime,
$events_loc.launcher.time_format
)}</strong>
<span class="font-normal">
{ae_util.iso_datetime_formatter(
$lq__event_session_obj.end_datetime,
$events_loc.launcher.time_format
)}
</span>
</button>
</h3>
<span
class="flex w-full flex-row items-center justify-between gap-2">
<!-- grow + line-clamp-2 = stable 2-line max; title provides full text for screen readers + hover -->
<h2
class="line-clamp-2 min-w-0 grow text-xl"
title={`Name: ${$lq__event_session_obj.name}\nType: ${$lq__event_session_obj.type_code} \nCode: ${$lq__event_session_obj.code} \nID: ${$lq__event_session_obj.event_session_id} \nStart Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, 'week_long')} ${ae_util.iso_datetime_formatter($lq__event_session_obj.start_datetime, $events_loc.launcher.time_format)} \nEnd Date/Time: ${ae_util.iso_datetime_formatter($lq__event_session_obj.end_datetime, $events_loc.launcher.time_format)}`}>
{$lq__event_session_obj?.name}
</h2>
{#if $lq__event_session_obj?.code}
<!-- shrink-0: code badge never gets squeezed by a long name -->
<span
class="shrink-0 p-1 text-base font-normal text-gray-500"
title="Session code {$lq__event_session_obj.code}">
<Barcode size="1em" class="inline" />
{$lq__event_session_obj?.code}
</span>
{/if}
</span>
</header>
<!-- <section class="event_session_description text-xs" class:d_none="{hide_description}">
{@html $lq__event_session_obj.description}
</section> -->
{#if $lq__event_session_obj?.file_count_all === 0}
<p class="text-center text-2xl font-bold text-red-500">
<AlertTriangle size="1em" class="inline" />
Warning
<AlertTriangle size="1em" class="inline" />
<br />
No files available show for this session.
</p>
{/if}
{#if $lq__event_file_obj_li && $lq__event_file_obj_li.length}
<section class="event_session_file_list">
<div>
<div class="text-surface-600-400 text-xs">
<strong>
<Archive size="1em" class="inline" />
Session Files:
<span
class:hidden={!$ae_loc.trusted_access ||
!$ae_loc.edit_mode}>
({$lq__event_file_obj_li?.length}&times;)
</span>
</strong>
</div>
<!-- {#if $ae_loc.trusted_access || $events_loc.launcher.trusted_access}
<button type="button"
type="button" class="ae_btn btn_outline_warning btn_xs" title="Upload updated or additional files"
>
<span class="fas fa-upload"></span> Upload Session File(s)
</button>
{/if} -->
</div>
<ul class="space-y-1">
{#each $lq__event_file_obj_li as event_file_obj (event_file_obj.event_file_id)}
<li
class="flex flex-row flex-wrap items-center justify-center gap-1"
class:hidden={!$events_loc.launcher
.show_content__hidden_files &&
event_file_obj.hide}>
<Event_launcher_file_cont
event_file_id={event_file_obj.event_file_id}
{event_file_obj}
hide_created_on={true}
show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode}
session_type={type_code || 'oral'}
open_method={type_code == 'poster'
? 'modal'
: null}
modal_title={$lq__event_session_obj?.name}
bind:modal__title={
$events_sess.launcher.modal__title
}
bind:modal__open_event_file_id={
$events_sess.launcher
.modal__open_event_file_id
}
bind:modal__event_file_obj={
$events_sess.launcher
.modal__event_file_obj
} />
<!-- <Launcher_file_cont {event_file_obj} hide_created_on={false} show_bak_download={($ae_loc.trusted_access || $events_loc.launcher.trusted_access)} open_file_as={$lq__event_session_obj.type_code} poster_title={$lq__event_session_obj.title} /> -->
<!-- <a
href="{$ae_api.base_url}/event/file/{event_file_obj.event_file_id}/download?filename={event_file_obj.filename}&key={$ae_api.account_id}"
class="btn btn-sm variant-soft-secondary m-0.5 *:hover:inline"
class:hidden={!ae_tmp.show__direct_download}
title={`Download this file:\n${event_file_obj.filename}\n[API] SHA256: ${event_file_obj.hash_sha256.slice(0, 10)}... Hosted ID: ${event_file_obj.hosted_file_id} Event File ID: ${event_file_obj.event_file_id}`}
>
<span class="fas fa-download mx-1"></span>
<div class="hidden">
Download
</div>
</a> -->
</li>
{/each}
</ul>
</section>
{/if}
<!-- <hr class="w-full border border-gray-200" /> -->
<section class="event_presentation_list">
<!-- {$lq__event_session_obj?.event_presentation_li?.length ?? 'loading...?'} -->
{#if $lq__event_presentation_obj_li}
<div class="text-surface-600-400 text-xs">
<strong>
{#if type_code == 'poster'}
<Image size="1em" class="inline" />
Posters:
{:else}
<Monitor size="1em" class="inline" />
Presentations:
{/if}
{#if $ae_loc.administrator_access && $ae_loc.edit_mode}
({$lq__event_presentation_obj_li?.length}&times;)
{/if}
</strong>
</div>
<!-- Maybe set max with? max-w-(--breakpoint-md) -->
<ul class="event_presentation_list max-w-full space-y-2">
{#each $lq__event_presentation_obj_li as event_presentation_obj (event_presentation_obj.event_presentation_id)}
<li
class="my-1 border-b-2 border-gray-300 py-1 text-center md:text-left dark:border-gray-700">
<!-- The presentation information -->
<div
class="event_presentation_datetime_name flex flex-row justify-evenly gap-4">
<!-- <div class="event_presentation_datetime_name"> -->
{#if event_presentation_obj?.start_datetime}
<span
class="event_presentation_datetime"
><strong
>{ae_util.iso_datetime_formatter(
event_presentation_obj?.start_datetime,
'time_12_short_no_leading'
)}</strong
></span>
{/if}
<span class="event_presentation_name grow"
>{event_presentation_obj?.name}</span>
<!-- </div> -->
<!-- Yes, this is kind of inefficient, but it works for now. -->
{#if $lq__event_presenter_obj_li && type_code == 'poster'}
{#each $lq__event_presenter_obj_li as event_presenter_obj, index (event_presenter_obj.event_presenter_id)}
{#if event_presenter_obj.event_presentation_id == event_presentation_obj.event_presentation_id}
<span
class="event_presentation_single_presenter text-sm text-gray-500 italic">
{#if $lq__event_presenter_obj_li[index]?.given_name && $lq__event_presenter_obj_li[index]?.given_name != 'Group'}
<User
size="0.85em"
class="inline" />
{$lq__event_presenter_obj_li[
index
]?.full_name}
{:else if $lq__event_presenter_obj_li[index]?.given_name == 'Group'}
<Users
size="0.85em"
class="inline" />
{$lq__event_presenter_obj_li[
index
]?.affiliations}
{:else}
--not set--
{/if}
</span>
{/if}
{/each}
{/if}
</div>
<!-- Presentation-level files -->
<Launcher_presentation_view
lq__event_presentation_obj={event_presentation_obj}
session_type={type_code} />
<!-- The presenter list -->
<!-- WHY: In poster mode, presenter names are already shown inline
in the presentation header above, so hide_name=true.
We still render Launcher_presenter_view_posters here because
some events store files at the PRESENTER level (for_id=event_presenter_id)
rather than the presentation level — particularly group/company presenters.
The component renders nothing if there are no presenter-level files,
so this has no visual cost for events that use presentation-level files. -->
{#if $lq__event_presenter_obj_li && $lq__event_presenter_obj_li.length}
<ul
class="event_presentation_presenter_list">
{#each $lq__event_presenter_obj_li as event_presenter_obj (event_presenter_obj.event_presenter_id)}
{#if event_presenter_obj.event_presentation_id == event_presentation_obj.event_presentation_id}
<li
class="
hover:bg-surface-100-900 hover:border-surface-400-600
rounded-lg
border
border-transparent
p-1
transition-all
">
{#if type_code == 'poster'}
<Launcher_presenter_view_posters
lq__event_presenter_obj={event_presenter_obj}
hide_name={true} />
{:else}
<Launcher_presenter_view
lq__event_presenter_obj={event_presenter_obj}
session_type={type_code} />
{/if}
</li>
{/if}
{/each}
</ul>
{/if}
</li>
{/each}
</ul>
{:else}
<p>No presentations available to display.</p>
{/if}
</section>
{/if}<!-- end type_code !== 'poster' -->
{:else}
<LoaderCircle size="1em" class="inline animate-spin" />
No session selected
{/if}
</div>
<style>
</style>