feat(launcher): add device launch timing override
This commit is contained in:
@@ -326,7 +326,8 @@ immediately. See `PROJECT__AE_Events_Launcher_Native_integration.md` Section 8.
|
|||||||
These are the initial built-in defaults shipped with the Launcher. They are the Svelte-side
|
These are the initial built-in defaults shipped with the Launcher. They are the Svelte-side
|
||||||
fallbacks used when neither device config nor event config defines a profile for the file
|
fallbacks used when neither device config nor event config defines a profile for the file
|
||||||
extension. Each canonical profile can have multiple extension aliases. `post_delay_ms` is part
|
extension. Each canonical profile can have multiple extension aliases. `post_delay_ms` is part
|
||||||
of the profile object, so a device-specific `launch_profiles` entry can override it later.
|
of the profile object, so a device-specific `other_json.launcher.post_delay_ms` override can
|
||||||
|
tune it later without changing the profile table.
|
||||||
|
|
||||||
| Profile name | Extension aliases | Default app | Display mode | Post delay | Notes |
|
| Profile name | Extension aliases | Default app | Display mode | Post delay | Notes |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
|
|||||||
@@ -202,9 +202,10 @@ The native layer should not invent or guess a default launch path.
|
|||||||
|
|
||||||
The built-in defaults are organized as canonical profile names plus extension aliases. That
|
The built-in defaults are organized as canonical profile names plus extension aliases. That
|
||||||
lets multiple file types share one profile without repeating the same app/script details.
|
lets multiple file types share one profile without repeating the same app/script details.
|
||||||
The profile object also carries `post_delay_ms`, so a device-specific `launch_profiles`
|
The profile object also carries `post_delay_ms`, and a device-specific
|
||||||
override can tune the delay without changing the bridge contract. URL-based presentations
|
`other_json.launcher.post_delay_ms` override can tune the delay without changing the bridge
|
||||||
remain a special pseudo-extension handled separately from the cache open flow.
|
contract. URL-based presentations remain a special pseudo-extension handled separately from
|
||||||
|
the cache open flow.
|
||||||
|
|
||||||
### Native Template Formats
|
### Native Template Formats
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ guessing defaults.
|
|||||||
- [x] `native:copy-from-cache-to-temp` primitive added — copy to tmp, caller decides launch
|
- [x] `native:copy-from-cache-to-temp` primitive added — copy to tmp, caller decides launch
|
||||||
- [x] `native:launch-from-cache` executes a provided `native_template` string — AppleScript or `shell:` prefix; no Electron-side fallback
|
- [x] `native:launch-from-cache` executes a provided `native_template` string — AppleScript or `shell:` prefix; no Electron-side fallback
|
||||||
- [x] `get_launch_profile()` in `launcher_file_cont.svelte` reads from device config then event config; resolves to a `native_template` string and passes it to `launch_from_cache`
|
- [x] `get_launch_profile()` in `launcher_file_cont.svelte` reads from device config then event config; resolves to a `native_template` string and passes it to `launch_from_cache`
|
||||||
|
- [x] Built-in Launcher defaults refactored into canonical profile names plus extension aliases
|
||||||
|
- [x] Device-level Launch Timing section added under Launcher Configuration → Device, with `other_json.launcher.post_delay_ms` override
|
||||||
|
|
||||||
**Svelte-side migration — remaining before May 26:**
|
**Svelte-side migration — remaining before May 26:**
|
||||||
- [ ] **[Launcher] Built-in Svelte default profiles** — move the built-in presentation/media
|
- [ ] **[Launcher] Built-in Svelte default profiles** — move the built-in presentation/media
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export interface LaunchProfile {
|
|||||||
post_script?: string;
|
post_script?: string;
|
||||||
/**
|
/**
|
||||||
* Milliseconds to wait after open_cmd before running post_script.
|
* Milliseconds to wait after open_cmd before running post_script.
|
||||||
* Default: 2000. Can be overridden per device via launch_profiles.
|
* Default: 2000. Can be overridden per device via other_json.launcher.post_delay_ms.
|
||||||
*/
|
*/
|
||||||
post_delay_ms?: number;
|
post_delay_ms?: number;
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export interface LauncherLocState {
|
|||||||
show_section__controller: boolean;
|
show_section__controller: boolean;
|
||||||
section_state__health: SectionState;
|
section_state__health: SectionState;
|
||||||
section_state__native_os: SectionState;
|
section_state__native_os: SectionState;
|
||||||
|
section_state__launch_timing: SectionState;
|
||||||
section_state__sync_timers: SectionState;
|
section_state__sync_timers: SectionState;
|
||||||
section_state__updates: SectionState;
|
section_state__updates: SectionState;
|
||||||
section_state__controller: SectionState;
|
section_state__controller: SectionState;
|
||||||
@@ -158,6 +159,7 @@ export const launcher_loc_defaults: LauncherLocState = {
|
|||||||
// Values: 'collapsed' | 'auto' | 'pinned'
|
// Values: 'collapsed' | 'auto' | 'pinned'
|
||||||
section_state__health: 'auto',
|
section_state__health: 'auto',
|
||||||
section_state__native_os: 'collapsed',
|
section_state__native_os: 'collapsed',
|
||||||
|
section_state__launch_timing: 'collapsed',
|
||||||
section_state__sync_timers: 'collapsed',
|
section_state__sync_timers: 'collapsed',
|
||||||
section_state__updates: 'collapsed',
|
section_state__updates: 'collapsed',
|
||||||
section_state__controller: 'auto',
|
section_state__controller: 'auto',
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||||
|
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||||
|
import { events_loc } from '$lib/stores/ae_events_stores';
|
||||||
|
import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
|
||||||
|
import { AlarmClock, RotateCcw, Save, Timer } from '@lucide/svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
on_expand?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { on_expand }: Props = $props();
|
||||||
|
|
||||||
|
type NativeDeviceLike = {
|
||||||
|
event_device_id?: string;
|
||||||
|
id?: string;
|
||||||
|
other_json?: string | Record<string, unknown> | null;
|
||||||
|
post_delay_ms?: number | string | null;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
let delay_ms_input = $state('');
|
||||||
|
let save_status = $state('');
|
||||||
|
let last_device_id: string | null = null;
|
||||||
|
|
||||||
|
function get_native_device(): NativeDeviceLike | null {
|
||||||
|
const store_loc = $ae_loc as { native_device?: NativeDeviceLike };
|
||||||
|
return store_loc.native_device ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_device_id(): string | null {
|
||||||
|
const native_device = get_native_device();
|
||||||
|
return native_device?.event_device_id ?? native_device?.id ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_other_json(raw_json: unknown): Record<string, unknown> {
|
||||||
|
if (!raw_json) return {};
|
||||||
|
if (typeof raw_json === 'object') {
|
||||||
|
return { ...(raw_json as Record<string, unknown>) };
|
||||||
|
}
|
||||||
|
if (typeof raw_json !== 'string') return {};
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw_json);
|
||||||
|
return parsed && typeof parsed === 'object' ? { ...parsed } : {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_current_delay_ms(): number | null {
|
||||||
|
const native_device = get_native_device();
|
||||||
|
const parsed_other_json = parse_other_json(native_device?.other_json);
|
||||||
|
const launcher_cfg =
|
||||||
|
(parsed_other_json.launcher as Record<string, unknown> | undefined) ?? {};
|
||||||
|
const raw_delay = launcher_cfg.post_delay_ms;
|
||||||
|
const parsed_delay =
|
||||||
|
typeof raw_delay === 'string'
|
||||||
|
? Number(raw_delay)
|
||||||
|
: typeof raw_delay === 'number'
|
||||||
|
? raw_delay
|
||||||
|
: Number.NaN;
|
||||||
|
return Number.isFinite(parsed_delay) && parsed_delay >= 0
|
||||||
|
? parsed_delay
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sync_from_device() {
|
||||||
|
const device_id = get_device_id();
|
||||||
|
if (device_id === last_device_id) return;
|
||||||
|
|
||||||
|
last_device_id = device_id;
|
||||||
|
delay_ms_input = get_current_delay_ms() === null ? '' : String(get_current_delay_ms());
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_from_device();
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
sync_from_device();
|
||||||
|
});
|
||||||
|
|
||||||
|
function build_other_json(delay_value: number | null): string {
|
||||||
|
const native_device = get_native_device();
|
||||||
|
const other_json = parse_other_json(native_device?.other_json);
|
||||||
|
const launcher_cfg = {
|
||||||
|
...((other_json.launcher as Record<string, unknown> | undefined) ?? {})
|
||||||
|
};
|
||||||
|
|
||||||
|
if (delay_value === null) {
|
||||||
|
delete launcher_cfg.post_delay_ms;
|
||||||
|
} else {
|
||||||
|
launcher_cfg.post_delay_ms = delay_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(launcher_cfg).length > 0) {
|
||||||
|
other_json.launcher = launcher_cfg;
|
||||||
|
} else {
|
||||||
|
delete other_json.launcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(other_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save_delay_override() {
|
||||||
|
const device_id = get_device_id();
|
||||||
|
if (!device_id) {
|
||||||
|
save_status = 'No native device loaded';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = delay_ms_input.trim();
|
||||||
|
let delay_value: number | null = null;
|
||||||
|
|
||||||
|
if (trimmed !== '') {
|
||||||
|
const parsed = Number(trimmed);
|
||||||
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
||||||
|
save_status = 'Enter a valid non-negative delay';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delay_value = Math.round(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
save_status = 'Saving...';
|
||||||
|
const result = await events_func.update_ae_obj__event_device({
|
||||||
|
api_cfg: $ae_api,
|
||||||
|
event_device_id: device_id,
|
||||||
|
data_kv: {
|
||||||
|
other_json: build_other_json(delay_value)
|
||||||
|
},
|
||||||
|
log_lvl: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
save_status = 'Save failed';
|
||||||
|
setTimeout(() => (save_status = ''), 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const store_loc = $ae_loc as { native_device?: NativeDeviceLike };
|
||||||
|
store_loc.native_device = {
|
||||||
|
...get_native_device(),
|
||||||
|
...result,
|
||||||
|
other_json: build_other_json(delay_value)
|
||||||
|
};
|
||||||
|
|
||||||
|
save_status = delay_value === null ? 'Reset to profile defaults' : 'Saved';
|
||||||
|
setTimeout(() => (save_status = ''), 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reset_delay_override() {
|
||||||
|
delay_ms_input = '';
|
||||||
|
await save_delay_override();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Launcher_Cfg_Section
|
||||||
|
title="Launch Timing"
|
||||||
|
icon={Timer}
|
||||||
|
bind:state={$events_loc.launcher.section_state__launch_timing}
|
||||||
|
{on_expand}
|
||||||
|
description="Device-level post-open delay override">
|
||||||
|
{#if $ae_loc.edit_mode && !$ae_loc.is_native}
|
||||||
|
<div
|
||||||
|
class="bg-warning-500/10 border-warning-500/30 mb-1 flex items-center gap-2 rounded-lg border px-2 py-1.5">
|
||||||
|
<AlarmClock size="0.75em" class="text-warning-500" />
|
||||||
|
<span
|
||||||
|
class="text-warning-500 text-[9px] font-bold tracking-wide uppercase"
|
||||||
|
>Dev Preview — visible for layout only; save requires the native
|
||||||
|
device record</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||||
|
Native Open Delay
|
||||||
|
</p>
|
||||||
|
<p class="ml-1 text-[10px] leading-snug opacity-60">
|
||||||
|
Overrides the built-in `post_delay_ms` for this device. Leave blank
|
||||||
|
to use the profile default for each extension/profile.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-[1fr_auto] gap-2">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="100"
|
||||||
|
bind:value={delay_ms_input}
|
||||||
|
placeholder="Use profile defaults"
|
||||||
|
class="input input-sm preset-tonal-surface h-8 text-[10px]" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={save_delay_override}
|
||||||
|
class="btn btn-sm preset-filled-primary h-8 px-3 text-[10px] font-bold"
|
||||||
|
disabled={!get_device_id()}>
|
||||||
|
<Save size="0.8em" class="mr-1" /> Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between gap-2 px-1 text-[9px] opacity-60">
|
||||||
|
<span>
|
||||||
|
Current:
|
||||||
|
{#if get_current_delay_ms() === null}
|
||||||
|
profile defaults
|
||||||
|
{:else}
|
||||||
|
{get_current_delay_ms()} ms
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={reset_delay_override}
|
||||||
|
class="btn btn-xs preset-tonal-surface h-6 px-2 text-[9px]"
|
||||||
|
disabled={!get_device_id()}>
|
||||||
|
<RotateCcw size="0.75em" class="mr-1" /> Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if save_status}
|
||||||
|
<div class="text-primary-500 text-center text-[9px] italic">
|
||||||
|
{save_status}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</Launcher_Cfg_Section>
|
||||||
@@ -32,6 +32,7 @@ import Launcher_Cfg_Controller from './cfg_components/launcher_cfg_controller.sv
|
|||||||
import Launcher_Cfg_Screen_Saver from './cfg_components/launcher_cfg_screen_saver.svelte';
|
import Launcher_Cfg_Screen_Saver from './cfg_components/launcher_cfg_screen_saver.svelte';
|
||||||
import Launcher_Cfg_App_Modes from './cfg_components/launcher_cfg_app_modes.svelte';
|
import Launcher_Cfg_App_Modes from './cfg_components/launcher_cfg_app_modes.svelte';
|
||||||
import Launcher_Cfg_Local_Actions from './cfg_components/launcher_cfg_local_actions.svelte';
|
import Launcher_Cfg_Local_Actions from './cfg_components/launcher_cfg_local_actions.svelte';
|
||||||
|
import Launcher_Cfg_Launch_Timing from './cfg_components/launcher_cfg_launch_timing.svelte';
|
||||||
import {
|
import {
|
||||||
Bug,
|
Bug,
|
||||||
Code,
|
Code,
|
||||||
@@ -150,7 +151,7 @@ function handle_section_expand(current_key: string) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab Content -->
|
<!-- Tab Content -->
|
||||||
<div class="flex min-h-[400px] w-full flex-col gap-2">
|
<div class="flex min-h-100 w-full flex-col gap-2">
|
||||||
<!-- SETUP: everything onsite operators need day-to-day -->
|
<!-- SETUP: everything onsite operators need day-to-day -->
|
||||||
{#if active_tab === 'setup'}
|
{#if active_tab === 'setup'}
|
||||||
<div
|
<div
|
||||||
@@ -180,6 +181,8 @@ function handle_section_expand(current_key: string) {
|
|||||||
on_expand={() => handle_section_expand('health')} />
|
on_expand={() => handle_section_expand('health')} />
|
||||||
<Launcher_Cfg_Native_OS
|
<Launcher_Cfg_Native_OS
|
||||||
on_expand={() => handle_section_expand('native_os')} />
|
on_expand={() => handle_section_expand('native_os')} />
|
||||||
|
<Launcher_Cfg_Launch_Timing
|
||||||
|
on_expand={() => handle_section_expand('launch_timing')} />
|
||||||
{#if $ae_loc.is_native}
|
{#if $ae_loc.is_native}
|
||||||
<Launcher_Cfg_Updates
|
<Launcher_Cfg_Updates
|
||||||
on_expand={() =>
|
on_expand={() =>
|
||||||
|
|||||||
@@ -128,10 +128,34 @@ function get_launch_profile(
|
|||||||
extension: string,
|
extension: string,
|
||||||
file_obj?: any
|
file_obj?: any
|
||||||
): LaunchProfile {
|
): LaunchProfile {
|
||||||
const device_profiles = ($ae_loc as any).native_device?.launch_profiles ?? null;
|
const native_device = ($ae_loc as any).native_device ?? null;
|
||||||
|
const device_profiles =
|
||||||
|
native_device?.other_json?.launcher?.launch_profiles ??
|
||||||
|
native_device?.launch_profiles ??
|
||||||
|
null;
|
||||||
const local_profiles = ($events_loc as any).launcher?.launch_profiles ?? null;
|
const local_profiles = ($events_loc as any).launcher?.launch_profiles ?? null;
|
||||||
const display_override = file_obj?.cfg_json?.display_override ?? null;
|
const display_override = file_obj?.cfg_json?.display_override ?? null;
|
||||||
return resolve_launch_profile(extension, display_override, device_profiles, local_profiles);
|
const profile = resolve_launch_profile(
|
||||||
|
extension,
|
||||||
|
display_override,
|
||||||
|
device_profiles,
|
||||||
|
local_profiles
|
||||||
|
);
|
||||||
|
|
||||||
|
const device_post_delay_ms =
|
||||||
|
native_device?.other_json?.launcher?.post_delay_ms ??
|
||||||
|
native_device?.post_delay_ms ??
|
||||||
|
null;
|
||||||
|
const parsed_delay_ms =
|
||||||
|
typeof device_post_delay_ms === 'string'
|
||||||
|
? Number(device_post_delay_ms)
|
||||||
|
: device_post_delay_ms;
|
||||||
|
|
||||||
|
if (Number.isFinite(parsed_delay_ms) && parsed_delay_ms >= 0) {
|
||||||
|
profile.post_delay_ms = parsed_delay_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user