feat(launcher): wallpaper auto-apply from device config
Stores wallpaper URL(s) in event_device.other_json.launcher.wallpaper and auto-applies on all native Launcher instances within one heartbeat cycle (~60s), eliminating manual per-Mac setup at events. - electron_relay: typed set_wallpaper wrapper (url, url_external, display, auth headers) - launcher_defaults: wallpaper_applied_url tracking + section_state__wallpaper - launcher_cfg_wallpaper: new config section — save to device config + apply now - launcher_cfg: add wallpaper section to device tab - launcher_background_sync: auto-apply if config URL changed since last apply; external-only config targets only the secondary display, leaving built-in unchanged Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -384,13 +384,33 @@ export async function control_presentation({
|
|||||||
|
|
||||||
// 4. System Management (Phase 5+)
|
// 4. System Management (Phase 5+)
|
||||||
|
|
||||||
export async function set_wallpaper({ path }: { path: string }) {
|
export async function set_wallpaper({
|
||||||
|
path,
|
||||||
|
url,
|
||||||
|
url_external,
|
||||||
|
display = 'all',
|
||||||
|
api_key,
|
||||||
|
account_id
|
||||||
|
}: {
|
||||||
|
/** Local file path (existing behavior). */
|
||||||
|
path?: string;
|
||||||
|
/** HTTPS URL — downloaded to ~/Library/Caches/OSIT/wallpaper/ before applying. */
|
||||||
|
url?: string;
|
||||||
|
/** Optional separate URL for the external/projector display only. */
|
||||||
|
url_external?: string;
|
||||||
|
/** Which display(s) to target. Defaults to 'all'. */
|
||||||
|
display?: 'all' | 'primary' | 'external';
|
||||||
|
/** Aether API key passed as x-aether-api-key header on the download request. */
|
||||||
|
api_key?: string;
|
||||||
|
/** Aether account ID passed as x-account-id header on the download request. */
|
||||||
|
account_id?: string;
|
||||||
|
}) {
|
||||||
if (!native || !native.set_wallpaper)
|
if (!native || !native.set_wallpaper)
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Native handler set_wallpaper not available'
|
error: 'Native handler set_wallpaper not available'
|
||||||
};
|
};
|
||||||
return await native.set_wallpaper({ path });
|
return await native.set_wallpaper({ path, url, url_external, display, api_key, account_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update_app(args: {
|
export async function update_app(args: {
|
||||||
|
|||||||
@@ -39,12 +39,17 @@ export interface LauncherLocState {
|
|||||||
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__launch_timing: SectionState;
|
||||||
|
section_state__wallpaper: 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;
|
||||||
section_state__screen_saver: SectionState;
|
section_state__screen_saver: SectionState;
|
||||||
section_state__app_modes: SectionState;
|
section_state__app_modes: SectionState;
|
||||||
section_state__local_actions: SectionState;
|
section_state__local_actions: SectionState;
|
||||||
|
/** URL last successfully applied as wallpaper on this device. Used to detect remote config changes. */
|
||||||
|
wallpaper_applied_url: string | null;
|
||||||
|
/** URL last applied to the external display (when url_external is configured). */
|
||||||
|
wallpaper_applied_url_external: string | null;
|
||||||
datetime_format: string;
|
datetime_format: string;
|
||||||
time_format: string;
|
time_format: string;
|
||||||
time_hours: 12 | 24;
|
time_hours: 12 | 24;
|
||||||
@@ -160,6 +165,7 @@ export const launcher_loc_defaults: LauncherLocState = {
|
|||||||
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__launch_timing: 'collapsed',
|
||||||
|
section_state__wallpaper: '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',
|
||||||
@@ -208,7 +214,9 @@ export const launcher_loc_defaults: LauncherLocState = {
|
|||||||
controller: 'local',
|
controller: 'local',
|
||||||
controller_group_code: 'launcher-00',
|
controller_group_code: 'launcher-00',
|
||||||
controller_client_id: null,
|
controller_client_id: null,
|
||||||
native_test_mode: false
|
native_test_mode: false,
|
||||||
|
wallpaper_applied_url: null,
|
||||||
|
wallpaper_applied_url_external: null
|
||||||
// controller_cmd: null,
|
// controller_cmd: null,
|
||||||
// controller_trigger_send: null,
|
// controller_trigger_send: null,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,296 @@
|
|||||||
|
<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 * as native from '$lib/electron/electron_relay';
|
||||||
|
import Launcher_Cfg_Section from './launcher_cfg_section.svelte';
|
||||||
|
import { FlaskConical, Image, Monitor, Save, Zap } 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;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
let url_input = $state('');
|
||||||
|
let url_external_input = $state('');
|
||||||
|
let save_status = $state('');
|
||||||
|
let apply_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_configured_wallpaper(): { url?: string; url_external?: 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) ?? {};
|
||||||
|
return (launcher_cfg.wallpaper as { url?: string; url_external?: string } | undefined) ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sync_from_device() {
|
||||||
|
const device_id = get_device_id();
|
||||||
|
if (device_id === last_device_id) return;
|
||||||
|
last_device_id = device_id;
|
||||||
|
const configured = get_configured_wallpaper();
|
||||||
|
url_input = configured.url ?? '';
|
||||||
|
url_external_input = configured.url_external ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_from_device();
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
sync_from_device();
|
||||||
|
});
|
||||||
|
|
||||||
|
function set_save_status(msg: string) {
|
||||||
|
save_status = msg;
|
||||||
|
setTimeout(() => { if (save_status === msg) save_status = ''; }, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_apply_status(msg: string) {
|
||||||
|
apply_status = msg;
|
||||||
|
setTimeout(() => { if (apply_status === msg) apply_status = ''; }, 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handle_save() {
|
||||||
|
const device_id = get_device_id();
|
||||||
|
if (!device_id) { set_save_status('No device loaded'); return; }
|
||||||
|
|
||||||
|
const native_device = get_native_device();
|
||||||
|
const other_json_obj = parse_other_json(native_device?.other_json);
|
||||||
|
const launcher_cfg: Record<string, unknown> = {
|
||||||
|
...((other_json_obj.launcher as Record<string, unknown> | undefined) ?? {})
|
||||||
|
};
|
||||||
|
|
||||||
|
const wallpaper: Record<string, string> = {};
|
||||||
|
if (url_input.trim()) wallpaper.url = url_input.trim();
|
||||||
|
if (url_external_input.trim()) wallpaper.url_external = url_external_input.trim();
|
||||||
|
|
||||||
|
if (Object.keys(wallpaper).length > 0) {
|
||||||
|
launcher_cfg.wallpaper = wallpaper;
|
||||||
|
} else {
|
||||||
|
delete launcher_cfg.wallpaper;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(launcher_cfg).length > 0) {
|
||||||
|
other_json_obj.launcher = launcher_cfg;
|
||||||
|
} else {
|
||||||
|
delete other_json_obj.launcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
const other_json = JSON.stringify(other_json_obj);
|
||||||
|
set_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 },
|
||||||
|
log_lvl: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) { set_save_status('Save failed'); return; }
|
||||||
|
|
||||||
|
const store_loc = $ae_loc as { native_device?: NativeDeviceLike };
|
||||||
|
store_loc.native_device = { ...get_native_device(), ...result, other_json };
|
||||||
|
set_save_status('Saved');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handle_apply() {
|
||||||
|
const url = url_input.trim();
|
||||||
|
const url_ext = url_external_input.trim();
|
||||||
|
if (!url && !url_ext) { set_apply_status('Enter a URL first'); return; }
|
||||||
|
|
||||||
|
// If only external is set, target only that display so the built-in stays unchanged.
|
||||||
|
const display = !url && url_ext ? 'external' : 'all';
|
||||||
|
|
||||||
|
set_apply_status('Downloading & applying...');
|
||||||
|
const result = await native.set_wallpaper({
|
||||||
|
url: url || undefined,
|
||||||
|
url_external: url_ext || undefined,
|
||||||
|
display,
|
||||||
|
api_key: $ae_api.api_secret_key,
|
||||||
|
account_id: String($ae_api.account_id ?? '')
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result?.success) {
|
||||||
|
$events_loc.launcher.wallpaper_applied_url = url || null;
|
||||||
|
$events_loc.launcher.wallpaper_applied_url_external = url_ext || null;
|
||||||
|
set_apply_status('Applied ✓');
|
||||||
|
} else {
|
||||||
|
set_apply_status(`Error: ${(result as any)?.error ?? 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handle_save_and_apply() {
|
||||||
|
await handle_save();
|
||||||
|
if (save_status !== 'Save failed' && save_status !== 'No device loaded') {
|
||||||
|
await handle_apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const configured_url = $derived(get_configured_wallpaper().url ?? '');
|
||||||
|
const is_applied = $derived(
|
||||||
|
!!url_input.trim() &&
|
||||||
|
$events_loc.launcher.wallpaper_applied_url === url_input.trim()
|
||||||
|
);
|
||||||
|
const section_description = $derived(
|
||||||
|
configured_url
|
||||||
|
? is_applied
|
||||||
|
? 'Configured • Applied ✓'
|
||||||
|
: 'Configured • Pending'
|
||||||
|
: 'Not configured'
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Launcher_Cfg_Section
|
||||||
|
title="Wallpaper"
|
||||||
|
icon={Image}
|
||||||
|
bind:state={$events_loc.launcher.section_state__wallpaper}
|
||||||
|
{on_expand}
|
||||||
|
description={section_description}>
|
||||||
|
|
||||||
|
{#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">
|
||||||
|
<FlaskConical size="0.75em" class="text-warning-500" />
|
||||||
|
<span class="text-warning-500 text-[9px] font-bold tracking-wide uppercase">
|
||||||
|
Dev Preview — Apply requires Electron; Save works from any device
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||||
|
Desktop Background Images
|
||||||
|
</p>
|
||||||
|
<p class="ml-1 text-[10px] leading-snug opacity-60">
|
||||||
|
Paste an HTTPS image URL. Save writes it to this device's config so all
|
||||||
|
devices auto-apply on their next heartbeat. Apply sets it immediately on
|
||||||
|
this machine.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{#if !get_device_id()}
|
||||||
|
<div
|
||||||
|
class="bg-warning-500/10 border-warning-500/20 rounded-lg border px-2 py-2 text-[9px] opacity-80">
|
||||||
|
No device record loaded — Save requires a native device config.
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- All Displays (primary URL) -->
|
||||||
|
<section class="border-surface-500/10 rounded-lg border p-2">
|
||||||
|
<div class="mb-1.5 flex items-center gap-1.5">
|
||||||
|
<Monitor size="0.85em" class="opacity-50" />
|
||||||
|
<p class="text-[10px] font-bold uppercase tracking-wide">
|
||||||
|
All Displays
|
||||||
|
</p>
|
||||||
|
{#if is_applied}
|
||||||
|
<span
|
||||||
|
class="text-success-500 ml-auto text-[9px] font-bold">Applied ✓</span>
|
||||||
|
{:else if $events_loc.launcher.wallpaper_applied_url}
|
||||||
|
<span class="text-warning-500 ml-auto text-[9px] italic"
|
||||||
|
>Pending apply</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
bind:value={url_input}
|
||||||
|
placeholder="https://example.com/wallpaper.jpg"
|
||||||
|
class="input input-sm preset-tonal-surface mb-1.5 h-8 w-full text-[10px]" />
|
||||||
|
{#if $events_loc.launcher.wallpaper_applied_url && $events_loc.launcher.wallpaper_applied_url !== url_input.trim()}
|
||||||
|
<p class="text-[9px] italic opacity-40 truncate">
|
||||||
|
Applied: {$events_loc.launcher.wallpaper_applied_url}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- External Display (optional override) -->
|
||||||
|
<section class="border-surface-500/10 rounded-lg border p-2">
|
||||||
|
<div class="mb-1.5 flex items-center gap-1.5">
|
||||||
|
<Monitor size="0.85em" class="opacity-40" />
|
||||||
|
<p class="text-[10px] font-bold uppercase tracking-wide">
|
||||||
|
External / Projector
|
||||||
|
<span class="ml-1 font-normal normal-case opacity-50">(optional)</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1.5 text-[9px] leading-snug opacity-50">
|
||||||
|
Leave blank to use the same image on all displays.
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
bind:value={url_external_input}
|
||||||
|
placeholder="https://example.com/projector-bg.jpg"
|
||||||
|
class="input input-sm preset-tonal-surface h-8 w-full text-[10px]" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={handle_save}
|
||||||
|
disabled={!get_device_id()}
|
||||||
|
class="btn btn-sm preset-tonal-surface h-8 text-[10px]">
|
||||||
|
<Save size="0.8em" class="mr-1" /> Save Config
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={handle_apply}
|
||||||
|
disabled={!url_input.trim() && !url_external_input.trim()}
|
||||||
|
class="btn btn-sm preset-tonal-primary h-8 text-[10px]">
|
||||||
|
<Zap size="0.8em" class="mr-1" /> Apply Now
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={handle_save_and_apply}
|
||||||
|
disabled={!get_device_id() || (!url_input.trim() && !url_external_input.trim())}
|
||||||
|
class="btn btn-sm preset-filled-primary h-8 w-full text-[10px] font-bold">
|
||||||
|
<Save size="0.8em" class="mr-1" />
|
||||||
|
<Zap size="0.8em" class="mr-1" />
|
||||||
|
Save & Apply
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if save_status}
|
||||||
|
<div
|
||||||
|
class="text-primary-500 text-center text-[9px] italic"
|
||||||
|
class:text-error-500={save_status.includes('failed') || save_status.includes('No device')}>
|
||||||
|
{save_status}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if apply_status}
|
||||||
|
<div
|
||||||
|
class="text-[9px] italic text-center"
|
||||||
|
class:text-success-500={apply_status.includes('✓')}
|
||||||
|
class:text-primary-500={apply_status.includes('Downloading')}
|
||||||
|
class:text-error-500={apply_status.includes('Error')}>
|
||||||
|
{apply_status}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</Launcher_Cfg_Section>
|
||||||
@@ -17,6 +17,69 @@ import { db_events } from '$lib/ae_events/db_events';
|
|||||||
import * as native from '$lib/electron/electron_relay';
|
import * as native from '$lib/electron/electron_relay';
|
||||||
const { cleanup_tmp_files } = native;
|
const { cleanup_tmp_files } = native;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses other_json (string or object) into a plain Record.
|
||||||
|
* Duplicated from launcher_cfg_wallpaper to keep this component self-contained.
|
||||||
|
*/
|
||||||
|
function parse_other_json(raw: unknown): Record<string, unknown> {
|
||||||
|
if (!raw) return {};
|
||||||
|
if (typeof raw === 'object') return { ...(raw as Record<string, unknown>) };
|
||||||
|
if (typeof raw !== 'string') return {};
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
return parsed && typeof parsed === 'object' ? { ...parsed } : {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-applies the configured wallpaper if the URL in device config differs from
|
||||||
|
* what was last applied on this machine. Called at startup and after each heartbeat.
|
||||||
|
*
|
||||||
|
* WHY: staff updates the wallpaper URL in the device config once (from any machine);
|
||||||
|
* all native Launcher instances pick it up within one heartbeat cycle (~60s) without
|
||||||
|
* anyone needing to visit each podium Mac physically.
|
||||||
|
*/
|
||||||
|
async function apply_wallpaper_if_changed(device_other_json: unknown) {
|
||||||
|
if (!$ae_loc.is_native) return;
|
||||||
|
|
||||||
|
const other = parse_other_json(device_other_json);
|
||||||
|
const launcher_cfg = (other.launcher as Record<string, unknown> | undefined) ?? {};
|
||||||
|
const wallpaper = (launcher_cfg.wallpaper as { url?: string; url_external?: string } | undefined) ?? {};
|
||||||
|
|
||||||
|
const configured_url = wallpaper.url ?? '';
|
||||||
|
const configured_url_external = wallpaper.url_external ?? '';
|
||||||
|
const applied_url = $events_loc.launcher.wallpaper_applied_url ?? '';
|
||||||
|
const applied_url_external = $events_loc.launcher.wallpaper_applied_url_external ?? '';
|
||||||
|
|
||||||
|
const url_changed = configured_url && configured_url !== applied_url;
|
||||||
|
const ext_changed = configured_url_external !== applied_url_external;
|
||||||
|
|
||||||
|
if (!url_changed && !ext_changed) return;
|
||||||
|
|
||||||
|
if (log_lvl) console.log('Sync: Wallpaper config changed — applying.', { configured_url, applied_url });
|
||||||
|
|
||||||
|
// If only the external URL is configured, leave the built-in display unchanged.
|
||||||
|
const display = !configured_url && configured_url_external ? 'external' : 'all';
|
||||||
|
|
||||||
|
const result = await native.set_wallpaper({
|
||||||
|
url: configured_url || undefined,
|
||||||
|
url_external: configured_url_external || undefined,
|
||||||
|
display,
|
||||||
|
api_key: $ae_api.api_secret_key,
|
||||||
|
account_id: String($ae_api.account_id ?? '')
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result?.success) {
|
||||||
|
$events_loc.launcher.wallpaper_applied_url = configured_url || null;
|
||||||
|
$events_loc.launcher.wallpaper_applied_url_external = configured_url_external || null;
|
||||||
|
if (log_lvl) console.log('Sync: Wallpaper applied.');
|
||||||
|
} else {
|
||||||
|
console.warn('Sync: Wallpaper apply failed.', (result as any)?.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let { log_lvl = 1 } = $props();
|
let { log_lvl = 1 } = $props();
|
||||||
|
|
||||||
let currently_syncing: string | null = $state(null);
|
let currently_syncing: string | null = $state(null);
|
||||||
@@ -134,6 +197,12 @@ onMount(async () => {
|
|||||||
run_device_heartbeat();
|
run_device_heartbeat();
|
||||||
refresh_location_config();
|
refresh_location_config();
|
||||||
|
|
||||||
|
// On startup: apply wallpaper if the device config has one that differs from
|
||||||
|
// what was last applied (covers reboots and first-time device setup).
|
||||||
|
if ($ae_loc.is_native) {
|
||||||
|
apply_wallpaper_if_changed($ae_loc.native_device?.other_json);
|
||||||
|
}
|
||||||
|
|
||||||
// Stagger initial data fetches
|
// Stagger initial data fetches
|
||||||
setTimeout(() => refresh_session_data(), 1000);
|
setTimeout(() => refresh_session_data(), 1000);
|
||||||
setTimeout(() => refresh_presentation_data(), 3000);
|
setTimeout(() => refresh_presentation_data(), 3000);
|
||||||
@@ -432,13 +501,19 @@ async function run_device_heartbeat() {
|
|||||||
update_payload.notes = 'Heartbeat from non-native web session.';
|
update_payload.notes = 'Heartbeat from non-native web session.';
|
||||||
}
|
}
|
||||||
|
|
||||||
await events_func.update_ae_obj__event_device({
|
const heartbeat_result = await events_func.update_ae_obj__event_device({
|
||||||
api_cfg: $ae_api,
|
api_cfg: $ae_api,
|
||||||
event_device_id: String(device_id),
|
event_device_id: String(device_id),
|
||||||
data_kv: update_payload,
|
data_kv: update_payload,
|
||||||
log_lvl: 0
|
log_lvl: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// V3 PATCH returns the full updated record. Use it to detect wallpaper
|
||||||
|
// config changes made remotely (e.g. staff updated other_json from another device).
|
||||||
|
if (heartbeat_result) {
|
||||||
|
apply_wallpaper_if_changed((heartbeat_result as any).other_json);
|
||||||
|
}
|
||||||
|
|
||||||
last_heartbeat = new Date().toLocaleTimeString();
|
last_heartbeat = new Date().toLocaleTimeString();
|
||||||
$events_sess.launcher.heartbeat_info = {
|
$events_sess.launcher.heartbeat_info = {
|
||||||
last_timestamp: last_heartbeat,
|
last_timestamp: last_heartbeat,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import Launcher_Cfg_Screen_Saver from './cfg_components/launcher_cfg_screen_save
|
|||||||
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 Launcher_Cfg_Launch_Timing from './cfg_components/launcher_cfg_launch_timing.svelte';
|
||||||
|
import Launcher_Cfg_Wallpaper from './cfg_components/launcher_cfg_wallpaper.svelte';
|
||||||
import {
|
import {
|
||||||
Bug,
|
Bug,
|
||||||
Code,
|
Code,
|
||||||
@@ -181,6 +182,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_Wallpaper
|
||||||
|
on_expand={() => handle_section_expand('wallpaper')} />
|
||||||
<Launcher_Cfg_Launch_Timing
|
<Launcher_Cfg_Launch_Timing
|
||||||
on_expand={() => handle_section_expand('launch_timing')} />
|
on_expand={() => handle_section_expand('launch_timing')} />
|
||||||
{#if $ae_loc.is_native}
|
{#if $ae_loc.is_native}
|
||||||
|
|||||||
Reference in New Issue
Block a user