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:
@@ -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).
|
||||
- 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
|
||||
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]`
|
||||
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)
|
||||
...
|
||||
macOS requires explicit user consent for several features. The new app will handle these during the "Splash Screen" phase.
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
123
src/lib/electron/electron_relay.ts
Normal file
123
src/lib/electron/electron_relay.ts
Normal 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}'`);
|
||||
}
|
||||
@@ -645,11 +645,12 @@
|
||||
}
|
||||
|
||||
// *** Electron Native Mode Detection ***
|
||||
// If window.native_app exists, we are running inside the Electron bridge
|
||||
// @ts-ignore - native_app is injected by the Electron preload script
|
||||
if (window.native_app) {
|
||||
// If window.native_app or window.aetherNative exists, we are running inside the Electron bridge
|
||||
// @ts-ignore - native_app is injected by legacy, aetherNative by new V3 bridge
|
||||
if (window.native_app || window.aetherNative) {
|
||||
console.log('ELECTRON: Native environment detected. Switching to native app_mode.');
|
||||
$events_loc.launcher.app_mode = 'native';
|
||||
$ae_loc.is_native = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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['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) {
|
||||
console.error('ROOT LOAD: Failed to fetch native device config.', err);
|
||||
|
||||
@@ -45,7 +45,9 @@
|
||||
run_cmd_sync,
|
||||
run_osascript,
|
||||
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_session_view from '../../launcher_session_view.svelte';
|
||||
|
||||
@@ -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}
|
||||
@@ -22,6 +22,10 @@
|
||||
events_trigger,
|
||||
events_trig
|
||||
} from '$lib/stores/ae_events_stores';
|
||||
|
||||
import * as native from '$lib/electron/electron_relay';
|
||||
|
||||
let test_cmd_result = $state('');
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -44,6 +48,76 @@
|
||||
</button>
|
||||
</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 " /> -->
|
||||
|
||||
<section
|
||||
|
||||
@@ -3,22 +3,20 @@
|
||||
log_lvl?: number;
|
||||
event_file_id: string;
|
||||
event_file_obj: any;
|
||||
// export let os: string = null;
|
||||
max_filename_length?: number;
|
||||
hide_launch_icon?: boolean;
|
||||
hide_meta?: boolean;
|
||||
hide_created_on?: boolean;
|
||||
hide_os?: boolean;
|
||||
hide_size?: boolean;
|
||||
hide_draft?: boolean; // Based on the file purpose
|
||||
hide_draft?: boolean;
|
||||
show_bak_download?: boolean;
|
||||
// export let hide_api_download: boolean = true;
|
||||
btn_size?: string;
|
||||
btn_text_align?: string;
|
||||
text_size?: string;
|
||||
text_size_md?: string;
|
||||
session_type?: string; // oral, poster, workshop, symposium, roundtable, other
|
||||
open_method?: null | string; // modal, download, native open (download, cache, copy, open), URL
|
||||
session_type?: string;
|
||||
open_method?: null | string;
|
||||
modal_title?: string;
|
||||
|
||||
modal__title?: any;
|
||||
@@ -51,278 +49,139 @@
|
||||
modal__event_file_obj = $bindable(null)
|
||||
}: Props = $props();
|
||||
|
||||
// *** Import Svelte specific
|
||||
// import { preventDefault } from 'svelte/legacy';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { fade, scale, fly } from 'svelte/transition';
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
// *** Import other supporting libraries
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { api } from '$lib/api/api';
|
||||
import {
|
||||
ae_snip,
|
||||
ae_loc,
|
||||
ae_sess,
|
||||
ae_api,
|
||||
ae_trig,
|
||||
slct,
|
||||
slct_trigger
|
||||
} from '$lib/stores/ae_stores';
|
||||
import { ae_loc, ae_api, ae_sess, slct } from '$lib/stores/ae_stores';
|
||||
import { core_func } from '$lib/ae_core/ae_core_functions';
|
||||
import {
|
||||
events_loc,
|
||||
events_sess,
|
||||
events_slct,
|
||||
events_trigger
|
||||
} from '$lib/stores/ae_events_stores';
|
||||
import { events_loc, events_sess, events_slct } from '$lib/stores/ae_events_stores';
|
||||
import { events_func } from '$lib/ae_events_functions';
|
||||
|
||||
import Element_ae_crud from '$lib/elements/element_ae_crud.svelte';
|
||||
|
||||
// Import the relay
|
||||
import * as native from '$lib/electron/electron_relay';
|
||||
|
||||
// *** Functions and Logic
|
||||
let ae_downloads: key_val = {};
|
||||
let ae_promises: key_val = $state({});
|
||||
let ae_tmp: key_val = $state({});
|
||||
let ae_triggers: key_val = $state({});
|
||||
|
||||
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);
|
||||
|
||||
if (!$events_loc.launcher.screen_saver_img_kv) {
|
||||
$events_loc.launcher.screen_saver_img_kv = {};
|
||||
}
|
||||
|
||||
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(() => {
|
||||
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)) {
|
||||
// $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;
|
||||
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 };
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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}`
|
||||
);
|
||||
if (log_lvl) console.log('*** handle_open_file() ***');
|
||||
|
||||
$events_slct.event_file_id = event_file_id;
|
||||
$events_slct.event_file_obj = event_file_obj;
|
||||
|
||||
// null or "default" is for regular use in their preferred browser.
|
||||
// "app" mode is for Electron and node.js.
|
||||
// "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) {
|
||||
console.log('* ** *** **** BEGIN TESTING **** *** ** *');
|
||||
console.log(
|
||||
'Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory'
|
||||
);
|
||||
// 1. NATIVE MODE (Electron)
|
||||
if ($ae_loc.is_native && $events_loc.launcher.app_mode === 'native') {
|
||||
const cacheRoot = $ae_loc.local_file_cache_path;
|
||||
const tempRoot = $ae_loc.host_file_temp_path;
|
||||
|
||||
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_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,
|
||||
hash: event_file_obj.hash_sha256,
|
||||
check_hash: true
|
||||
});
|
||||
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}`
|
||||
);
|
||||
|
||||
const exists = await native.check_hash_file_cache({ cacheRoot, hash: event_file_obj.hash_sha256 });
|
||||
|
||||
if (!exists) {
|
||||
open_file_status = 'downloading_file';
|
||||
open_file_status_message = 'Downloading file...';
|
||||
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(() => {
|
||||
console.log('Finished waiting to hide the message.');
|
||||
open_file_clicked = false;
|
||||
}, 15000); // Default 15 seconds (15000)
|
||||
|
||||
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,
|
||||
open_file_status_message = 'Downloading file to cache...';
|
||||
|
||||
const url = `${$ae_api.base_url}/v3/crud/hosted_file/${event_file_obj.hosted_file_id}/download`;
|
||||
const dlResult = await native.download_to_cache({
|
||||
url,
|
||||
cacheRoot,
|
||||
hash: event_file_obj.hash_sha256,
|
||||
host_file_temp_path: $events_loc.launcher.host_file_temp_path,
|
||||
filename: filename,
|
||||
verify_hash: true
|
||||
apiKey: $ae_api.api_secret_key
|
||||
});
|
||||
|
||||
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(
|
||||
'Done with test of cache check, download file, and open cached file from temp directory.'
|
||||
);
|
||||
} 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
|
||||
open_file_status = 'opening_file';
|
||||
open_file_status_message = 'Opening Application';
|
||||
|
||||
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;
|
||||
await tick();
|
||||
setTimeout(() => {
|
||||
console.log('Finished waiting to hide the message.');
|
||||
open_file_clicked = false;
|
||||
}, 15000);
|
||||
open_file_status = 'downloading_onsite';
|
||||
open_file_status_message = 'Downloading (Onsite Mode)...';
|
||||
|
||||
let filename = event_file_obj.filename;
|
||||
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) {
|
||||
return function (event: T) {
|
||||
event.preventDefault();
|
||||
@@ -334,338 +193,81 @@
|
||||
<div
|
||||
class:justify-between={!hide_meta}
|
||||
class:justify-center={hide_meta}
|
||||
class:hidden={hide_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:hidden={hide_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"
|
||||
>
|
||||
{#if open_file_clicked}
|
||||
<div
|
||||
class="open_file_clicked alert"
|
||||
in:fade={{ duration: 250 }}
|
||||
out:fade={{ duration: 2000 }}
|
||||
>
|
||||
{#if $events_loc.launcher.app_mode == 'native'}
|
||||
{#if open_file_status}
|
||||
<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 class="open_file_clicked alert" in:fade={{ duration: 250 }} out:fade={{ duration: 2000 }}>
|
||||
<div class="alert_msg_pulse">
|
||||
<strong>*** {open_file_status_message || 'Please wait while this file downloads...'} ***</strong>
|
||||
</div>
|
||||
<p>Most files will automatically be opened full screen.</p>
|
||||
<p>PowerPoint or KeyNote will attempt to display in presenter view.</p>
|
||||
<p>Please close the file when finished.</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<span
|
||||
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) -->
|
||||
<span class="event_file_action grow max-w-full flex flex-row flex-wrap gap-1 items-center justify-center">
|
||||
{#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
|
||||
type="button"
|
||||
onclick={() => {
|
||||
// modal__title = modal_title;
|
||||
modal__open_event_file_id = event_file_id;
|
||||
modal__event_file_obj = event_file_obj;
|
||||
|
||||
// $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`;
|
||||
|
||||
if (!modal__title) modal__title = event_file_obj.filename;
|
||||
$events_slct.event_file_id = event_file_id;
|
||||
$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="
|
||||
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}`}
|
||||
class="btn btn-sm md:btn-md lg:btn-lg preset-tonal-primary border border-primary-500 min-w-96"
|
||||
>
|
||||
{#if screen_saver_exts.includes(event_file_obj.extension)}
|
||||
<span class="fas fa-chart-bar m-1" class:hidden={hide_launch_icon}></span>
|
||||
Open Poster
|
||||
<!-- {event_file_id} -->
|
||||
<span class="fas fa-chart-bar m-1" class:hidden={hide_launch_icon}></span> Open Poster
|
||||
{:else}
|
||||
<span class="fas fa-paper-plane m-1" class:hidden={hide_launch_icon}></span>
|
||||
{ae_util.shorten_filename({
|
||||
filename: event_file_obj.filename,
|
||||
max_length: max_filename_length
|
||||
})}
|
||||
{ae_util.shorten_filename({ filename: event_file_obj.filename, max_length: max_filename_length })}
|
||||
{/if}
|
||||
<!-- {$events_sess.launcher.modal__open_event_file_id ?? '-- not set --'} -->
|
||||
</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}
|
||||
<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: 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();
|
||||
}
|
||||
}}
|
||||
onclick={handle_open_file}
|
||||
class:outline-2={$events_slct.event_file_id == event_file_id}
|
||||
class="
|
||||
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}`}
|
||||
class="btn {btn_size} gap-1 justify-between min-w-full w-full max-w-96 preset-tonal-primary border border-primary-500"
|
||||
>
|
||||
<span class="shrink text-xs border-r border-gray-400 pr-1">
|
||||
{#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="">
|
||||
Downloading
|
||||
<span>
|
||||
{#if $ae_sess.api_download_kv[event_file_obj.hosted_file_id]}
|
||||
{$ae_sess.api_download_kv[event_file_obj.hosted_file_id]
|
||||
.percent_completed}%
|
||||
{$ae_sess.api_download_kv[event_file_obj.hosted_file_id].percent_completed}%
|
||||
{:else}
|
||||
...
|
||||
{/if}
|
||||
</span>
|
||||
<!-- </span> -->
|
||||
{:then result}
|
||||
<span
|
||||
class="fas fa-{ae_util.file_extension_icon(
|
||||
event_file_obj.extension
|
||||
)} mx-0.5"
|
||||
></span>
|
||||
<span class="fas fa-{ae_util.file_extension_icon(event_file_obj.extension)} mx-0.5"></span>
|
||||
{event_file_obj.extension}
|
||||
{#if result === null || result === false}
|
||||
<span class="text-error-500">
|
||||
<span class="fas fa-exclamation-triangle mx-1"></span>
|
||||
Failed!
|
||||
</span>
|
||||
<span class="text-error-500"><span class="fas fa-exclamation-triangle mx-1"></span>Failed!</span>
|
||||
{/if}
|
||||
{:catch error}
|
||||
<span class="text-error-500" title={error?.message}>
|
||||
<span class="fas fa-exclamation-circle mx-0.5"></span>
|
||||
Error!
|
||||
</span>
|
||||
<span class="text-error-500" title={error?.message}><span class="fas fa-exclamation-circle mx-0.5"></span>Error!</span>
|
||||
{/await}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="
|
||||
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 class="grow {text_size} {text_size_md} w-full max-w-full overflow-hidden text-ellipsis {btn_text_align}">
|
||||
{ae_util.shorten_string({ string: event_file_obj.filename_no_ext, begin_length: 45, max_length: 65 })}
|
||||
</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}
|
||||
>
|
||||
<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}>
|
||||
{event_file_obj.file_purpose}
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
</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}
|
||||
>
|
||||
<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>
|
||||
|
||||
<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}>
|
||||
<Element_ae_crud
|
||||
trigger_patch={ae_triggers.open_in_os}
|
||||
api_cfg={$ae_api}
|
||||
@@ -674,124 +276,37 @@
|
||||
field_name={'open_in_os'}
|
||||
field_type={'button'}
|
||||
field_value={ae_tmp.value__open_in_os}
|
||||
allow_null={false}
|
||||
hide_edit_btn={true}
|
||||
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
|
||||
});
|
||||
on:ae_crud_updated={() => {
|
||||
events_func.load_ae_obj_id__event_file({ api_cfg: $ae_api, event_file_id: event_file_obj?.event_file_id, log_lvl });
|
||||
}}
|
||||
>
|
||||
<!-- {(event_file_obj?.open_in_os ? 'Hidden' : 'Not Hidden')} -->
|
||||
<button
|
||||
type="button"
|
||||
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';
|
||||
} 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;
|
||||
if (!event_file_obj?.open_in_os) ae_tmp.value__open_in_os = 'win';
|
||||
else if (event_file_obj?.open_in_os == 'win') ae_tmp.value__open_in_os = 'mac';
|
||||
else ae_tmp.value__open_in_os = null;
|
||||
ae_triggers.open_in_os = true;
|
||||
}}
|
||||
class="btn btn-sm transition-all group"
|
||||
class:preset-tonal-success={event_file_obj?.open_in_os == 'win'}
|
||||
class:preset-tonal-warning={event_file_obj?.open_in_os == 'mac'}
|
||||
disabled={!$ae_loc.trusted_access}
|
||||
title="Open with: {event_file_obj?.open_in_os ?? 'Default'}"
|
||||
>
|
||||
{#if event_file_obj?.open_in_os == 'win'}
|
||||
<span class="fab fa-windows m-1"></span>
|
||||
<span class="hidden group-hover:inline-block"> Windows </span>
|
||||
{: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?')} -->
|
||||
{#if event_file_obj?.open_in_os == 'win'}<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>
|
||||
{:else}<span class="fas fa-folder-open m-1"></span>{/if}
|
||||
</button>
|
||||
</Element_ae_crud>
|
||||
|
||||
<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}
|
||||
>
|
||||
<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}>
|
||||
<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">
|
||||
<!-- <span class="fas fa-clock"></span> -->
|
||||
{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'time_12_short')}
|
||||
</span>
|
||||
</span>
|
||||
<span class="w-18">{ae_util.iso_datetime_formatter(event_file_obj.created_on, 'date_short')}</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}
|
||||
>
|
||||
<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}>
|
||||
<span class="fas fa-save"></span>
|
||||
{#if event_file_obj.file_size}{ae_util.format_bytes(event_file_obj.file_size)}{/if}
|
||||
</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>
|
||||
|
||||
<!-- <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>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@
|
||||
// Check if results are different than the current $events_slct.event_file_obj_li
|
||||
if ($events_slct.event_file_obj_li && 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) {
|
||||
console.log(
|
||||
`$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;
|
||||
|
||||
@@ -131,10 +131,10 @@
|
||||
|
||||
<!-- Preview Area -->
|
||||
<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="text-[10px] opacity-50 uppercase tracking-widest">{selected_template.name}</span>
|
||||
</label>
|
||||
</div>
|
||||
<textarea
|
||||
class="textarea h-64 font-mono text-xs bg-gray-50 dark:bg-gray-900"
|
||||
readonly
|
||||
|
||||
Reference in New Issue
Block a user