diff --git a/src/lib/ae_events/ae_launcher__default_launch_profiles.ts b/src/lib/ae_events/ae_launcher__default_launch_profiles.ts index ac07c952..87e81168 100644 --- a/src/lib/ae_events/ae_launcher__default_launch_profiles.ts +++ b/src/lib/ae_events/ae_launcher__default_launch_profiles.ts @@ -345,6 +345,18 @@ end tell` display_mode: 'none' // No open_cmd — execution falls through to open_local_file_v2(path) // No post_script + }, + + // ------------------------------------------------------------------------- + // URL-type files: event_file.filename IS the URL (https://...) + // Opened via native.open_external({ url, app: 'chrome' }) — no local file involved. + // display_mode 'extend' is the default for URL presentations (e.g. Google Slides). + // ------------------------------------------------------------------------- + + url: { + app: 'Chrome', + display_mode: 'mirror' + // No open_cmd or post_script — URL branch in handle_open_file() handles this } }; diff --git a/src/routes/events/[event_id]/(launcher)/launcher_file_cont.svelte b/src/routes/events/[event_id]/(launcher)/launcher_file_cont.svelte index cc703d98..12d71813 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_file_cont.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_file_cont.svelte @@ -70,10 +70,12 @@ import { CalendarDays, FolderOpen, Laptop, + Link2, LoaderCircle, Monitor, Save, - Send + Send, + WifiOff } from '@lucide/svelte'; import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte'; @@ -98,6 +100,19 @@ let test_mode_popup_data: Record | null = $state(null); /** Simple promise-based delay for post-open script timing */ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); +/** True when the device has network connectivity. Updated reactively for URL-type files. */ +let is_online: boolean = $state(typeof navigator !== 'undefined' ? navigator.onLine : true); + +/** + * True when this file's filename IS a URL rather than a hosted filename. + * Convention: event_file.filename = 'https://...' or 'http://...' + * Use event_file.title as the human-readable label; extension = 'url' (or omitted). + */ +const is_url = $derived( + (event_file_obj?.filename ?? '').startsWith('https://') || + (event_file_obj?.filename ?? '').startsWith('http://') +); + let screen_saver_exts = ['jpg', 'png', 'PNG', 'webp']; /** @@ -127,6 +142,17 @@ onMount(() => { ...event_file_obj }; } + // Only register online/offline listeners for URL-type files — no point on file rows. + if (is_url && typeof window !== 'undefined') { + const on_online = () => (is_online = true); + const on_offline = () => (is_online = false); + window.addEventListener('online', on_online); + window.addEventListener('offline', on_offline); + return () => { + window.removeEventListener('online', on_online); + window.removeEventListener('offline', on_offline); + }; + } }); async function handle_open_file() { @@ -136,6 +162,76 @@ async function handle_open_file() { $events_slct.event_file_id = event_file_id; $events_slct.event_file_obj = event_file_obj; + // URL-TYPE FILE: event_file.filename is a URL (https://...), not a hosted file path. + // Handled entirely here — no cache, no download, no temp copy. + if (is_url) { + const url = event_file_obj.filename as string; + + // Test mode: show debug popup instead of opening + if ($events_loc.launcher.native_test_mode && $events_loc.launcher.app_mode === 'native') { + open_file_clicked = true; + open_file_status = 'opening_file'; + const profile = get_launch_profile('url', event_file_obj); + test_mode_popup_data = { + is_url: true, + filename: url, + extension: 'url', + title: event_file_obj.title || null, + hash_sha256: null, + simulated_temp_path: null, + profile, + open_cmd_resolved: `native.open_external({ url: "${url}", app: "chrome" })`, + display_override: event_file_obj?.cfg_json?.display_override ?? null, + cache_check: 'N/A — URL file', + copy_to_temp: 'N/A — URL file' + }; + test_mode_popup_open = true; + open_file_status = 'open'; + open_file_status_message = 'Test Mode: URL profile resolved — see popup'; + setTimeout(() => (open_file_clicked = false), 6000); + return true; + } + + // Offline guard: warn and abort before attempting to open + if (!is_online) { + open_file_clicked = true; + open_file_status = 'error'; + open_file_status_message = 'Network offline — cannot open URL'; + open_file_error_detail = `URL: ${url}`; + setTimeout(() => (open_file_clicked = false), 6000); + return false; + } + + open_file_clicked = true; + open_file_status = 'opening_file'; + const profile = get_launch_profile('url', event_file_obj); + + // URL presentations may still want to set display mode (e.g. Google Slides → extend) + if (profile.display_mode !== 'none') { + open_file_status_message = `Setting display (${profile.display_mode})...`; + await native.set_display_layout({ mode: profile.display_mode }).catch(() => { + /* No external display or displayplacer unconfigured — continue */ + }); + } + + open_file_status_message = `Opening ${event_file_obj.title || 'URL'}...`; + + if ($ae_loc.is_native && $events_loc.launcher.app_mode === 'native') { + // Native: open in Chrome for kiosk-style presentation; fall back to default browser + const result = await native.open_external({ url, app: 'chrome' }); + if (!result?.success) { + await native.open_external({ url, app: 'default' }).catch(() => {}); + } + } else { + window.open(url, '_blank', 'noopener,noreferrer'); + } + + open_file_status = 'open'; + open_file_status_message = `Opened: ${event_file_obj.title || url}`; + setTimeout(() => (open_file_clicked = false), 4000); + return true; + } + // 0. NATIVE TEST MODE — simulate full native flow, show debug popup instead of running commands // Active when native_test_mode toggle is on (regardless of is_native / app_mode). // Lets you preview the resolved profile, open command, and post-script from any device. @@ -491,17 +587,23 @@ function prevent_default(fn: (event: T) => void) { {/if} {:then result} - {@const FileIcon = - ae_util.file_extension_icon_lucide( - event_file_obj.extension - )} - - {event_file_obj.extension} - {#if result === null || result === false} - Failed! + {#if is_url} + + url + {#if !is_online}{/if} + {:else} + {@const FileIcon = + ae_util.file_extension_icon_lucide( + event_file_obj.extension + )} + + {event_file_obj.extension} + {#if result === null || result === false} + Failed! + {/if} {/if} {:catch error} (fn: (event: T) => void) { {ae_util.shorten_string({ - string: event_file_obj.filename_no_ext, + string: is_url + ? (event_file_obj.title || event_file_obj.filename) + : event_file_obj.filename_no_ext, begin_length: 45, max_length: 65 })} @@ -676,10 +780,18 @@ function prevent_default(fn: (event: T) => void) {
File
-
filename: {test_mode_popup_data.filename}
-
extension: {test_mode_popup_data.extension}
-
hash: {test_mode_popup_data.hash_sha256}
-
temp path: {test_mode_popup_data.simulated_temp_path}
+ {#if test_mode_popup_data.is_url} +
type: URL file (no local cache)
+
url: {test_mode_popup_data.filename}
+ {#if test_mode_popup_data.title} +
title: {test_mode_popup_data.title}
+ {/if} + {:else} +
filename: {test_mode_popup_data.filename}
+
extension: {test_mode_popup_data.extension}
+
hash: {test_mode_popup_data.hash_sha256}
+
temp path: {test_mode_popup_data.simulated_temp_path}
+ {/if}
@@ -687,8 +799,12 @@ function prevent_default(fn: (event: T) => void) {
Steps 1–2
-
check_hash_file_cache: {test_mode_popup_data.cache_check}
-
copy_from_cache_to_temp: {test_mode_popup_data.copy_to_temp}
+ {#if test_mode_popup_data.is_url} +
Skipped — URL file (no cache download or temp copy)
+ {:else} +
check_hash_file_cache: {test_mode_popup_data.cache_check}
+
copy_from_cache_to_temp: {test_mode_popup_data.copy_to_temp}
+ {/if}
@@ -721,7 +837,10 @@ function prevent_default(fn: (event: T) => void) {
Step 4 — Open File
- {#if test_mode_popup_data.open_cmd_resolved} + {#if test_mode_popup_data.is_url} +
native.open_external()
+
{test_mode_popup_data.open_cmd_resolved}
+ {:else if test_mode_popup_data.open_cmd_resolved}
native.run_cmd()
{test_mode_popup_data.open_cmd_resolved}
{:else}