botapp/js/app-extensions.js
Rodrigo Rodriguez (Pragmatismo) 64e11506a2 Initial botapp - Tauri wrapper for General Bots
This crate wraps botui (pure web UI) with Tauri for desktop/mobile:
- Native file system access via Tauri commands
- System tray integration (prepared)
- App-specific guides injected at runtime
- Desktop settings and configuration

Architecture:
- botui: Pure web UI (no Tauri deps)
- botapp: Tauri wrapper that loads botui's suite

Files:
- src/desktop/drive.rs: File system commands
- src/desktop/tray.rs: System tray (prepared)
- js/app-extensions.js: Injects app guides into suite
- ui/app-guides/: App-only HTML content
2025-12-04 09:02:10 -03:00

223 lines
7.6 KiB
JavaScript

// BotApp Extensions - Injected by Tauri into botui's suite
// Adds app-only guides and native functionality
//
// This script runs only in the Tauri app context and extends
// the botui suite with desktop-specific features.
(function() {
'use strict';
// App-only guides that will be injected into the suite navigation
const APP_GUIDES = [
{
id: 'local-files',
label: 'Local Files',
icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
<path d="M12 11v6M9 14h6"/>
</svg>`,
hxGet: '/app/guides/local-files.html',
description: 'Access and manage files on your device'
},
{
id: 'native-settings',
label: 'App Settings',
icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>`,
hxGet: '/app/guides/native-settings.html',
description: 'Configure desktop app settings'
}
];
// CSS for app-only elements
const APP_STYLES = `
.app-grid-separator {
grid-column: 1 / -1;
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0;
margin-top: 0.5rem;
border-top: 1px solid var(--border-color, #e0e0e0);
}
.app-grid-separator span {
font-size: 0.75rem;
color: var(--text-secondary, #666);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.app-item.app-only {
position: relative;
}
.app-item.app-only::after {
content: '';
position: absolute;
top: 4px;
right: 4px;
width: 6px;
height: 6px;
background: var(--accent-color, #4a90d9);
border-radius: 50%;
}
`;
/**
* Inject app-specific styles
*/
function injectStyles() {
const styleEl = document.createElement('style');
styleEl.id = 'botapp-styles';
styleEl.textContent = APP_STYLES;
document.head.appendChild(styleEl);
}
/**
* Inject app-only guides into the suite navigation
*/
function injectAppGuides() {
const grid = document.querySelector('.app-grid');
if (!grid) {
// Retry if grid not found yet (page still loading)
setTimeout(injectAppGuides, 100);
return;
}
// Check if already injected
if (document.querySelector('.app-grid-separator')) {
return;
}
// Add separator
const separator = document.createElement('div');
separator.className = 'app-grid-separator';
separator.innerHTML = '<span>Desktop Features</span>';
grid.appendChild(separator);
// Add app-only guides
APP_GUIDES.forEach(guide => {
const item = document.createElement('a');
item.className = 'app-item app-only';
item.href = `#${guide.id}`;
item.dataset.section = guide.id;
item.setAttribute('role', 'menuitem');
item.setAttribute('aria-label', guide.description || guide.label);
item.setAttribute('hx-get', guide.hxGet);
item.setAttribute('hx-target', '#main-content');
item.setAttribute('hx-push-url', 'true');
item.innerHTML = `
<div class="app-icon" aria-hidden="true">${guide.icon}</div>
<span>${guide.label}</span>
`;
grid.appendChild(item);
});
// Re-process HTMX on new elements
if (window.htmx) {
htmx.process(grid);
}
console.log('[BotApp] App guides injected successfully');
}
/**
* Setup Tauri event listeners
*/
function setupTauriEvents() {
if (!window.__TAURI__) {
console.warn('[BotApp] Tauri API not available');
return;
}
const { listen } = window.__TAURI__.event;
// Listen for upload progress events
listen('upload_progress', (event) => {
const progress = event.payload;
const progressEl = document.getElementById('upload-progress');
if (progressEl) {
progressEl.style.width = `${progress}%`;
progressEl.textContent = `${Math.round(progress)}%`;
}
});
console.log('[BotApp] Tauri event listeners registered');
}
/**
* Initialize BotApp extensions
*/
function init() {
console.log('[BotApp] Initializing app extensions...');
// Inject styles
injectStyles();
// Inject app guides
injectAppGuides();
// Setup Tauri events
setupTauriEvents();
console.log('[BotApp] App extensions initialized');
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Expose BotApp API globally
window.BotApp = {
isApp: true,
version: '6.1.0',
guides: APP_GUIDES,
// Invoke Tauri commands
invoke: async function(cmd, args) {
if (!window.__TAURI__) {
throw new Error('Tauri API not available');
}
return window.__TAURI__.core.invoke(cmd, args);
},
// File system helpers
fs: {
listFiles: (path) => window.BotApp.invoke('list_files', { path }),
uploadFile: (srcPath, destPath) => window.BotApp.invoke('upload_file', { srcPath, destPath }),
createFolder: (path, name) => window.BotApp.invoke('create_folder', { path, name }),
deletePath: (path) => window.BotApp.invoke('delete_path', { path }),
getHomeDir: () => window.BotApp.invoke('get_home_dir'),
},
// Show native notification
notify: async function(title, body) {
if (window.__TAURI__?.notification) {
await window.__TAURI__.notification.sendNotification({ title, body });
}
},
// Open native file dialog
openFileDialog: async function(options = {}) {
if (!window.__TAURI__?.dialog) {
throw new Error('Dialog API not available');
}
return window.__TAURI__.dialog.open(options);
},
// Open native save dialog
saveFileDialog: async function(options = {}) {
if (!window.__TAURI__?.dialog) {
throw new Error('Dialog API not available');
}
return window.__TAURI__.dialog.save(options);
}
};
})();