fix(launcher): open_in_os win routing, display override, and onsite ext fix
- open_in_os='win' now routes to Windows launch profiles (pptxwin/pptwin/odpwin/pdfwin) via WIN_EXTENSION_MAP in get_launch_profile() — was silently ignored before - Display override migrated from non-existent cfg_json backend field to localStorage ($events_loc.launcher.file_display_overrides) — only visible in edit mode; TODO added for proper backend column when event_file gains cfg_json - Onsite mode WIN extension rename now covers all 4 types (pptx, ppt, odp, pdf) instead of only pptx/ppt - open_in_os button shows LoaderCircle spinner during API call - Remove cfg_json from properties_to_save (column does not exist on event_file table) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -539,7 +539,6 @@ export const properties_to_save = [
|
||||
'filename',
|
||||
'extension',
|
||||
'open_in_os',
|
||||
'cfg_json',
|
||||
'lu_file_purpose_id',
|
||||
'lu_event_file_purpose_name',
|
||||
'file_purpose',
|
||||
|
||||
@@ -93,6 +93,14 @@ let open_file_status: null | string = $state(null);
|
||||
let open_file_status_message: null | string = $state(null);
|
||||
let open_file_error_detail: string | null = $state(null);
|
||||
|
||||
let open_in_os_loading: boolean = $state(false);
|
||||
|
||||
/** Reactive display override for this file — stored in $events_loc (localStorage) not in the backend. */
|
||||
const current_display_override = $derived.by(() => {
|
||||
const overrides = (($events_loc.launcher as Record<string, unknown>)?.file_display_overrides ?? {}) as Record<string, string>;
|
||||
return (overrides[event_file_id] ?? null) as 'extend' | 'mirror' | 'none' | null;
|
||||
});
|
||||
|
||||
/** State for the native test mode debug popup */
|
||||
let test_mode_popup_open: boolean = $state(false);
|
||||
let test_mode_popup_data: Record<string, any> | null = $state(null);
|
||||
@@ -135,7 +143,11 @@ function get_launch_profile(
|
||||
native_device?.launch_profiles ??
|
||||
null;
|
||||
const local_profiles = ($events_loc as any).launcher?.launch_profiles ?? null;
|
||||
const display_override = file_obj?.cfg_json?.display_override ?? null;
|
||||
// Display override is stored per-device in $events_loc — not in the backend (event_file has no JSON column).
|
||||
// This is intentional: display mode is a room/device preference, not a global file property.
|
||||
const launcher_kv = $events_loc.launcher as Record<string, unknown>;
|
||||
const file_display_overrides = (launcher_kv?.file_display_overrides ?? {}) as Record<string, string>;
|
||||
const display_override = (file_display_overrides[event_file_id] ?? null) as 'extend' | 'mirror' | 'none' | null;
|
||||
|
||||
// open_in_os = 'win' routes to the Windows-variant profile for apps that have one.
|
||||
// These profiles target Windows PowerPoint / LibreOffice / Acrobat running via Parallels or CrossOver.
|
||||
@@ -469,12 +481,11 @@ async function handle_open_file() {
|
||||
open_file_status_message = 'Downloading (Onsite Mode)...';
|
||||
open_file_error_detail = null;
|
||||
|
||||
// Append 'win' to the filename for extensions that have Windows file associations
|
||||
// (pptx→pptxwin, ppt→pptwin, odp→odpwin, pdf→pdfwin). Must match WIN_EXTENSION_MAP.
|
||||
const WIN_ONSITE_EXTS = ['pptx', 'ppt', 'odp', 'pdf'];
|
||||
let filename = event_file_obj.filename;
|
||||
if (
|
||||
(event_file_obj.extension === 'ppt' ||
|
||||
event_file_obj.extension === 'pptx') &&
|
||||
event_file_obj.open_in_os === 'win'
|
||||
) {
|
||||
if (event_file_obj.open_in_os === 'win' && WIN_ONSITE_EXTS.includes(event_file_obj.extension)) {
|
||||
filename = event_file_obj.filename + 'win';
|
||||
}
|
||||
|
||||
@@ -620,7 +631,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
click={handle_open_file}>
|
||||
{#snippet label()}
|
||||
{@const file_id = event_file_obj.hosted_file_id}
|
||||
<span class="shrink border-r border-gray-400 pr-1 text-xs">
|
||||
<span class="shrink border-r border-surface-300-700 pr-1 text-xs">
|
||||
{#await ae_promises[event_file_id]}
|
||||
<LoaderCircle
|
||||
size="1em"
|
||||
@@ -634,26 +645,28 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
{/if}
|
||||
</span>
|
||||
{:then result}
|
||||
<span class=" font-mono">
|
||||
{#if is_url}
|
||||
<Link2 size="1em" class="mx-0.5 inline {!is_online ? 'text-warning-500' : ''}" />
|
||||
<span class:text-warning-500={!is_online}>url</span>
|
||||
{#if !is_online}<WifiOff size="0.85em" class="mx-0.5 inline text-warning-500" title="Network offline" />{/if}
|
||||
<Link2 size="1em" class="inline opacity-50 {!is_online ? 'text-warning-900-100' : ''}" />
|
||||
<span class:text-warning-900-100={!is_online}>url</span>
|
||||
{#if !is_online}<WifiOff size="0.85em" class="inline text-warning-900-100" title="Network offline" />{/if}
|
||||
{:else}
|
||||
{@const FileIcon =
|
||||
ae_util.file_extension_icon_lucide(
|
||||
event_file_obj.extension
|
||||
)}
|
||||
<FileIcon size="1em" class="mx-0.5 inline" />
|
||||
<FileIcon size="1em" class="inline opacity-50" />
|
||||
{event_file_obj.extension}
|
||||
{#if result === null || result === false}
|
||||
<span class="text-error-500"
|
||||
<span class="text-error-900-100"
|
||||
><TriangleAlert
|
||||
size="1em"
|
||||
class="mx-1 inline" />Failed!</span>
|
||||
class="inline" />Failed!</span>
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
{:catch error}
|
||||
<span class="text-error-500" title={error?.message}
|
||||
<span class="text-error-900-100" title={error?.message}
|
||||
><AlertCircle
|
||||
size="1em"
|
||||
class="mx-0.5 inline" />Error!</span>
|
||||
@@ -687,79 +700,71 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
<button
|
||||
type="button"
|
||||
onclick={async () => {
|
||||
let new_val: string | null;
|
||||
if (!event_file_obj?.open_in_os) new_val = 'win';
|
||||
else if (event_file_obj?.open_in_os == 'win') new_val = 'mac';
|
||||
else new_val = null;
|
||||
await api.update_ae_obj({
|
||||
api_cfg: $ae_api,
|
||||
obj_type: 'event_file',
|
||||
obj_id: event_file_id,
|
||||
fields: { open_in_os: new_val }
|
||||
});
|
||||
events_func.load_ae_obj_id__event_file({
|
||||
api_cfg: $ae_api,
|
||||
event_file_id: event_file_obj?.event_file_id,
|
||||
log_lvl
|
||||
});
|
||||
}}
|
||||
class="btn btn-sm group transition-all"
|
||||
class:preset-tonal-warning={event_file_obj?.open_in_os == 'win'}
|
||||
class:preset-tonal-success={event_file_obj?.open_in_os == 'mac'}
|
||||
disabled={!$ae_loc.trusted_access}
|
||||
title={`Open in OS: ${
|
||||
event_file_obj?.open_in_os
|
||||
? event_file_obj.open_in_os.toUpperCase()
|
||||
: 'None'
|
||||
}`}
|
||||
>
|
||||
{#if event_file_obj?.open_in_os == 'win'}
|
||||
<!-- <Monitor
|
||||
size="1em"
|
||||
class="m-1" /> -->
|
||||
Win
|
||||
{:else if event_file_obj?.open_in_os == 'mac'}
|
||||
<!-- <Laptop
|
||||
size="1em"
|
||||
class="m-1" /> -->
|
||||
Mac
|
||||
{:else}
|
||||
<FolderOpen size="1em" class="m-1" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if $ae_loc.trusted_access}
|
||||
<!-- Display override: per-file display_mode override for this file only.
|
||||
null = use profile default, 'extend' = force extend, 'mirror' = force mirror.
|
||||
Stored in event_file.cfg_json.display_override. Cycles null → extend → mirror → null.
|
||||
Settable from any device — takes effect when the file is opened in native mode. -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={async () => {
|
||||
const cur = event_file_obj?.cfg_json?.display_override ?? null;
|
||||
const next: string | null = !cur ? 'extend' : cur === 'extend' ? 'mirror' : null;
|
||||
const new_cfg = { ...(event_file_obj.cfg_json ?? {}), display_override: next };
|
||||
// Optimistic update — don't wait for the liveQuery round-trip
|
||||
event_file_obj = { ...event_file_obj, cfg_json: new_cfg };
|
||||
open_in_os_loading = true;
|
||||
try {
|
||||
let new_val: string | null;
|
||||
if (!event_file_obj?.open_in_os) new_val = 'win';
|
||||
else if (event_file_obj?.open_in_os == 'win') new_val = 'mac';
|
||||
else new_val = null;
|
||||
await api.update_ae_obj({
|
||||
api_cfg: $ae_api,
|
||||
obj_type: 'event_file',
|
||||
obj_id: event_file_id,
|
||||
fields: { cfg_json: new_cfg }
|
||||
fields: { open_in_os: new_val }
|
||||
});
|
||||
events_func.load_ae_obj_id__event_file({
|
||||
api_cfg: $ae_api,
|
||||
event_file_id: event_file_obj?.event_file_id,
|
||||
log_lvl
|
||||
});
|
||||
} finally {
|
||||
open_in_os_loading = false;
|
||||
}
|
||||
}}
|
||||
class="btn btn-sm group transition-all"
|
||||
class:preset-tonal-warning={event_file_obj?.open_in_os == 'win'}
|
||||
class:preset-tonal-success={event_file_obj?.open_in_os == 'mac'}
|
||||
disabled={!$ae_loc.trusted_access || open_in_os_loading}
|
||||
title={`Open in OS: ${
|
||||
event_file_obj?.open_in_os == 'win' ? 'Windows' : event_file_obj?.open_in_os == 'mac' ? 'macOS' : event_file_obj?.open_in_os == 'linux' ? 'Linux' : '--not set--'
|
||||
}`}
|
||||
>
|
||||
{#if open_in_os_loading}
|
||||
<LoaderCircle size="1em" class="m-1 animate-spin" />
|
||||
{:else if event_file_obj?.open_in_os == 'win'}
|
||||
Win
|
||||
{:else if event_file_obj?.open_in_os == 'mac'}
|
||||
Mac
|
||||
{:else}
|
||||
<FolderOpen size="1em" class="m-1" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if $ae_loc.edit_mode}
|
||||
<!-- Display override (temporary — local/device only, stored in $events_loc).
|
||||
Cycles null → extend → mirror → null. Instant write, no API call.
|
||||
TODO: replace with backend cfg_json once event_file gains a JSON column. -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
const cur = current_display_override;
|
||||
const next: 'extend' | 'mirror' | null = !cur ? 'extend' : cur === 'extend' ? 'mirror' : null;
|
||||
const launcher = $events_loc.launcher as Record<string, unknown>;
|
||||
const new_overrides = { ...((launcher?.file_display_overrides ?? {}) as Record<string, string>) };
|
||||
if (next === null) {
|
||||
delete new_overrides[event_file_id];
|
||||
} else {
|
||||
new_overrides[event_file_id] = next;
|
||||
}
|
||||
launcher.file_display_overrides = new_overrides;
|
||||
}}
|
||||
class="btn btn-sm transition-all"
|
||||
class:preset-tonal-primary={event_file_obj?.cfg_json?.display_override === 'extend'}
|
||||
class:preset-tonal-warning={event_file_obj?.cfg_json?.display_override === 'mirror'}
|
||||
title={`Display override: ${event_file_obj?.cfg_json?.display_override ?? 'default'}`}>
|
||||
{#if event_file_obj?.cfg_json?.display_override === 'extend'}
|
||||
class:preset-tonal-primary={current_display_override === 'extend'}
|
||||
class:preset-tonal-warning={current_display_override === 'mirror'}
|
||||
title={`Display override: ${current_display_override ?? 'default'}`}>
|
||||
{#if current_display_override === 'extend'}
|
||||
Ext
|
||||
{:else if event_file_obj?.cfg_json?.display_override === 'mirror'}
|
||||
{:else if current_display_override === 'mirror'}
|
||||
Mir
|
||||
{:else}
|
||||
<Monitor size="1em" class="m-1" />
|
||||
@@ -769,8 +774,13 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
|
||||
<span
|
||||
class="event_file_created_on preset-filled-surface-100-900 flex w-24 flex-row items-center justify-end gap-1 rounded px-1 py-0.5 text-center text-xs md:w-44"
|
||||
class:hidden={hide_created_on}>
|
||||
<CalendarDays size="0.85em" class="inline" />
|
||||
class:hidden={hide_created_on}
|
||||
title={`Created on:\n${ae_util.iso_datetime_formatter(
|
||||
event_file_obj.created_on,
|
||||
'datetime_long'
|
||||
)}`}
|
||||
>
|
||||
<CalendarDays size="0.85em" class="inline opacity-50" />
|
||||
<span class="w-18"
|
||||
>{ae_util.iso_datetime_formatter(
|
||||
event_file_obj.created_on,
|
||||
@@ -780,8 +790,10 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
|
||||
<span
|
||||
class="event_file_size preset-filled-surface-100-900 flex min-w-20 w-22 max-w-28 flex-row items-center justify-end gap-1 rounded py-0.5 text-center text-xs"
|
||||
class:hidden={hide_size}>
|
||||
<Save size="0.85em" class="inline" />
|
||||
class:hidden={hide_size}
|
||||
title={`File size:\n${event_file_obj.file_size ? ae_util.format_bytes(event_file_obj.file_size) : 'Unknown size'}\nBytes: ${event_file_obj.file_size}`}
|
||||
>
|
||||
<Save size="0.85em" class="inline opacity-50" />
|
||||
{#if event_file_obj.file_size}{ae_util.format_bytes(
|
||||
event_file_obj.file_size
|
||||
)}{/if}
|
||||
|
||||
Reference in New Issue
Block a user