Fix: Restore Native caching business logic and implement 3-mode launcher

- Implemented Default, Onsite, and Native launcher modes in launcher_file_cont.svelte.
- Restored background pre-caching logic with configurable timers in new LauncherBackgroundSync component.
- Fixed standard browser download regression for regular website mode.
- Modernized electron_relay to TypeScript and standardized bridge detection in layout.
- Detailed startup and background sync technical flow in documentation.
This commit is contained in:
Scott Idem
2026-01-23 15:17:50 -05:00
parent 20b41ebaef
commit 683ea0394d
11 changed files with 558 additions and 961 deletions

View File

@@ -11,6 +11,17 @@ The sole purpose of this Native App is to serve as the **AE Events Launcher**. I
- Execute OS-level shell commands and scripts (e.g., launching presentation software). - Execute OS-level shell commands and scripts (e.g., launching presentation software).
- Provide a "Zero-Config" experience for onsite event laptops. - Provide a "Zero-Config" experience for onsite event laptops.
### 0.1 Application Modes
The system must support three distinct operational modes:
| Mode | Refresh Logic | File Handling |
| --- | --- | --- |
| **Default** | Slower auto-refresh timers. | Standard browser downloads. No local caching. |
| **Onsite** | Faster auto-refresh timers. | Downloads files with modified extensions (e.g., .pptxwin). |
| **Native** | Fastest auto-refresh timers. | Full background pre-caching. Launching from local temp directory with original names. |
---
## 1. Minimalist Configuration Strategy ## 1. Minimalist Configuration Strategy
To simplify laptop deployment, we will move away from large local JSON files. To simplify laptop deployment, we will move away from large local JSON files.
@@ -36,6 +47,76 @@ Each laptop will contain a `seed.json`. For development, this is located at `~/s
`http://[app_base_url]/events/[event_id]/launcher/[event_location_id]` `http://[app_base_url]/events/[event_id]/launcher/[event_location_id]`
5. **Inject:** Config and JWT are injected into the SvelteKit frontend via the Preload script. 5. **Inject:** Config and JWT are injected into the SvelteKit frontend via the Preload script.
### 1.3 Technical Flow: Startup & Background Caching
The system utilizes a multi-layered hydration and synchronization strategy to ensure files are available instantly.
#### Step 1: Zero-Config Initialization (Main Process)
- **Seed Discovery:** Main process reads `seed.json`.
- **Identity Exchange:** Main process authenticates with the API using the `aether_api_key`.
- **Global Injections:** Once hydrated, the Main process provides the SvelteKit frontend with:
- `native_device`: Full record from `event_device` table (contains timers).
- `aether_api_key`: For authorized background downloads.
- `local_file_cache_path`: Root for the permanent hashed cache.
- `host_file_temp_path`: Root for the operational launch directory.
#### Step 2: Session-Driven Caching (Renderer Process)
- **View Trigger:** When a user navigates to a session or location, the SvelteKit frontend populates the `event_file_obj_li` store.
- **Sync Cycle:** The `LauncherBackgroundSync` component detects the new file list and:
1. Extracts all `event_file_id` values.
2. Fetches full metadata (hashes) from the local Dexie IndexedDB.
3. Asynchronously checks the Native Cache for each hash.
- **Background Download:** Missing files are downloaded directly to the hashed cache using the authorized Native API Key.
- **Timer Loop:** A background loop runs every `check_event_loop_period` (configurable via API) to ensure the cache stays in sync with server-side changes.
#### Step 3: Hashed Cache Pattern (Filesystem)
To prevent filename collisions and handle versioning, the permanent cache follows the server's structure:
- **Root:** `[local_file_cache_path]`
- **Subdirectory:** First 2 characters of SHA256 (e.g., `ab/`)
- **Filename:** Full SHA256 with `.file` extension (e.g., `abc123...file`)
#### Step 4: Safe Handover (Launch Sequence)
When a user clicks "Open", the system follows a non-destructive handover:
1. **Verify:** Confirm hash exists in `local_file_cache_path`.
2. **Prepare:** Copy the hashed file to `host_file_temp_path`.
3. **Restore:** Rename the copy back to its original filename (e.g., `Presentation.pptx`).
4. **Execute:** Trigger OS-level `shell.openPath()` on the temp file.
*This ensures the permanent cache remains clean while the third-party app (PowerPoint, etc.) can operate on a file with a familiar name.*
### 1.3 Technical Flow: Startup & Background Caching
The system utilizes a multi-layered hydration and synchronization strategy to ensure files are available instantly.
#### Step 1: Zero-Config Initialization (Main Process)
- **Seed Discovery:** Main process reads `seed.json`.
- **Identity Exchange:** Main process authenticates with the API using the `aether_api_key`.
- **Global Injections:** Once hydrated, the Main process provides the SvelteKit frontend with:
- `native_device`: Full record from `event_device` table (contains timers).
- `aether_api_key`: For authorized background downloads.
- `local_file_cache_path`: Root for the permanent hashed cache.
- `host_file_temp_path`: Root for the operational launch directory.
#### Step 2: Session-Driven Caching (Renderer Process)
- **View Trigger:** When a user navigates to a session or location, the SvelteKit frontend populates the `event_file_obj_li` store.
- **Sync Cycle:** The `LauncherBackgroundSync` component detects the new file list and:
1. Extracts all `event_file_id` values.
2. Fetches full metadata (hashes) from the local Dexie IndexedDB.
3. Asynchronously checks the Native Cache for each hash.
- **Background Download:** Missing files are downloaded directly to the hashed cache using the authorized Native API Key.
- **Timer Loop:** A background loop runs every `check_event_loop_period` (configurable via API) to ensure the cache stays in sync with server-side changes.
#### Step 3: Hashed Cache Pattern (Filesystem)
To prevent filename collisions and handle versioning, the permanent cache follows the server's structure:
- **Root:** `[local_file_cache_path]`
- **Subdirectory:** First 2 characters of SHA256 (e.g., `ab/`)
- **Filename:** Full SHA256 with `.file` extension (e.g., `abc123...file`)
#### Step 4: Safe Handover (Launch Sequence)
When a user clicks "Open", the system follows a non-destructive handover:
1. **Verify:** Confirm hash exists in `local_file_cache_path`.
2. **Prepare:** Copy the hashed file to `host_file_temp_path`.
3. **Restore:** Rename the copy back to its original filename (e.g., `Presentation.pptx`).
4. **Execute:** Trigger OS-level `shell.openPath()` on the temp file.
*This ensures the permanent cache remains clean while the third-party app (PowerPoint, etc.) can operate on a file with a familiar name.*
## 2. macOS Hardening (Permissions) ## 2. macOS Hardening (Permissions)
... ...
macOS requires explicit user consent for several features. The new app will handle these during the "Splash Screen" phase. macOS requires explicit user consent for several features. The new app will handle these during the "Splash Screen" phase.

View File

@@ -1,333 +0,0 @@
/* ### Electron Specific JavaScript ### */
// import crypto from 'crypto';
// import {fs} from 'fs';
// import path from 'path';
// const crypto = require('crypto');
// const fs = require('fs');
// const fs_promises = require('node:fs/promises');
// const path = require('path');
// const { ipcRenderer } = require('electron');
// function sleep(milliseconds) {
// const date = Date.now();
// let currentDate = null;
// do {
// currentDate = Date.now();
// } while (currentDate - date < milliseconds);
// }
// // exports.check_hash_file_cache should no longer be needed with this.
// // Updated 2022-10-11
// export let check_hash_file_cache_v2 = async function check_hash_file_cache_v2({local_file_cache_path, hash, check_hash=false}) {
// console.log('*** check_hash_file_cache_v2() ***');
// console.log(`Host File Cache Path: ${local_file_cache_path}; Hash: ${hash}`);
// let hash_filename = `${hash}.file`;
// let subdirectory = hash_filename.substring(0,2);
// let subdirectory_path = path.join(local_file_cache_path, subdirectory);
// if (fs.existsSync(subdirectory_path)) {
// } else {
// console.log(`Hashed file subdirectory not found in cache: ${subdirectory_path}`);
// return null;
// }
// let hash_file_cache_path = path.join(subdirectory_path, hash_filename);
// if (fs.existsSync(hash_file_cache_path)) {
// console.log(`Hashed file exists in cache: ${hash_file_cache_path}`);
// if (check_hash) {
// const file_buffer = fs.readFileSync(hash_file_cache_path);
// const file_hash_sha256 = crypto.createHash('sha256');
// file_hash_sha256.update(file_buffer);
// const file_hash_sha256_check = file_hash_sha256.digest('hex');
// if (file_hash_sha256_check == hash) {
// console.log('File hash match', file_hash_sha256_check);
// } else {
// console.log('File hash does not match', file_hash_sha256_check);
// return false;
// }
// }
// return true;
// } else {
// console.log(`Hashed file not found in cache: ${hash_file_cache_path}`);
// return null;
// }
// }
// @ts-nocheck
// Updated 2022-05-07
export let kill_processes = async function kill_processes({ process_name_li = [] }) {
console.log('*** kill_processes() ***');
console.log(`Process Name List: ${process_name_li}`);
let fail_flag = null;
if (process_name_li) {
for (let i = 0; i < process_name_li.length; i++) {
// separate the keys and the values
let process_name = process_name_li[i];
let signal = null;
if (process_name == 'osit_aperture_wrapper') {
signal = 'INT'; // INT (interrupt) correctly stops the wrapper
}
let kill_processes_result = await native_app.kill_processes({
process_name: process_name,
signal: signal
});
console.log(kill_processes_result);
if (kill_processes_result) {
console.log('Killed process.');
// return kill_processes_result;
} else {
console.log('Did not kill process. Something went wrong.');
fail_flag = true;
// return false;
}
}
}
return fail_flag;
// let kill_processes_result = await native_app.kill_processes({process_name: process_name});
// console.log(kill_processes_result);
// if (kill_processes_result) {
// console.log('Killed process.');
// return kill_processes_result;
// } else {
// console.log('Did not kill process. Something went wrong.');
// return false;
// }
};
// Updated 2022-05-06
export let open_local_file = async function open_local_file({ file_path, filename }) {
console.log('*** open_local_file() ***');
console.log(`File Path: ${file_path}; Filename: ${filename}`);
console.log(
'Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory'
);
// let check_local_file_result = await native_app.check_local_file({local_file_path: file_path, filename: filename});
// console.log(check_local_file_result);
// if (check_local_file_result) {
// console.log('Local file found.');
// } else {
// console.log('Local file not found. Will not attempt to open.');
// return false;
// }
// console.log('Local file file found and ready to be opened.');
let open_local_file_result = await native_app.open_local_file({
local_file_path: file_path,
filename: filename
});
console.log(open_local_file_result);
if (open_local_file_result) {
console.log('Local file was opened.');
return open_local_file_result;
} else {
console.log('Local file was not opened. Something went wrong.');
return false;
}
};
// exports.open_local_file should no longer be needed with this.
// Updated 2022-10-11
export let open_local_file_v2 = async function open_local_file_v2({ file_path, filename }) {
console.log('*** open_local_file_v2() ***');
console.log(`Local File Path: ${file_path}; Filename: ${filename}`);
console.log(
'Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory'
);
let open_local_file_result = await ipcRenderer
.invoke('open_local_file', file_path, filename)
.then((result) => {
console.log('IPC open local file finished');
if (result) {
console.log('Local file was opened.');
return result;
} else {
console.log('Local file was not opened. Something went wrong.');
console.log(result);
return false;
}
console.log(result);
return true;
});
return open_local_file_result;
};
// // Updated 2022-05-06
// export let open_hash_file_to_temp = async function open_hash_file_to_temp({local_file_cache_path, hash, host_file_temp_path, filename}) {
// console.log('*** open_hash_file_to_temp() ***');
// console.log(`Host File Cache Path: ${local_file_cache_path}; Hash: ${hash}; Host File Temp Path: ${host_file_temp_path}; Filename: ${filename}`);
// console.log('Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory');
// let open_hash_file_to_temp_result = await native_app.open_hash_file_to_temp({local_file_cache_path: local_file_cache_path, hash: hash, host_file_temp_path: host_file_temp_path, filename: filename});
// console.log(open_hash_file_to_temp_result);
// if (open_hash_file_to_temp_result) {
// console.log('Local hash file was opened from temp directory.');
// return open_hash_file_to_temp_result;
// } else {
// console.log('Local hash file was not opened from the temp directory. Something went wrong.');
// return false;
// }
// }
// // exports.open_hash_file_to_temp should no longer be needed with this.
// // Updated 2022-10-11
// export let open_hash_file_to_temp_v2 = async function open_hash_file_to_temp_v2({local_file_cache_path, hash, host_file_temp_path, filename}) {
// console.log('*** open_hash_file_to_temp_v2() ***');
// console.log(`Host File Cache Path: ${local_file_cache_path}; Hash: ${hash}; Host File Temp Path: ${host_file_temp_path}; Filename: ${filename}`);
// console.log('Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory');
// let subdirectory = hash.substring(0,2);
// let subdirectory_path = path.join(local_file_cache_path, subdirectory);
// if (fs.existsSync(subdirectory_path)) {
// } else {
// console.log(`Hashed file subdirectory not found in cache: ${subdirectory_path}`);
// return null;
// }
// let hash_filename = hash+'.file';
// let full_cache_file_path = path.join(subdirectory_path, hash_filename);
// console.log(full_cache_file_path);
// const file_buffer = fs.readFileSync(full_cache_file_path);
// const file_hash_sha256 = crypto.createHash('sha256');
// file_hash_sha256.update(file_buffer);
// const file_hash_sha256_check = file_hash_sha256.digest('hex');
// if (file_hash_sha256_check == hash) {
// console.log('File hash match', file_hash_sha256_check);
// } else {
// console.log('File hash does not match', file_hash_sha256_check);
// // await setTimeout(async () => {console.log('Done waiting????'); open_file_clicked = false;}, 5000);
// // sleep(6000);
// // console.log('???????? WAITED X SECONDS ????????');
// return false;
// }
// let open_hash_file_to_temp_result = await ipcRenderer.invoke('open_hash_file_to_temp', subdirectory_path, hash, host_file_temp_path, filename).then((result) => {
// console.log('IPC open hash file to temp finished');
// if (result) {
// console.log('Local hash file was opened from temp directory.');
// return result;
// } else {
// console.log('Local hash file was not opened from the temp directory. Something went wrong.');
// console.log(result);
// return false;
// }
// })
// return open_hash_file_to_temp_result;
// }
// Updated 2022-05-07
export let run_cmd = async function run_cmd({ cmd = null, return_stdout = null }) {
console.log('*** run_cmd() ***');
let run_cmd_result = await native_app
.run_cmd({ cmd: cmd, return_stdout: return_stdout })
.then(function (result) {
if (result) {
console.log('Command ran');
} else {
console.log('Command did not run. Something went wrong.');
return false;
}
if (return_stdout) {
return result;
} else {
return true;
}
});
console.log('Run Command Result:', run_cmd_result);
return run_cmd_result;
};
// Updated 2022-10-27
export let run_cmd_sync = function run_cmd_sync({ cmd = null, return_stdout = null }) {
console.log('*** run_cmd_sync() ***');
let run_cmd_result = native_app.run_cmd_sync({ cmd: cmd, return_stdout: return_stdout });
// if (run_cmd_result) {
// console.log('Command ran');
// } else {
// console.log('Command did not run. Something went wrong.');
// // return false;
// }
// if (return_stdout) {
// return run_cmd_result;
// } else {
// return true;
// }
console.log('Run Command Result:', run_cmd_result);
return run_cmd_result;
};
// Updated 2022-05-07
export let run_osascript = async function run_osascript({
cmd = null,
interactive = false,
language = null,
flags = 'h',
program_file = null
}) {
console.log('*** run_osascript() ***');
let run_osascript_result = await native_app.run_osascript({
cmd: cmd,
interactive: interactive,
language: language,
flags: flags,
program_file: program_file
});
console.log(run_osascript_result);
if (run_osascript_result) {
console.log('Apple Script ran');
} else {
console.log('Apple Script did not run. Something went wrong.');
}
return run_osascript_result;
};
// Updated 2022-05-07
export let get_device_info = async function get_device_info({ event_device_id }) {
console.log('*** get_device_info() ***');
console.log(event_device_id);
let get_device_info_result = await native_app.get_device_info();
console.log(get_device_info_result);
if (get_device_info_result) {
console.log('Success');
} else {
console.log('Failed? Something went wrong.');
}
return get_device_info_result;
};

View File

@@ -0,0 +1,123 @@
/**
* Aether Electron Relay (V3)
* Maps legacy launcher commands to the modern Aether Native Bridge.
*/
const log_lvl = 1;
/**
* Returns the native bridge if available.
*/
function getBridge() {
if (typeof window !== 'undefined' && (window as any).aetherNative) {
return (window as any).aetherNative;
}
return null;
}
/**
* Open a local directory in the OS File Manager.
*/
export async function open_folder(path: string) {
const bridge = getBridge();
if (!bridge) return { success: false, error: 'Native bridge not found' };
if (log_lvl) console.log(`Relay: Opening folder: ${path}`);
return await bridge.openFolder(path);
}
/**
* Launch a file using the OS default application.
*/
export async function launch_file(path: string) {
const bridge = getBridge();
if (!bridge) return { success: false, error: 'Native bridge not found' };
if (log_lvl) console.log(`Relay: Launching file: ${path}`);
return await bridge.launchFile(path);
}
/**
* Legacy compatibility alias for launch_file.
*/
export const open_local_file_v2 = async ({ file_path, filename }: { file_path: string, filename: string }) => {
return launch_file(`${file_path}/${filename}`);
};
/**
* Run a shell command.
*/
export async function run_cmd({ cmd, return_stdout = true }: { cmd: string, return_stdout?: boolean }) {
const bridge = getBridge();
if (!bridge) return { success: false, error: 'Native bridge not found' };
if (log_lvl) console.log(`Relay: Running command: ${cmd}`);
const result = await bridge.runCommand(cmd);
if (return_stdout) return result.stdout;
return result.success;
}
/**
* Legacy compatibility alias for run_cmd.
*/
export const run_cmd_sync = run_cmd;
/**
* Kill specific processes by name.
*/
export async function kill_processes({ process_name_li = [] }: { process_name_li: string[] }) {
const bridge = getBridge();
if (!bridge) return { success: false, error: 'Native bridge not found' };
for (const name of process_name_li) {
if (log_lvl) console.log(`Relay: Killing process: ${name}`);
await bridge.runCommand(`pkill -f "${name}"`);
}
return true;
}
/**
* Check if a file hash exists in the local cache.
*/
export async function check_hash_file_cache({ cacheRoot, hash }: { cacheRoot: string, hash: string }) {
const bridge = getBridge();
if (!bridge) return false;
return await bridge.checkCache({ cacheRoot, hash });
}
/**
* Download a file to the local cache.
*/
export async function download_to_cache({ url, cacheRoot, hash, apiKey }: { url: string, cacheRoot: string, hash: string, apiKey: string }) {
const bridge = getBridge();
if (!bridge) return { success: false, error: 'Native bridge not found' };
return await bridge.downloadToCache({ url, cacheRoot, hash, apiKey });
}
/**
* Launch a file from the local cache (copies to temp first).
*/
export async function launch_from_cache({ cacheRoot, hash, tempRoot, filename }: { cacheRoot: string, hash: string, tempRoot: string, filename: string }) {
const bridge = getBridge();
if (!bridge) return { success: false, error: 'Native bridge not found' };
return await bridge.launchFromCache({ cacheRoot, hash, tempRoot, filename });
}
/**
* Get system/device info.
*/
export async function get_device_info() {
const bridge = getBridge();
if (!bridge) return null;
return await bridge.getDeviceConfig();
}
/**
* Placeholder for legacy AppleScript execution.
*/
export async function run_osascript({ cmd }: { cmd: string }) {
const bridge = getBridge();
if (!bridge) return null;
return await bridge.runCommand(`osascript -e '${cmd}'`);
}

View File

@@ -645,11 +645,12 @@
} }
// *** Electron Native Mode Detection *** // *** Electron Native Mode Detection ***
// If window.native_app exists, we are running inside the Electron bridge // If window.native_app or window.aetherNative exists, we are running inside the Electron bridge
// @ts-ignore - native_app is injected by the Electron preload script // @ts-ignore - native_app is injected by legacy, aetherNative by new V3 bridge
if (window.native_app) { if (window.native_app || window.aetherNative) {
console.log('ELECTRON: Native environment detected. Switching to native app_mode.'); console.log('ELECTRON: Native environment detected. Switching to native app_mode.');
$events_loc.launcher.app_mode = 'native'; $events_loc.launcher.app_mode = 'native';
$ae_loc.is_native = true;
} }
} }

View File

@@ -148,6 +148,12 @@ export async function load({ fetch, params, parent, route, url }) {
ae_loc_init['host_file_temp_path'] = native_device_config.native_device.host_file_temp_path; ae_loc_init['host_file_temp_path'] = native_device_config.native_device.host_file_temp_path;
ae_loc_init['recording_path'] = native_device_config.native_device.recording_path; ae_loc_init['recording_path'] = native_device_config.native_device.recording_path;
} }
// IMPORTANT: Update API settings with the native-authorized key if present
if (native_device_config.aether_api_key) {
ae_api_init['api_secret_key'] = native_device_config.aether_api_key;
ae_api_headers['x-aether-api-key'] = native_device_config.aether_api_key;
}
} }
} catch (err) { } catch (err) {
console.error('ROOT LOAD: Failed to fetch native device config.', err); console.error('ROOT LOAD: Failed to fetch native device config.', err);

View File

@@ -45,7 +45,9 @@
run_cmd_sync, run_cmd_sync,
run_osascript, run_osascript,
get_device_info get_device_info
} from '$lib/electron/electron_relay.js'; } from '$lib/electron/electron_relay';
import LauncherBackgroundSync from '../../launcher_background_sync.svelte';
// import Event_launcher_menu from '../../launcher_menu.svelte'; // import Event_launcher_menu from '../../launcher_menu.svelte';
// import Event_launcher_session_view from '../../launcher_session_view.svelte'; // import Event_launcher_session_view from '../../launcher_session_view.svelte';

View File

@@ -0,0 +1,126 @@
<script lang="ts">
/**
* launcher_background_sync.svelte
* Handles background pre-caching of event files when running in Native mode.
* Uses configurable timers from the device configuration.
*/
import { onMount, untrack, onDestroy } from 'svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { events_slct } from '$lib/stores/ae_events_stores';
import { db_events } from '$lib/ae_events/db_events';
import * as native from '$lib/electron/electron_relay';
let { log_lvl = 0 } = $props();
let currently_syncing: string | null = $state(null);
let sync_results: Record<string, 'success' | 'error' | 'pending'> = $state({});
let sync_interval: any = null;
// Trigger initial sync and setup interval
onMount(() => {
if (!$ae_loc.is_native) return;
// 1. Initial Immediate Sync
setTimeout(() => {
if (log_lvl) console.log('Sync: Triggering immediate startup sync cycle.');
const files = $events_slct.event_file_obj_li;
if (files && files.length > 0) {
const ids = files.map((f: any) => f.event_file_id);
process_sync_queue(ids);
}
}, 1000);
// 2. Setup Loop Timer
// Default to 60 seconds if not specified in native_device config
const loop_period = $ae_loc.native_device?.check_event_loop_period || 60000;
if (log_lvl) console.log(`Sync: Starting background loop with period: ${loop_period}ms`);
sync_interval = setInterval(() => {
const files = $events_slct.event_file_obj_li;
if (files && files.length > 0) {
const ids = files.map((f: any) => f.event_file_id);
process_sync_queue(ids);
}
}, loop_period);
});
onDestroy(() => {
if (sync_interval) clearInterval(sync_interval);
});
// Reactive Watch: Also trigger if the file list changes significantly
$effect(() => {
if (!$ae_loc.is_native) return;
const files = $events_slct.event_file_obj_li;
if (!files || files.length === 0) return;
untrack(() => {
const file_ids = files.map((f: any) => f.event_file_id);
// Only process if we haven't checked these specific IDs recently
process_sync_queue(file_ids);
});
});
/**
* Iterates through the file list and downloads missing files.
*/
async function process_sync_queue(ids: string[]) {
const cacheRoot = $ae_loc.local_file_cache_path;
if (!cacheRoot) {
console.warn('Sync: local_file_cache_path not set. Skipping sync.');
return;
}
if (log_lvl) console.log(`Sync: Processing queue of ${ids.length} files...`);
for (const id of ids) {
// Skip if already successfully cached in this session to avoid redundant IPC calls
if (sync_results[id] === 'success') continue;
try {
const file_obj = await db_events.file.get(id);
if (!file_obj || !file_obj.hash_sha256) continue;
const exists = await native.check_hash_file_cache({
cacheRoot,
hash: file_obj.hash_sha256
});
if (exists) {
sync_results[id] = 'success';
continue;
}
if (log_lvl) console.log(`Sync: Downloading missing file: ${file_obj.filename}`);
currently_syncing = file_obj.filename;
const url = `${$ae_api.base_url}/v3/crud/hosted_file/${file_obj.hosted_file_id}/download`;
const result = await native.download_to_cache({
url,
cacheRoot,
hash: file_obj.hash_sha256,
apiKey: $ae_api.api_secret_key
});
if (result.success) {
sync_results[id] = 'success';
if (log_lvl) console.log(`Sync: Successfully cached ${file_obj.filename}`);
} else {
console.error(`Sync: Failed to cache ${file_obj.filename}:`, result.error);
sync_results[id] = 'error';
}
} catch (err) {
console.error(`Sync: Error processing file ${id}:`, err);
}
}
currently_syncing = null;
}
</script>
{#if currently_syncing && log_lvl}
<div class="fixed bottom-4 right-4 bg-black/80 text-white p-2 rounded-lg text-[10px] z-[9999] border border-primary-500 animate-pulse">
<span class="fas fa-sync fa-spin mr-2"></span>
Pre-Caching: {currently_syncing}
</div>
{/if}

View File

@@ -22,6 +22,10 @@
events_trigger, events_trigger,
events_trig events_trig
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import * as native from '$lib/electron/electron_relay';
let test_cmd_result = $state('');
</script> </script>
<div <div
@@ -44,6 +48,76 @@
</button> </button>
</div> </div>
<!-- Native OS Handlers Section -->
{#if $ae_loc.is_native}
<section
class:preset-outlined-warning-300-700={$events_loc.launcher.show_section__native_os}
class="native_os w-full preset-outlined-surface-300-700 transition-all mb-2"
>
<h3 class="text-center mb-2 text-sm font-semibold w-full">
<button
onclick={() => {
$events_loc.launcher.show_section__native_os =
!$events_loc.launcher.show_section__native_os;
}}
class="btn btn-sm w-full justify-between"
>
<span>
{#if $events_loc.launcher.show_section__native_os}
<span class="fas fa-chevron-down"></span>
{:else}
<span class="fas fa-chevron-right"></span>
{/if}
Native OS Folders
</span>
<span class="badge variant-filled-success">Active</span>
</button>
</h3>
<div
class="flex flex-col gap-2 p-2 items-center justify-start w-full"
class:hidden={!$events_loc.launcher.show_section__native_os}
>
<div class="grid grid-cols-1 gap-2 w-full">
<button
onclick={() => native.open_folder($ae_loc.local_file_cache_path)}
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500 justify-start"
>
<span class="fas fa-folder-open mr-2"></span> Open Local Cache
</button>
<button
onclick={() => native.open_folder($ae_loc.host_file_temp_path)}
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500 justify-start"
>
<span class="fas fa-folder-open mr-2"></span> Open Host Temp
</button>
<button
onclick={() => native.open_folder($ae_loc.recording_path)}
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500 justify-start"
>
<span class="fas fa-folder-open mr-2"></span> Open Recording Path
</button>
</div>
<div class="w-full border-t border-surface-500/30 pt-2 mt-2">
<button
onclick={async () => {
test_cmd_result = 'Running...';
const res = await native.run_cmd({ cmd: 'whoami && uptime', return_stdout: true });
test_cmd_result = res as string;
}}
class="btn btn-sm variant-soft-secondary w-full"
>
<span class="fas fa-terminal mr-2"></span> Test OS Command
</button>
{#if test_cmd_result}
<pre class="text-[10px] bg-black text-green-500 p-1 mt-1 overflow-x-auto rounded w-full">{test_cmd_result}</pre>
{/if}
</div>
</div>
</section>
{/if}
<!-- <hr class="w-full my-2 border-1 border-gray-200 dark:border-gray-800 " /> --> <!-- <hr class="w-full my-2 border-1 border-gray-200 dark:border-gray-800 " /> -->
<section <section

View File

@@ -3,22 +3,20 @@
log_lvl?: number; log_lvl?: number;
event_file_id: string; event_file_id: string;
event_file_obj: any; event_file_obj: any;
// export let os: string = null;
max_filename_length?: number; max_filename_length?: number;
hide_launch_icon?: boolean; hide_launch_icon?: boolean;
hide_meta?: boolean; hide_meta?: boolean;
hide_created_on?: boolean; hide_created_on?: boolean;
hide_os?: boolean; hide_os?: boolean;
hide_size?: boolean; hide_size?: boolean;
hide_draft?: boolean; // Based on the file purpose hide_draft?: boolean;
show_bak_download?: boolean; show_bak_download?: boolean;
// export let hide_api_download: boolean = true;
btn_size?: string; btn_size?: string;
btn_text_align?: string; btn_text_align?: string;
text_size?: string; text_size?: string;
text_size_md?: string; text_size_md?: string;
session_type?: string; // oral, poster, workshop, symposium, roundtable, other session_type?: string;
open_method?: null | string; // modal, download, native open (download, cache, copy, open), URL open_method?: null | string;
modal_title?: string; modal_title?: string;
modal__title?: any; modal__title?: any;
@@ -51,278 +49,139 @@
modal__event_file_obj = $bindable(null) modal__event_file_obj = $bindable(null)
}: Props = $props(); }: Props = $props();
// *** Import Svelte specific import { onMount } from 'svelte';
// import { preventDefault } from 'svelte/legacy'; import { fade } from 'svelte/transition';
import { onMount, tick } from 'svelte';
import { fade, scale, fly } from 'svelte/transition';
// *** Import other supporting libraries
// *** Import Aether specific variables and functions
import type { key_val } from '$lib/stores/ae_stores'; import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_util } from '$lib/ae_utils/ae_utils';
import { api } from '$lib/api/api'; import { api } from '$lib/api/api';
import { import { ae_loc, ae_api, ae_sess, slct } from '$lib/stores/ae_stores';
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
} from '$lib/stores/ae_stores';
import { core_func } from '$lib/ae_core/ae_core_functions'; import { core_func } from '$lib/ae_core/ae_core_functions';
import { import { events_loc, events_sess, events_slct } from '$lib/stores/ae_events_stores';
events_loc,
events_sess,
events_slct,
events_trigger
} from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events_functions'; import { events_func } from '$lib/ae_events_functions';
import Element_ae_crud from '$lib/elements/element_ae_crud.svelte'; import Element_ae_crud from '$lib/elements/element_ae_crud.svelte';
// *** Functions and Logic // Import the relay
let ae_downloads: key_val = {}; import * as native from '$lib/electron/electron_relay';
let ae_promises: key_val = $state({}); let ae_promises: key_val = $state({});
let ae_tmp: key_val = $state({}); let ae_tmp: key_val = $state({});
let ae_triggers: key_val = $state({}); let ae_triggers: key_val = $state({});
let open_file_clicked: null | boolean = $state(null); let open_file_clicked: null | boolean = $state(null);
let open_file_status: null | string = $state(null); // null, 'checking_cache', 'checking_cache_failed', 'downloading_file', 'downloading_file_failed', 'opening_file', 'opening_file_failed', 'opening_file_success' let open_file_status: null | string = $state(null);
let open_file_status_message: null | string = $state(null); let open_file_status_message: null | string = $state(null);
if (!$events_loc.launcher.screen_saver_img_kv) {
$events_loc.launcher.screen_saver_img_kv = {};
}
let screen_saver_exts = ['jpg', 'png', 'PNG', 'webp']; let screen_saver_exts = ['jpg', 'png', 'PNG', 'webp'];
if (screen_saver_exts.includes(event_file_obj.extension)) {
// $events_loc.launcher.screen_saver_img_kv.push(event_file_obj);
// $events_loc.launcher.screen_saver_img_kv[event_file_id] = event_file_obj;
$events_loc.launcher.screen_saver_img_kv[event_file_id] = Object.create(event_file_obj);
// let temp_obj = Object.create(event_file_obj)
// $events_loc.launcher.screen_saver_img_kv[temp_obj.event_file_id] = Object.create(temp_obj);
// $ae_event_launcher = $ae_event_launcher;
}
onMount(() => { onMount(() => {
console.log('** Component Mounted: ** Event Launcher File Container (Hash Open)');
console.log(`Session Type: ${session_type}; Open Method: ${open_method}`);
if (screen_saver_exts.includes(event_file_obj.extension)) { if (screen_saver_exts.includes(event_file_obj.extension)) {
// $events_loc.launcher.screen_saver_img_kv[event_file_id] = event_file_obj; if (!$events_loc.launcher.screen_saver_img_kv) $events_loc.launcher.screen_saver_img_kv = {};
$events_loc.launcher.screen_saver_img_kv[event_file_id] = { ...event_file_obj };
let temp_obj = Object.create(event_file_obj);
$events_loc.launcher.screen_saver_img_kv[temp_obj.event_file_id] =
Object.create(temp_obj);
// $ae_event_launcher = $ae_event_launcher;
} }
// window.addEventListener('message', function(event) {
// console.log('Message received in event file uploaded manage component:');
// console.log(event);
// if (event.data.type == 'api_download_blob') {
// console.log('Download blob (file) message received:');
// console.log(event.data);
// // Get the event_file_id from the event.data.endpoint value.
// // Example: /event/file/abc123/download
// let endpoint = event.data.endpoint;
// let event_file_id = endpoint.split('/')[3];
// ae_downloads[event_file_id] = {
// 'size_total': event.data.size_total,
// 'size_loaded': event.data.size_loaded,
// 'percent_completed': event.data.percent_completed,
// };
// // let event_file_id = event.data.event_file_id;
// // let filename = event.data.filename;
// // let auto_download = event.data.auto_download;
// // ae_promises[event_file_id]
// // ae_promises[event_file_id] = download_event_file({ 'event_file_id': event_file_id, 'return_file': true, filename: filename, auto_download: auto_download, log_lvl: 1 });
// }
// });
}); });
async function handle_open_file() { async function handle_open_file() {
console.log('*** handle_open_file() ***'); if (log_lvl) console.log('*** handle_open_file() ***');
console.log(
`App Mode: ${$events_loc.launcher.app_mode}; Cache Path: ${$events_loc.launcher.local_file_cache_path}; Temp Path: ${$events_loc.launcher.host_file_temp_path}`
);
// null or "default" is for regular use in their preferred browser. $events_slct.event_file_id = event_file_id;
// "app" mode is for Electron and node.js. $events_slct.event_file_obj = event_file_obj;
// "onsite" mode is for Chrome or Firefox and downloading files with modified extensions and other slight changes.
if ($events_loc.launcher.app_mode == 'native' && window.native_app) { // 1. NATIVE MODE (Electron)
console.log('* ** *** **** BEGIN TESTING **** *** ** *'); if ($ae_loc.is_native && $events_loc.launcher.app_mode === 'native') {
console.log( const cacheRoot = $ae_loc.local_file_cache_path;
'Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory' const tempRoot = $ae_loc.host_file_temp_path;
);
open_file_clicked = true; open_file_clicked = true;
// await tick();
// setTimeout(() => {console.log('Finished waiting to hide the message.'); open_file_clicked = false;}, 15000);
open_file_status = 'checking_cache'; open_file_status = 'checking_cache';
open_file_status_message = 'Checking local cache...'; open_file_status_message = 'Checking local cache...';
let check_hash_file_cache_result = await window.native_app.check_hash_file_cache_v2({
local_file_cache_path: $events_loc.launcher.local_file_cache_path, const exists = await native.check_hash_file_cache({ cacheRoot, hash: event_file_obj.hash_sha256 });
hash: event_file_obj.hash_sha256,
check_hash: true if (!exists) {
});
if (check_hash_file_cache_result) {
console.log('Cached hash file found.');
} else if (check_hash_file_cache_result == null) {
console.log(
`Cached hash file not found. Need to download from API server. Base URL ${$ae_api.base_url}`
);
open_file_status = 'downloading_file'; open_file_status = 'downloading_file';
open_file_status_message = 'Downloading file...'; open_file_status_message = 'Downloading file to cache...';
let download_hash_file_to_cache_result =
await window.native_app.download_hash_file_to_cache_v2({
api_base_url: $ae_api.base_url,
api_base_url_backup: $ae_api.base_url_bak,
local_file_cache_path: $events_loc.launcher.local_file_cache_path,
event_file_id: event_file_id,
hash: event_file_obj.hash_sha256,
verify_hash: true,
overwrite_existing: false
});
// console.log(download_hash_file_to_cache_result);
if (download_hash_file_to_cache_result) {
console.log('Hash file downloaded to cache.');
} else if (download_hash_file_to_cache_result == null) {
console.log('Hash file was not found in cache and or could not be downloaded.');
open_file_status = 'file_not_found';
open_file_status_message =
'File was not found in cache and could not be downloaded.';
setTimeout(() => {
console.log(`Hiding Open File Message: ${open_file_status_message}`);
open_file_clicked = false;
}, 10000); // Default 15 seconds (15000)
return null;
} else {
console.log(
'Hash file may be in the process of being downloaded or something went wrong.'
);
open_file_status = 'downloading_file_or_failed';
open_file_status_message =
'Hash file may be in the process of being downloaded or something went wrong.';
setTimeout(() => {
console.log(`Hiding Open File Message: ${open_file_status_message}`);
open_file_clicked = false;
}, 10000); // Default 15 seconds (15000)
return false;
}
} else {
console.log(
`Cached hash file found, but hash did not match. May still be downloading from the API server. Base URL ${$events_loc.launcher.api.base_url}`
);
open_file_status = 'try_again';
open_file_status_message = 'Please try again...';
return null;
}
setTimeout(() => { const url = `${$ae_api.base_url}/v3/crud/hosted_file/${event_file_obj.hosted_file_id}/download`;
console.log('Finished waiting to hide the message.'); const dlResult = await native.download_to_cache({
open_file_clicked = false; url,
}, 15000); // Default 15 seconds (15000) cacheRoot,
console.log('Cached hash file found and ready to be opened from the temp directory.');
open_file_status = 'opening_file';
open_file_status_message = 'Opening file...';
let filename = '';
if (
(event_file_obj.extension == 'ppt' || event_file_obj.extension == 'pptx') &&
event_file_obj.open_in_os == 'win'
) {
if (event_file_obj.extension == 'ppt') {
filename = event_file_obj.filename.replace('.ppt', '.pptwin');
} else if (event_file_obj.extension == 'pptx') {
filename = event_file_obj.filename.replace('.pptx', '.pptxwin');
}
} else {
filename = event_file_obj.filename;
}
console.log(`Opening ${filename}`);
let electron_open_hash_file_to_temp_result = await window.native_app.open_hash_file_to_temp_v2(
{
local_file_cache_path: $events_loc.launcher.local_file_cache_path,
hash: event_file_obj.hash_sha256, hash: event_file_obj.hash_sha256,
host_file_temp_path: $events_loc.launcher.host_file_temp_path, apiKey: $ae_api.api_secret_key
filename: filename, });
verify_hash: true
if (!dlResult.success) {
open_file_status = 'error';
open_file_status_message = `Download failed: ${dlResult.error}`;
setTimeout(() => open_file_clicked = false, 5000);
return;
} }
);
if (electron_open_hash_file_to_temp_result) {
console.log('Local hash file was opened to temp.');
open_file_status = 'opening_file_success';
open_file_status_message = 'Opening Application';
} else if (electron_open_hash_file_to_temp_result == null) {
console.log('Local hash file was not found. It may still be downloading.');
open_file_status = 'file_not_found';
open_file_status_message = 'File not found!';
} else {
console.log('Local hash file was not opened to temp. Something went wrong.');
open_file_status = 'opening_file_failed';
open_file_status_message = 'Failed to open the file!';
return false;
} }
console.log( open_file_status = 'opening_file';
'Done with test of cache check, download file, and open cached file from temp directory.' open_file_status_message = 'Opening Application';
);
} else if ($events_loc.launcher.app_mode == 'onsite') {
open_file_clicked = true;
await tick();
setTimeout(() => {
console.log('Finished waiting to hide the message.');
open_file_clicked = false;
}, 15000);
} else {
// NOTE: Add first test controller command here.
// $events_sess.launcher.controller_cmd = `ae:open:event_file_id=${$slct.event_file_id}`;
// $events_sess.launcher.controller_trigger_send = true; // Trigger the controller to send.
// $events_sess.launcher.controller_cmd = null; // Reset command to null
const launchResult = await native.launch_from_cache({
cacheRoot,
hash: event_file_obj.hash_sha256,
tempRoot,
filename: event_file_obj.filename
});
if (!launchResult.success) {
open_file_status = 'error';
open_file_status_message = `Failed to open: ${launchResult.error}`;
}
setTimeout(() => open_file_clicked = false, 5000);
}
// 2. ONSITE MODE (Browser with Modified Extensions)
else if ($events_loc.launcher.app_mode === 'onsite') {
open_file_clicked = true; open_file_clicked = true;
await tick(); open_file_status = 'downloading_onsite';
setTimeout(() => { open_file_status_message = 'Downloading (Onsite Mode)...';
console.log('Finished waiting to hide the message.');
open_file_clicked = false; let filename = event_file_obj.filename;
}, 15000); if ((event_file_obj.extension === 'ppt' || event_file_obj.extension === 'pptx') && event_file_obj.open_in_os === 'win') {
filename = event_file_obj.filename + 'win';
}
ae_promises[event_file_id] = api.download_hosted_file({
api_cfg: $ae_api,
hosted_file_id: event_file_obj.hosted_file_id,
return_file: true,
filename: filename,
auto_download: true,
log_lvl: 1
});
setTimeout(() => open_file_clicked = false, 5000);
}
// 3. DEFAULT MODE (Standard Browser)
else {
open_file_clicked = true;
open_file_status = 'downloading_default';
open_file_status_message = 'Downloading...';
ae_promises[event_file_id] = api.download_hosted_file({
api_cfg: $ae_api,
hosted_file_id: event_file_obj.hosted_file_id,
return_file: true,
filename: event_file_obj.filename,
auto_download: true,
log_lvl: 1
});
if ($events_loc.launcher.controller == 'local_push') {
$events_sess.launcher.controller_cmd = `ae_download:hosted_file=${event_file_obj.hosted_file_id}:${event_file_obj.filename}:${event_file_obj.extension}`;
$events_sess.launcher.controller_trigger_send = true;
}
setTimeout(() => open_file_clicked = false, 5000);
} }
} }
// function handle_open_method_poster() {
// console.log('*** handle_open_method_poster() ***');
// $events_loc.launcher.show_modal_event_poster = true;
// if (event_file_obj.extension == 'png' || event_file_obj.extension == 'jpg') {
// $events_loc.launcher.event_poster_file_type = 'image';
// } else if (event_file_obj.extension == 'mp4' || event_file_obj.extension == 'mov') {
// $events_loc.launcher.event_poster_file_type = 'video';
// }
// $events_loc.launcher.poster_src = `/event/file/${event_file_id}/download`;
// $events_loc.launcher.modal_title = modal_title;
// }
function preventDefault<T extends Event>(fn: (event: T) => void) { function preventDefault<T extends Event>(fn: (event: T) => void) {
return function (event: T) { return function (event: T) {
event.preventDefault(); event.preventDefault();
@@ -334,338 +193,81 @@
<div <div
class:justify-between={!hide_meta} class:justify-between={!hide_meta}
class:justify-center={hide_meta} class:justify-center={hide_meta}
class:hidden={hide_draft && class:hidden={hide_draft && (event_file_obj.file_purpose == 'outline' || event_file_obj.file_purpose == 'draft')}
(event_file_obj.file_purpose == 'outline' || event_file_obj.file_purpose == 'draft')} class="event_launcher_file_cont grow flex flex-col md:flex-row flex-wrap gap-1 items-center justify-center max-w-full transition-all"
class="
event_launcher_file_cont
grow
flex flex-col md:flex-row flex-wrap
gap-1 items-center justify-center
max-w-full
transition-all
"
> >
{#if open_file_clicked} {#if open_file_clicked}
<div <div class="open_file_clicked alert" in:fade={{ duration: 250 }} out:fade={{ duration: 2000 }}>
class="open_file_clicked alert" <div class="alert_msg_pulse">
in:fade={{ duration: 250 }} <strong>*** {open_file_status_message || 'Please wait while this file downloads...'} ***</strong>
out:fade={{ duration: 2000 }} </div>
> <p>Most files will automatically be opened full screen.</p>
{#if $events_loc.launcher.app_mode == 'native'} <p>PowerPoint or KeyNote will attempt to display in presenter view.</p>
{#if open_file_status} <p>Please close the file when finished.</p>
<div class="alert_msg_pulse">
<strong>*** {open_file_status_message} ***</strong>
</div>
{/if}
<!-- <strong>*** Please wait while this file is cached... ***</strong> -->
<p>Most files will automatically be opened full screen.</p>
<p>PowerPoint or KeyNote will attempt to display in presenter view.</p>
<p>PDFs, videos, and images will attempt to be displayed mirrored.</p>
<p>Please close the file when finished.</p>
{:else if $events_loc.launcher.app_mode == 'onsite'}
<strong>*** Please wait while this file loads... ***</strong>
<p>Most files will automatically be opened full screen.</p>
<p>PowerPoint or KeyNote will attempt to display in presenter view.</p>
<p>PDFs, videos, and images will attempt to be displayed mirrored.</p>
<p>Please close the file when finished.</p>
{:else}
<strong>*** Please wait while this file downloads... ***</strong>
<p>Onsite in the Speaker Ready Room and conference session rooms:</p>
<ul>
<li>Most files will automatically be opened full screen.</li>
<li>PowerPoint or KeyNote will attempt to display in presenter view.</li>
<li>PDFs, videos, and images will attempt to be displayed mirrored.</li>
<li>Please close the file when finished.</li>
</ul>
{/if}
</div> </div>
{/if} {/if}
<span <span class="event_file_action grow max-w-full flex flex-row flex-wrap gap-1 items-center justify-center">
class="event_file_action grow max-w-full flex flex-row flex-wrap gap-1 items-center justify-center"
>
<!-- First [WORKING!] - Handle opening using a modal. This applies to all Launcher app modes (default, onsite, native) -->
{#if session_type == 'poster' || open_method == 'modal'} {#if session_type == 'poster' || open_method == 'modal'}
<!-- <a
href="/event/file/{event_file_id}/download"
download
class="ae_btn btn_info {btn_size}"
on:click|preventDefault={() => {handle_open_method_poster();}}
data-hash_sha256={event_file_obj.hash_sha256}
data-filename={event_file_obj.filename}
title={event_file_obj.filename}
>
<span class="fas fa-paper-plane" class:hidden="{hide_launch_icon}"></span> {ae_util.shorten_filename({filename: event_file_obj.filename, max_length: max_filename_length})}
</a> -->
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
// modal__title = modal_title;
modal__open_event_file_id = event_file_id; modal__open_event_file_id = event_file_id;
modal__event_file_obj = event_file_obj; modal__event_file_obj = event_file_obj;
if (!modal__title) modal__title = event_file_obj.filename;
// $events_sess.launcher.modal__open_event_file_id = event_file_id;
// $events_sess.launcher.modal__open = event_file_id;
// $events_sess.launcher.modal__open_event_file_id = event_file_id;
// $events_sess.launcher.modal__event_file_obj = event_file_obj;
if (!modal__title) {
modal__title = event_file_obj.filename;
}
// $events_sess.launcher.modal__title = modal__title;
// $events_sess.launcher.modal__img_src = `/event/file/${event_file_id}/download`;
$events_slct.event_file_id = event_file_id; $events_slct.event_file_id = event_file_id;
$events_slct.event_file_obj = event_file_obj; $events_slct.event_file_obj = event_file_obj;
// let as_modal_result = open_event_file_as_modal({
// event_file_id: event_file_id,
// filename: event_file_obj.filename,
// extension: event_file_obj.extension,
// modal_title: modal_title
// });
// if (as_modal_result) {
// console.log($events_loc.launcher);
// console.log(event_file_obj);
// events_loc.launcher.set({...$events_loc.launcher, ...as_modal_result});
// }
if ($events_loc.launcher.controller == 'local_push') {
console.log(
`Local Push Controller Command: ae_open:event_file=${event_file_id}`
);
$events_sess.launcher.controller_cmd = `ae_open:event_file=${event_file_id}`;
$events_sess.launcher.controller_trigger_send = true;
// tick();
core_func.create_ae_obj__activity_log({
api_cfg: $ae_api,
account_id: $ae_loc.account_id,
data_kv: {
name: `Open Poster: ${event_file_obj.filename}`,
object_type: 'event_file',
object_id_random: event_file_id,
action: 'open_poster_w_controller',
meta_json: {
event_file_id: event_file_id,
filename: event_file_obj.filename,
extension: event_file_obj.extension,
modal_title: modal_title
}
},
log_lvl: log_lvl
});
}
}} }}
class:preset-tonal-success={$events_slct.event_file_id == event_file_id} class:preset-tonal-success={$events_slct.event_file_id == event_file_id}
class=" class="btn btn-sm md:btn-md lg:btn-lg preset-tonal-primary border border-primary-500 min-w-96"
btn btn-sm md:btn-md lg:btn-lg
preset-tonal-primary border border-primary-500
min-w-96
"
title={`Open this file in a modal window:\n${event_file_obj.filename}\n[API] SHA256: ${event_file_obj.hash_sha256.slice(0, 10)}...\nHosted ID: ${event_file_obj.hosted_file_id} Event File ID: ${event_file_id}`}
> >
{#if screen_saver_exts.includes(event_file_obj.extension)} {#if screen_saver_exts.includes(event_file_obj.extension)}
<span class="fas fa-chart-bar m-1" class:hidden={hide_launch_icon}></span> <span class="fas fa-chart-bar m-1" class:hidden={hide_launch_icon}></span> Open Poster
Open Poster
<!-- {event_file_id} -->
{:else} {:else}
<span class="fas fa-paper-plane m-1" class:hidden={hide_launch_icon}></span> <span class="fas fa-paper-plane m-1" class:hidden={hide_launch_icon}></span>
{ae_util.shorten_filename({ {ae_util.shorten_filename({ filename: event_file_obj.filename, max_length: max_filename_length })}
filename: event_file_obj.filename,
max_length: max_filename_length
})}
{/if} {/if}
<!-- {$events_sess.launcher.modal__open_event_file_id ?? '-- not set --'} -->
</button> </button>
<!-- {#if ($events_loc.launcher.app_mode == 'native')} -->
<!-- Second [NOT WORKING!!!] - Handle opening a file. This applies to all Launcher app modes (default, onsite, native) -->
{:else if $events_loc.launcher.app_mode == 'native'}
<a
href="/event/file/{event_file_id}/download?use_os=true"
download
class="ae_btn btn_info {btn_size}"
onclick={preventDefault(() => {
handle_open_file();
})}
data-hash_sha256={event_file_obj.hash_sha256}
data-filename={event_file_obj.filename}
title={`${event_file_obj.filename} [A] -- SHA256 hash: ${event_file_obj.hash_sha256.slice(0, 10)}...`}
>
<span class="fas fa-paper-plane" class:hidden={hide_launch_icon}></span>
{ae_util.shorten_filename({
filename: event_file_obj.filename,
max_length: max_filename_length
})}
</a>
<!-- Third [NOT WORKING!!!] - Handle opening a file. This applies to all Launcher app modes (default, onsite, native) -->
<!-- {:else if ($events_loc.launcher.app_mode == 'onsite' && (event_file_obj.extension == 'ppt' || event_file_obj.extension == 'pptx') && event_file_obj.open_in_os == 'win')}
<a
href="/event/file/{event_file_id}/download?use_os=true" download
class="ae_btn btn_info {btn_size}"
on:click={() => {handle_open_file();}}
data-hash_sha256={event_file_obj.hash_sha256}
data-filename={event_file_obj.filename}
title={`${event_file_obj.filename} [A] -- SHA256 hash: ${event_file_obj.hash_sha256.slice(0, 10)}...`}
>
<span class="fas fa-paper-plane" class:hidden="{hide_launch_icon}"></span> {ae_util.shorten_filename({filename: event_file_obj.filename, max_length: max_filename_length})}
</a> -->
<!-- Last [WORKING!] - Handle opening a file. This applies to all Launcher app modes (default, onsite, native) -->
{:else} {:else}
<button <button
type="button" type="button"
onclick={() => { onclick={handle_open_file}
let new_filename = event_file_obj.filename;
if (
$events_loc.launcher.app_mode == 'onsite' &&
(event_file_obj.extension == 'ppt' || event_file_obj.extension == 'pptx') &&
event_file_obj.open_in_os == 'win'
) {
// Example: the_new_filename.pptxwin
new_filename = event_file_obj.filename + 'win';
}
// ae_promises[event_file_id]
ae_promises[event_file_id] = api.download_hosted_file({
api_cfg: $ae_api,
hosted_file_id: event_file_obj.hosted_file_id, // +'x'
return_file: true,
filename: new_filename,
auto_download: true,
log_lvl: 1
});
$events_slct.event_file_id = event_file_id;
$events_slct.event_file_obj = event_file_obj;
// window.postMessage({ type: 'download_event_file', event_file_id: event_file_id, filename: event_file_obj.filename, auto_download: true }, '*');
if ($events_loc.launcher.controller == 'local_push') {
console.log(
`Local Push Controller Command: ae_download:hosted_file=${event_file_obj.hosted_file_id}`
);
$events_sess.launcher.controller_cmd = `ae_download:hosted_file=${event_file_obj.hosted_file_id}:${new_filename}:${event_file_obj.extension}`;
$events_sess.launcher.controller_trigger_send = true;
// tick();
}
}}
class:outline-2={$events_slct.event_file_id == event_file_id} class:outline-2={$events_slct.event_file_id == event_file_id}
class=" class="btn {btn_size} gap-1 justify-between min-w-full w-full max-w-96 preset-tonal-primary border border-primary-500"
btn {btn_size}
gap-1 justify-between
min-w-full w-full max-w-96
preset-tonal-primary border border-primary-500
"
title={`Download this file:\n${event_file_obj.filename}\n[API] SHA256: ${event_file_obj.hash_sha256.slice(0, 10)}...\nHosted ID: ${event_file_obj.hosted_file_id} Event File ID: ${event_file_id}`}
> >
<span class="shrink text-xs border-r border-gray-400 pr-1"> <span class="shrink text-xs border-r border-gray-400 pr-1">
{#await ae_promises[event_file_id]} {#await ae_promises[event_file_id]}
<!-- <span class="shrink text-sm p-0"> -->
<span class="fas fa-spinner fa-spin mx-0.5"></span> <span class="fas fa-spinner fa-spin mx-0.5"></span>
<span class=""> <span>
Downloading
{#if $ae_sess.api_download_kv[event_file_obj.hosted_file_id]} {#if $ae_sess.api_download_kv[event_file_obj.hosted_file_id]}
{$ae_sess.api_download_kv[event_file_obj.hosted_file_id] {$ae_sess.api_download_kv[event_file_obj.hosted_file_id].percent_completed}%
.percent_completed}% {:else}
...
{/if} {/if}
</span> </span>
<!-- </span> -->
{:then result} {:then result}
<span <span class="fas fa-{ae_util.file_extension_icon(event_file_obj.extension)} mx-0.5"></span>
class="fas fa-{ae_util.file_extension_icon(
event_file_obj.extension
)} mx-0.5"
></span>
{event_file_obj.extension} {event_file_obj.extension}
{#if result === null || result === false} {#if result === null || result === false}
<span class="text-error-500"> <span class="text-error-500"><span class="fas fa-exclamation-triangle mx-1"></span>Failed!</span>
<span class="fas fa-exclamation-triangle mx-1"></span>
Failed!
</span>
{/if} {/if}
{:catch error} {:catch error}
<span class="text-error-500" title={error?.message}> <span class="text-error-500" title={error?.message}><span class="fas fa-exclamation-circle mx-0.5"></span>Error!</span>
<span class="fas fa-exclamation-circle mx-0.5"></span>
Error!
</span>
{/await} {/await}
</span> </span>
<span <span class="grow {text_size} {text_size_md} w-full max-w-full overflow-hidden text-ellipsis {btn_text_align}">
class=" {ae_util.shorten_string({ string: event_file_obj.filename_no_ext, begin_length: 45, max_length: 65 })}
grow {text_size} {text_size_md} w-full max-w-full
overflow-hidden text-ellipsis
{btn_text_align}
"
>
<!-- {event_file_obj.filename_no_ext} -->
<!-- {ae_util.shorten_filename({filename: event_file_obj.filename_w_ext, max_length: 40})} -->
{ae_util.shorten_string({
string: event_file_obj.filename_no_ext,
begin_length: 45,
max_length: 65
})}
<!-- {event_file_obj.filename_no_ext.slice(0, 35)} -->
</span> </span>
<span <span class="badge my-0 py-0.5 preset-tonal-success hover:preset-filled-success-500 text-xs xl:text-sm" class:hidden={!event_file_obj.file_purpose}>
class="
badge my-0 py-0.5
preset-tonal-success hover:preset-filled-success-500 text-xs xl:text-sm
"
class:hidden={!event_file_obj.file_purpose}
>
{event_file_obj.file_purpose} {event_file_obj.file_purpose}
</span> </span>
</button> </button>
{/if} {/if}
</span> </span>
<span <span class="event_file_meta grow text-sm text-gray-500 flex flex-col sm:flex-row gap-1 wrap items-center justify-between w-64 max-w-80 font-mono" class:hidden={hide_meta}>
class="event_file_meta grow text-sm text-gray-500 flex flex-col sm:flex-row gap-1 wrap items-center justify-between w-64 max-w-80 font-mono"
class:hidden={hide_meta}
>
<button
type="button"
onclick={() => {
// let new_filename = event_file_obj.filename;
// if ($events_loc.launcher.app_mode == 'onsite' && (event_file_obj.extension == 'ppt' || event_file_obj.extension == 'pptx') && event_file_obj.open_in_os == 'win') {
// // Example: the_new_filename.pptxwin
// new_filename = event_file_obj.filename + 'win';
// }
// ae_promises[event_file_id]
ae_promises[event_file_id] = api.download_hosted_file({
api_cfg: $ae_api,
hosted_file_id: event_file_obj.hosted_file_id, // +'x'
return_file: true,
filename: event_file_obj.filename,
auto_download: true,
log_lvl: 1
});
// window.postMessage({ type: 'download_event_file', event_file_id: event_file_id, filename: event_file_obj.filename, auto_download: true }, '*');
}}
class="event_file_os"
class:hidden={hide_os || 1 == 1}
>
{#if event_file_obj.open_in_os == 'win'}
<span class="fab fa-windows"></span> Win
{:else if event_file_obj.open_in_os == 'mac'}
<span class="fab fa-apple"></span> Mac
{:else}
<span class="fas fa-folder-open"></span>
{/if}
</button>
<Element_ae_crud <Element_ae_crud
trigger_patch={ae_triggers.open_in_os} trigger_patch={ae_triggers.open_in_os}
api_cfg={$ae_api} api_cfg={$ae_api}
@@ -674,124 +276,37 @@
field_name={'open_in_os'} field_name={'open_in_os'}
field_type={'button'} field_type={'button'}
field_value={ae_tmp.value__open_in_os} field_value={ae_tmp.value__open_in_os}
allow_null={false} on:ae_crud_updated={() => {
hide_edit_btn={true} events_func.load_ae_obj_id__event_file({ api_cfg: $ae_api, event_file_id: event_file_obj?.event_file_id, log_lvl });
outline_element={false}
show_crud={false}
display_inline={true}
class_li={''}
on:ae_crud_updated={(e) => {
console.log(`ae_crud_updated:`, e.detail);
events_func.load_ae_obj_id__event_file({
api_cfg: $ae_api,
event_file_id: event_file_obj?.event_file_id,
log_lvl: log_lvl
});
}} }}
> >
<!-- {(event_file_obj?.open_in_os ? 'Hidden' : 'Not Hidden')} -->
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
// Go from null to win, win to mac, mac to null, null to win, etc. if (!event_file_obj?.open_in_os) ae_tmp.value__open_in_os = 'win';
if (!event_file_obj?.open_in_os) { else if (event_file_obj?.open_in_os == 'win') ae_tmp.value__open_in_os = 'mac';
ae_tmp.value__open_in_os = 'win'; else ae_tmp.value__open_in_os = null;
} else if (event_file_obj?.open_in_os == 'win') {
ae_tmp.value__open_in_os = 'mac';
} else if (event_file_obj?.open_in_os == 'mac') {
ae_tmp.value__open_in_os = null;
} else {
ae_tmp.value__open_in_os = null;
}
// $events_slct.exhibit_tracking_obj.open_in_os = !event_file_obj?.open_in_os;
ae_triggers.open_in_os = true; ae_triggers.open_in_os = true;
}} }}
class="btn btn-sm transition-all group" class="btn btn-sm transition-all group"
class:preset-tonal-success={event_file_obj?.open_in_os == 'win'} class:preset-tonal-success={event_file_obj?.open_in_os == 'win'}
class:preset-tonal-warning={event_file_obj?.open_in_os == 'mac'} class:preset-tonal-warning={event_file_obj?.open_in_os == 'mac'}
disabled={!$ae_loc.trusted_access} disabled={!$ae_loc.trusted_access}
title="Open with: {event_file_obj?.open_in_os ?? 'Default'}"
> >
{#if event_file_obj?.open_in_os == 'win'} {#if event_file_obj?.open_in_os == 'win'}<span class="fab fa-windows m-1"></span>
<span class="fab fa-windows m-1"></span> {:else if event_file_obj?.open_in_os == 'mac'}<span class="fab fa-apple m-1"></span>
<span class="hidden group-hover:inline-block"> Windows </span> {:else}<span class="fas fa-folder-open m-1"></span>{/if}
{:else if event_file_obj?.open_in_os == 'mac'}
<!-- <span class="fas fa-toggle-off m-1"></span> -->
<span class="fab fa-apple m-1"></span>
<span class="hidden group-hover:inline-block"> macOS </span>
{:else}
<span class="fas fa-folder-open m-1"></span>
<span class="hidden group-hover:inline-block"> Default </span>
{/if}
<!-- {@html (event_file_obj?.hide ? '<span class="fas fa-eye m-1"></span> Unhide?' : '<span class="fas fa-eye-slash m-1"></span> Hide?')} -->
</button> </button>
</Element_ae_crud> </Element_ae_crud>
<span <span class="event_file_created_on text-xs text-center flex flex-row gap-1 items-center justify-end w-24 md:w-44 preset-filled-surface-100-900 rounded px-1 py-0.5" class:hidden={hide_created_on}>
class="
event_file_created_on
text-xs text-center
flex flex-row gap-1 items-center justify-end
w-24 md:w-44
preset-filled-surface-100-900
rounded
px-1
py-0.5
"
class:hidden={hide_created_on}
>
<span class="fas fa-calendar-day"></span> <span class="fas fa-calendar-day"></span>
<span class="flex flex-row flex-wrap md:flex-nowrap gap-1 items-center justify-end"> <span class="w-18">{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'date_short')}</span>
<span class="w-18">
{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'date_short')}
</span>
<span class="w-18">
<!-- <span class="fas fa-clock"></span> -->
{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'time_12_short')}
</span>
</span>
</span> </span>
<span <span class="event_file_size text-xs text-center flex flex-row gap-1 items-center justify-end preset-filled-surface-100-900 w-22 max-w-28 rounded py-0.5" class:hidden={hide_size}>
class="
event_file_size
text-xs text-center
flex flex-row gap-1 items-center justify-end
preset-filled-surface-100-900
w-22 max-w-28
rounded
py-0.5
"
class:hidden={hide_size}
>
<span class="fas fa-save"></span> <span class="fas fa-save"></span>
{#if event_file_obj.file_size}{ae_util.format_bytes(event_file_obj.file_size)}{/if} {#if event_file_obj.file_size}{ae_util.format_bytes(event_file_obj.file_size)}{/if}
</span> </span>
<!-- {#if ($events_loc.launcher.app_mode == 'native' || $events_loc.launcher.app_mode == 'onsite')} -->
<!-- {#if (show_bak_download)} -->
<!-- <a href="/event/file/{event_file_id}/download" class="event_file_download" class:hidden="{!show_bak_download}" title="Download with original filename and extension"><span class="fas fa-download"></span></a> -->
<!-- <a href="/event/file/{event_file_id}/download?filename={event_file_obj.filename}&use_os=true" class="ae_btn btn_info {btn_size}">{event_file_obj.filename}</a> -->
<!-- {/if} -->
</span> </span>
<!-- <button
class="ae_btn btn_xs btn_outline_debug"
class:hidden="{hide_api_download}"
on:click={() => {
// if (!confirm('Download file from API server?')) {
// return false;
// }
download_event_file({ 'event_file_id': event_file_id, 'return_file': true, filename: event_file_obj.filename, auto_download: true, log_lvl: 1 });
}}
title="Download file from API server?"
>
<span class="fas fa-download"></span>
</button> -->
</div> </div>
<style>
</style>

View File

@@ -83,7 +83,7 @@
// Check if results are different than the current $events_slct.event_file_obj_li // Check if results are different than the current $events_slct.event_file_obj_li
if ($events_slct.event_file_obj_li && results) { if ($events_slct.event_file_obj_li && results) {
if (JSON.stringify($events_slct.event_file_obj_li) !== JSON.stringify(results)) { if (JSON.stringify($events_slct.event_file_obj_li) !== JSON.stringify(results)) {
$events_slct.event_file_obj_li = { ...results }; $events_slct.event_file_obj_li = [...results];
if (log_lvl) { if (log_lvl) {
console.log( console.log(
`$events_slct.event_file_obj_li has changed for event_session_id: ${slct__event_session_id}`, `$events_slct.event_file_obj_li has changed for event_session_id: ${slct__event_session_id}`,
@@ -97,6 +97,8 @@
); );
} }
} }
} else if (results) {
$events_slct.event_file_obj_li = [...results];
} }
return results; return results;

View File

@@ -131,10 +131,10 @@
<!-- Preview Area --> <!-- Preview Area -->
<div class="form-control"> <div class="form-control">
<label class="label flex justify-between items-center"> <div class="label flex justify-between items-center">
<span class="label-text">Preview</span> <span class="label-text">Preview</span>
<span class="text-[10px] opacity-50 uppercase tracking-widest">{selected_template.name}</span> <span class="text-[10px] opacity-50 uppercase tracking-widest">{selected_template.name}</span>
</label> </div>
<textarea <textarea
class="textarea h-64 font-mono text-xs bg-gray-50 dark:bg-gray-900" class="textarea h-64 font-mono text-xs bg-gray-50 dark:bg-gray-900"
readonly readonly