feat(launcher): implement LaunchProfile system — MasterKey replacement
- Add ae_launcher__default_launch_profiles.ts with LaunchProfile interface, DEFAULT_LAUNCH_PROFILES constant, and resolve_launch_profile() helper. Covers pptx/ppt/key/odp/pdf, all VLC media formats, Windows/Parallels variants (pptxwin/pptwin/odpwin/pdfwin), and a catch-all 'default'. - Replace get_launch_script_template() with get_launch_profile() in launcher_file_cont.svelte. Override priority: device API config > local persistent config > built-in defaults > 'default' catch-all. - Rewrite handle_open_file() native branch with 9-step profile-driven flow: copy_from_cache_to_temp → resolve profile → set_display_layout (silent fail) → open (run_cmd or OS default) → sleep(post_delay_ms) → run post_script → fallback to OS default on open failure → surface status/error detail. - Add open_file_error_detail state var; show error pre block in status alert for native error state, show fallback note for fallback state. - Add display override toggle button in event_file_meta (visible when trusted_access + is_native): cycles null → extend → mirror → null, PATCHes event_file.cfg_json.display_override via V3 CRUD.
This commit is contained in:
382
src/lib/ae_events/ae_launcher__default_launch_profiles.ts
Normal file
382
src/lib/ae_events/ae_launcher__default_launch_profiles.ts
Normal file
@@ -0,0 +1,382 @@
|
||||
/**
|
||||
* ae_launcher__default_launch_profiles.ts
|
||||
*
|
||||
* Built-in launch profiles for the Aether Events Launcher — the Svelte-side
|
||||
* replacement for the legacy OSIT MasterKey Swift app.
|
||||
*
|
||||
* These are the last-resort defaults. Override priority (high → low):
|
||||
* 1. event_file.cfg_json.display_override — per-file, display_mode only
|
||||
* 2. event_device.data_json.launch_profiles[ext] — full profile, per device (API)
|
||||
* 3. $events_loc.launcher.launch_profiles[ext] — local persistent override
|
||||
* 4. DEFAULT_LAUNCH_PROFILES[ext] — these built-ins
|
||||
* 5. DEFAULT_LAUNCH_PROFILES['default'] — catch-all
|
||||
*
|
||||
* Keys are lowercase file extensions without the dot: "pptx", "key", "pdf", etc.
|
||||
* The special key "default" catches any unrecognised extension.
|
||||
*
|
||||
* post_script formats:
|
||||
* - Plain string → run as AppleScript via run_osascript() (macOS only)
|
||||
* - "shell:..." prefix → run as shell command via run_cmd()
|
||||
*
|
||||
* Reserved for future use (not yet read anywhere):
|
||||
* - speed_factor: number — delay multiplier for slower machines (1.0 = normal)
|
||||
* - url: string — for URL-type presentations (e.g. Google Slides)
|
||||
*/
|
||||
|
||||
export interface LaunchProfile {
|
||||
/** Human-readable label for status messages */
|
||||
app: string;
|
||||
/** Display layout to set before opening. 'extend' only applied if external display found. */
|
||||
display_mode: 'extend' | 'mirror' | 'none';
|
||||
/**
|
||||
* Shell command to open the file. {{path}} is replaced with the resolved temp path.
|
||||
* If omitted, falls back to open_local_file_v2(path) — OS default handler.
|
||||
*/
|
||||
open_cmd?: string;
|
||||
/**
|
||||
* Script to run after the file opens and post_delay_ms has elapsed.
|
||||
* Plain string → AppleScript (macOS). "shell:" prefix → shell command.
|
||||
*/
|
||||
post_script?: string;
|
||||
/** Milliseconds to wait after open_cmd before running post_script. Default: 2000 */
|
||||
post_delay_ms?: number;
|
||||
|
||||
// --- Reserved for future use — not yet implemented ---
|
||||
// speed_factor?: number;
|
||||
// url?: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_LAUNCH_PROFILES: Record<string, LaunchProfile> = {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// macOS presentation formats
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
pptx: {
|
||||
app: 'Microsoft PowerPoint',
|
||||
display_mode: 'extend',
|
||||
open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "Microsoft PowerPoint"
|
||||
keystroke return using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
ppt: {
|
||||
app: 'Microsoft PowerPoint',
|
||||
display_mode: 'extend',
|
||||
open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "Microsoft PowerPoint"
|
||||
keystroke return using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
key: {
|
||||
app: 'Keynote',
|
||||
display_mode: 'extend',
|
||||
open_cmd: 'open -a "Keynote" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "Keynote"
|
||||
activate
|
||||
start (front document)
|
||||
end tell`
|
||||
},
|
||||
|
||||
odp: {
|
||||
app: 'LibreOffice',
|
||||
display_mode: 'extend',
|
||||
open_cmd: 'open -a "LibreOffice" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "LibreOffice"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "soffice"
|
||||
key code 96
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
pdf: {
|
||||
app: 'Adobe Acrobat Reader DC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "Adobe Acrobat Reader DC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "Adobe Acrobat Reader DC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "AdobeReader"
|
||||
keystroke "l" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Media (VLC) — mirror display
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
mp4: {
|
||||
app: 'VLC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "VLC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "VLC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "VLC"
|
||||
keystroke "f" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
mkv: {
|
||||
app: 'VLC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "VLC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "VLC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "VLC"
|
||||
keystroke "f" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
mov: {
|
||||
app: 'VLC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "VLC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "VLC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "VLC"
|
||||
keystroke "f" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
mpeg: {
|
||||
app: 'VLC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "VLC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "VLC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "VLC"
|
||||
keystroke "f" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
avi: {
|
||||
app: 'VLC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "VLC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "VLC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "VLC"
|
||||
keystroke "f" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
flv: {
|
||||
app: 'VLC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "VLC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "VLC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "VLC"
|
||||
keystroke "f" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
ogg: {
|
||||
app: 'VLC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "VLC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "VLC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "VLC"
|
||||
keystroke "f" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
ogv: {
|
||||
app: 'VLC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "VLC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "VLC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "VLC"
|
||||
keystroke "f" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
mp3: {
|
||||
app: 'VLC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "VLC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "VLC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "VLC"
|
||||
keystroke "f" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
wmv: {
|
||||
app: 'VLC',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "VLC" "{{path}}"',
|
||||
post_delay_ms: 2000,
|
||||
post_script: `tell application "VLC"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "VLC"
|
||||
keystroke "f" using command down
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Windows / Parallels variants — longer post_delay_ms (Parallels needs more time)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
pptxwin: {
|
||||
app: 'Microsoft Office PowerPoint (Windows)',
|
||||
display_mode: 'extend',
|
||||
open_cmd: 'open -a "Microsoft Office PowerPoint" "{{path}}"',
|
||||
post_delay_ms: 3000,
|
||||
post_script: `tell application "Microsoft Office PowerPoint"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
key code 96
|
||||
end tell`
|
||||
},
|
||||
|
||||
pptwin: {
|
||||
app: 'Microsoft Office PowerPoint (Windows)',
|
||||
display_mode: 'extend',
|
||||
open_cmd: 'open -a "Microsoft Office PowerPoint" "{{path}}"',
|
||||
post_delay_ms: 3000,
|
||||
post_script: `tell application "Microsoft Office PowerPoint"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
key code 96
|
||||
end tell`
|
||||
},
|
||||
|
||||
odpwin: {
|
||||
app: 'LibreOffice (Windows)',
|
||||
display_mode: 'extend',
|
||||
open_cmd: 'open -a "LibreOffice" "{{path}}"',
|
||||
post_delay_ms: 3000,
|
||||
post_script: `tell application "LibreOffice"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
tell process "soffice"
|
||||
key code 96
|
||||
end tell
|
||||
end tell`
|
||||
},
|
||||
|
||||
pdfwin: {
|
||||
app: 'Acrobat Reader (Windows)',
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "Acrobat Reader Windows" "{{path}}"',
|
||||
post_delay_ms: 3000,
|
||||
post_script: `tell application "Acrobat Reader Windows"
|
||||
activate
|
||||
end tell
|
||||
tell application "System Events"
|
||||
key code 108 using control down
|
||||
end tell`
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Catch-all — OS default handler, no display change
|
||||
// Works on macOS (open) and Linux (xdg-open via open_local_file_v2)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
default: {
|
||||
app: 'OS Default',
|
||||
display_mode: 'none'
|
||||
// No open_cmd — execution falls through to open_local_file_v2(path)
|
||||
// No post_script
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a shallow copy of the built-in profile for the given extension,
|
||||
* with a display_override applied if provided.
|
||||
*
|
||||
* Falls back to 'default' if no specific profile exists.
|
||||
*/
|
||||
export function resolve_launch_profile(
|
||||
extension: string,
|
||||
display_override?: 'extend' | 'mirror' | 'none' | null,
|
||||
device_profiles?: Record<string, LaunchProfile> | null,
|
||||
local_profiles?: Record<string, LaunchProfile> | null
|
||||
): LaunchProfile {
|
||||
const ext = (extension || '').toLowerCase().replace(/^\./, '');
|
||||
|
||||
// Priority: device config → local config → built-ins → default
|
||||
const source =
|
||||
device_profiles?.[ext] ??
|
||||
device_profiles?.['default'] ??
|
||||
local_profiles?.[ext] ??
|
||||
local_profiles?.['default'] ??
|
||||
DEFAULT_LAUNCH_PROFILES[ext] ??
|
||||
DEFAULT_LAUNCH_PROFILES['default'];
|
||||
|
||||
const profile = { ...source };
|
||||
|
||||
// Per-file display override wins over everything
|
||||
if (display_override) {
|
||||
profile.display_mode = display_override;
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
@@ -79,48 +79,40 @@ import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_f
|
||||
|
||||
// Import the relay
|
||||
import * as native from '$lib/electron/electron_relay';
|
||||
import {
|
||||
type LaunchProfile,
|
||||
resolve_launch_profile
|
||||
} from '$lib/ae_events/ae_launcher__default_launch_profiles';
|
||||
|
||||
let ae_promises: key_val = $state({});
|
||||
|
||||
let open_file_clicked: null | boolean = $state(null);
|
||||
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);
|
||||
|
||||
/** Simple promise-based delay for post-open script timing */
|
||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
let screen_saver_exts = ['jpg', 'png', 'PNG', 'webp'];
|
||||
|
||||
/**
|
||||
* Resolves a data-driven launch script template for a given file extension.
|
||||
* Checked in priority order:
|
||||
* 1. event_device.data_json.launch_scripts (API-driven, per-device, most specific)
|
||||
* 2. $events_loc.launcher.launch_scripts (local persistent override)
|
||||
* Keys are lowercase extensions without the dot ("pptx", "key", "pdf", etc.).
|
||||
* A "default" key acts as a catch-all for unrecognised extensions.
|
||||
*
|
||||
* Returns null when no config is found → Electron falls back to its hardcoded defaults.
|
||||
*
|
||||
* Template formats:
|
||||
* - AppleScript (macOS): plain string with {{path}} placeholder
|
||||
* - Shell command: prefix with "shell:" → "shell:open \"{{path}}\""
|
||||
* Resolves the LaunchProfile for a given file extension and optional per-file
|
||||
* display override. Checked in priority order:
|
||||
* 1. event_device.data_json.launch_profiles (API-driven, per-device)
|
||||
* 2. $events_loc.launcher.launch_profiles (local persistent override)
|
||||
* 3. DEFAULT_LAUNCH_PROFILES[ext] (Svelte built-in defaults)
|
||||
* 4. DEFAULT_LAUNCH_PROFILES['default'] (catch-all)
|
||||
* Per-file display_override from event_file.cfg_json overrides display_mode only.
|
||||
*/
|
||||
function get_launch_script_template(extension: string): string | null {
|
||||
const ext = (extension || '').toLowerCase().replace('.', '');
|
||||
|
||||
// 1. Device-level config (from API, per device — highest priority)
|
||||
const device_scripts = ($ae_loc as any).native_device?.launch_scripts;
|
||||
if (device_scripts) {
|
||||
if (device_scripts[ext]) return device_scripts[ext];
|
||||
if (device_scripts['default']) return device_scripts['default'];
|
||||
}
|
||||
|
||||
// 2. Launcher local config override (set manually via Launcher config UI)
|
||||
const local_scripts = ($events_loc as any).launcher?.launch_scripts;
|
||||
if (local_scripts) {
|
||||
if (local_scripts[ext]) return local_scripts[ext];
|
||||
if (local_scripts['default']) return local_scripts['default'];
|
||||
}
|
||||
|
||||
// 3. No override — let Electron use its built-in hardcoded defaults
|
||||
return null;
|
||||
function get_launch_profile(
|
||||
extension: string,
|
||||
file_obj?: any
|
||||
): LaunchProfile {
|
||||
const device_profiles = ($ae_loc as any).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;
|
||||
return resolve_launch_profile(extension, display_override, device_profiles, local_profiles);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@@ -178,29 +170,115 @@ async function handle_open_file() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Step 1: Copy cached file to a writable temp path ---
|
||||
open_file_status = 'opening_file';
|
||||
open_file_status_message = 'Opening Application';
|
||||
open_file_status_message = 'Preparing file...';
|
||||
open_file_error_detail = null;
|
||||
|
||||
// Phase 2/5: Use the atomic copy-and-launch operation.
|
||||
// The main process handler (file_handlers.ts) now handles the
|
||||
// specialized LibreOffice/AppleScript logic internally after copying.
|
||||
// script_template is null when no device/local config exists → Electron uses hardcoded defaults.
|
||||
const script_template = get_launch_script_template(event_file_obj.extension);
|
||||
const launch_result = await native.launch_from_cache({
|
||||
const copy_result = await native.copy_from_cache_to_temp({
|
||||
cache_root,
|
||||
hash: event_file_obj.hash_sha256,
|
||||
temp_root,
|
||||
filename: event_file_obj.filename,
|
||||
script_template
|
||||
filename: event_file_obj.filename
|
||||
});
|
||||
|
||||
if (!launch_result.success) {
|
||||
if (!copy_result.success || !copy_result.path) {
|
||||
open_file_status = 'error';
|
||||
open_file_status_message = `Failed to open: ${launch_result.error}`;
|
||||
open_file_status_message = 'Failed to prepare file';
|
||||
open_file_error_detail = copy_result.error ?? 'copy_from_cache_to_temp returned no path';
|
||||
setTimeout(() => (open_file_clicked = false), 6000);
|
||||
return false;
|
||||
}
|
||||
|
||||
const resolved_path = copy_result.path;
|
||||
|
||||
// --- Step 2: Resolve launch profile ---
|
||||
const profile = get_launch_profile(event_file_obj.extension, event_file_obj);
|
||||
if (log_lvl) console.log('LaunchProfile:', profile);
|
||||
|
||||
// --- Step 3: Set display layout (skip silently on failure / no external display) ---
|
||||
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 unavailable — continue */
|
||||
});
|
||||
}
|
||||
|
||||
// --- Step 4: Open the file ---
|
||||
open_file_status_message = `Opening ${profile.app}...`;
|
||||
let open_ok = true;
|
||||
let open_error: string | null = null;
|
||||
|
||||
if (profile.open_cmd) {
|
||||
const cmd = profile.open_cmd.replaceAll('{{path}}', resolved_path);
|
||||
const cmd_result = await native.run_cmd({ cmd });
|
||||
if (!cmd_result.success) {
|
||||
open_ok = false;
|
||||
open_error = cmd_result.error ?? 'run_cmd failed';
|
||||
}
|
||||
} else {
|
||||
// No open_cmd → OS default handler via shell.openPath
|
||||
const os_result = await native.open_local_file_v2(resolved_path);
|
||||
if (!os_result.success) {
|
||||
open_ok = false;
|
||||
open_error = os_result.error ?? 'open_local_file_v2 failed';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Step 5: Wait for app to load before running post-script ---
|
||||
if (open_ok) {
|
||||
const delay = profile.post_delay_ms ?? 2000;
|
||||
open_file_status_message = `Waiting for ${profile.app}...`;
|
||||
await sleep(delay);
|
||||
}
|
||||
|
||||
// --- Step 6: Run post-script (AppleScript or shell) ---
|
||||
if (open_ok && profile.post_script) {
|
||||
open_file_status_message = 'Running post-open automation...';
|
||||
let script_ok = true;
|
||||
let script_error: string | null = null;
|
||||
|
||||
if (profile.post_script.startsWith('shell:')) {
|
||||
const shell_cmd = profile.post_script.slice('shell:'.length);
|
||||
const sr = await native.run_cmd({ cmd: shell_cmd });
|
||||
if (!sr.success) { script_ok = false; script_error = sr.error ?? 'run_cmd (post) failed'; }
|
||||
} else {
|
||||
const sr = await native.run_osascript(profile.post_script);
|
||||
if (!sr.success) { script_ok = false; script_error = sr.error ?? 'run_osascript failed'; }
|
||||
}
|
||||
|
||||
if (!script_ok) {
|
||||
// Non-fatal: file is already open. Surface as warning, not error.
|
||||
if (log_lvl) console.warn('post_script failed:', script_error);
|
||||
open_file_status = 'fallback';
|
||||
open_file_status_message = `Opened (post-script failed: ${script_error})`;
|
||||
setTimeout(() => (open_file_clicked = false), 8000);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Step 7: Fallback if open_cmd itself failed ---
|
||||
if (!open_ok) {
|
||||
if (log_lvl) console.warn('open_cmd failed, falling back to OS default:', open_error);
|
||||
const fb_result = await native.open_local_file_v2(resolved_path);
|
||||
if (!fb_result.success) {
|
||||
open_file_status = 'error';
|
||||
open_file_status_message = 'Failed to open file';
|
||||
open_file_error_detail = `${profile.app} failed: ${open_error}; OS fallback: ${fb_result.error}`;
|
||||
setTimeout(() => (open_file_clicked = false), 8000);
|
||||
return false;
|
||||
}
|
||||
open_file_status = 'fallback';
|
||||
open_file_status_message = '(opened with OS default)';
|
||||
setTimeout(() => (open_file_clicked = false), 5000);
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Success ---
|
||||
open_file_status = 'open';
|
||||
open_file_status_message = `Opened in ${profile.app}`;
|
||||
setTimeout(() => (open_file_clicked = false), 5000);
|
||||
return launch_result.success;
|
||||
return true;
|
||||
}
|
||||
// 2. ONSITE MODE (Browser with Modified Extensions)
|
||||
else if ($events_loc.launcher.app_mode === 'onsite') {
|
||||
@@ -289,12 +367,21 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
'Please wait while this file downloads...'} ***</strong>
|
||||
</div>
|
||||
{#if $ae_loc.is_native && $events_loc.launcher.app_mode === 'native'}
|
||||
<p>Most files will automatically be opened full screen.</p>
|
||||
<p>
|
||||
PowerPoint or KeyNote will attempt to display in presenter
|
||||
view.
|
||||
</p>
|
||||
<p>Please close the file when finished.</p>
|
||||
{#if open_file_status === 'error'}
|
||||
<p class="text-red-400">Failed to open file.</p>
|
||||
{#if open_file_error_detail}
|
||||
<pre class="mt-1 max-w-full overflow-x-auto whitespace-pre-wrap break-all rounded bg-black/30 px-2 py-1 font-mono text-xs text-red-300">{open_file_error_detail}</pre>
|
||||
{/if}
|
||||
{:else if open_file_status === 'fallback'}
|
||||
<p class="text-yellow-400">(opened with OS default)</p>
|
||||
{:else}
|
||||
<p>Most files will automatically be opened full screen.</p>
|
||||
<p>
|
||||
PowerPoint or KeyNote will attempt to display in presenter
|
||||
view.
|
||||
</p>
|
||||
<p>Please close the file when finished.</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -448,6 +535,45 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if $ae_loc.trusted_access && $ae_loc.is_native}
|
||||
<!-- 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. -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={async () => {
|
||||
const cur = event_file_obj?.cfg_json?.display_override ?? null;
|
||||
let next: string | null;
|
||||
if (!cur) next = 'extend';
|
||||
else if (cur === 'extend') next = 'mirror';
|
||||
else next = null;
|
||||
const new_cfg = { ...(event_file_obj.cfg_json ?? {}), display_override: next };
|
||||
await api.update_ae_obj({
|
||||
api_cfg: $ae_api,
|
||||
obj_type: 'event_file',
|
||||
obj_id: event_file_id,
|
||||
fields: { cfg_json: new_cfg }
|
||||
});
|
||||
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 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'}
|
||||
Ext
|
||||
{:else if event_file_obj?.cfg_json?.display_override === 'mirror'}
|
||||
Mir
|
||||
{:else}
|
||||
<Monitor size="1em" class="m-1" />
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<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}>
|
||||
|
||||
Reference in New Issue
Block a user