/** * GoingsOn - Import Module * Handles importing data from files using plugins. */ (function() { 'use strict'; const esc = GoingsOn.utils.escapeHtml; const escAttr = GoingsOn.utils.escapeAttr; // ============ State ============ let availablePlugins = []; let selectedPlugin = null; let selectedFilePath = null; let previewData = null; // ============ Import Wizard ============ /** * Opens the import wizard modal. */ async function openImportModal() { // Reset state selectedPlugin = null; selectedFilePath = null; previewData = null; // Load available plugins try { availablePlugins = await GoingsOn.api.plugins.listEnabled(); } catch (err) { GoingsOn.ui.showToast('Failed to load import plugins: ' + GoingsOn.utils.getErrorMessage(err), 'error'); return; } if (availablePlugins.length === 0) { GoingsOn.ui.showToast('No import plugins available. Enable plugins in Settings.', 'info'); return; } const content = `

1. Select Import Source

${availablePlugins.map(p => renderPluginOption(p)).join('')}
`; GoingsOn.ui.openModal('Import Data', content); } /** * Render a plugin option button for the import wizard. * @param {Object} plugin - Plugin object with id, name, description, plugin_type * @returns {string} HTML string for the plugin option */ function renderPluginOption(plugin) { const config = plugin.plugin_type?.import || {}; const extensions = config.file_extensions || []; const entityTypes = config.entity_types || []; return ` `; } /** * Select a plugin in the import wizard and advance to file selection. * @param {string} pluginId - Plugin ID to select */ function selectPlugin(pluginId) { selectedPlugin = availablePlugins.find(p => p.id === pluginId); if (!selectedPlugin) return; // Update UI - highlight selected plugin document.querySelectorAll('.plugin-option').forEach(el => { el.classList.remove('selected'); }); document.querySelector(`.plugin-option[data-plugin-id="${pluginId}"]`)?.classList.add('selected'); // Show step 2 document.getElementById('import-step-2').classList.remove('hidden'); // Reset file selection selectedFilePath = null; previewData = null; document.getElementById('selected-file-name').textContent = ''; document.getElementById('import-step-3').classList.add('hidden'); document.getElementById('import-confirm-btn').disabled = true; } /** * Opens the file dialog to select a file. */ async function selectFile() { if (!selectedPlugin) return; const config = selectedPlugin.plugin_type?.import || {}; const extensions = config.file_extensions || ['*']; try { // Use Tauri's file dialog const { open } = window.__TAURI__.dialog; const filePath = await open({ multiple: false, filters: [{ name: selectedPlugin.name, extensions: extensions, }], }); if (!filePath) return; // User cancelled selectedFilePath = filePath; document.getElementById('selected-file-name').textContent = getFileName(filePath); // Show step 3 and load preview document.getElementById('import-step-3').classList.remove('hidden'); await loadPreview(); } catch (err) { GoingsOn.ui.showToast('Failed to select file: ' + GoingsOn.utils.getErrorMessage(err), 'error'); } } /** * Gets the filename from a full path. */ function getFileName(path) { return path.split(/[/\\]/).pop() || path; } /** * Loads and displays the preview of the import data. */ async function loadPreview() { const container = document.getElementById('import-preview-container'); container.innerHTML = '
Parsing file...
'; try { previewData = await GoingsOn.api.plugins.preview(selectedPlugin.id, selectedFilePath, {}); if (!previewData.items || previewData.items.length === 0) { container.innerHTML = '

No items found in file.

'; document.getElementById('import-confirm-btn').disabled = true; return; } container.innerHTML = renderPreviewTable(previewData); document.getElementById('import-confirm-btn').disabled = false; // Show warnings if any if (previewData.warnings && previewData.warnings.length > 0) { const warningsHtml = `
Warnings:
`; container.insertAdjacentHTML('beforeend', warningsHtml); } } catch (err) { container.innerHTML = `

Failed to parse file: ${esc(GoingsOn.utils.getErrorMessage(err))}

`; document.getElementById('import-confirm-btn').disabled = true; } } /** * Renders the preview table. */ function renderPreviewTable(data) { const entityType = data.entity_type || 'item'; const items = data.items || []; const maxPreview = 25; const displayItems = items.slice(0, maxPreview); // Determine columns based on entity type const columns = getColumnsForEntityType(entityType); return `

${items.length} ${entityType}${items.length !== 1 ? 's' : ''} found

${columns.map(c => ``).join('')} ${displayItems.map((item, idx) => renderPreviewRow(item, columns, idx)).join('')}
${c.label}
${items.length > maxPreview ? `

...and ${items.length - maxPreview} more

` : ''} `; } /** * Gets the columns to display based on entity type. */ function getColumnsForEntityType(entityType) { switch (entityType) { case 'task': return [ { key: 'description', label: 'Description' }, { key: 'project_name', label: 'Project' }, { key: 'priority', label: 'Priority' }, { key: 'due', label: 'Due' }, ]; case 'project': return [ { key: 'name', label: 'Name' }, { key: 'description', label: 'Description' }, { key: 'project_type', label: 'Type' }, { key: 'status', label: 'Status' }, ]; case 'event': return [ { key: 'title', label: 'Title' }, { key: 'start', label: 'Start' }, { key: 'end', label: 'End' }, { key: 'location', label: 'Location' }, ]; default: return [ { key: 'description', label: 'Description' }, ]; } } /** * Renders a preview table row. */ function renderPreviewRow(item, columns, index) { const data = item.data || {}; return ` ${columns.map(c => { const value = data[c.key] || ''; return `${esc(truncate(value, 50))}`; }).join('')} `; } /** * Truncates a string to a maximum length. */ function truncate(str, maxLen) { if (!str) return ''; str = String(str); return str.length > maxLen ? str.substring(0, maxLen) + '...' : str; } /** * Executes the import. */ async function executeImport() { if (!selectedPlugin || !selectedFilePath || !previewData) return; const btn = document.getElementById('import-confirm-btn'); btn.disabled = true; btn.textContent = 'Importing...'; try { const result = await GoingsOn.api.plugins.execute(selectedPlugin.id, selectedFilePath, {}); GoingsOn.ui.closeModal(); const entityType = previewData.entity_type || 'item'; if (result.failed_count > 0) { GoingsOn.ui.showToast(`Imported ${result.imported_count} ${entityType}(s), ${result.failed_count} failed`, 'warning'); } else { GoingsOn.ui.showToast(`Successfully imported ${result.imported_count} ${entityType}(s)!`, 'success'); } // Refresh the current view if (typeof GoingsOn.navigation !== 'undefined' && GoingsOn.navigation.reloadCurrentView) { GoingsOn.navigation.reloadCurrentView(); } } catch (err) { GoingsOn.ui.showToast('Import failed: ' + GoingsOn.utils.getErrorMessage(err), 'error'); btn.disabled = false; btn.textContent = 'Import'; } } // ============ Populate Namespace ============ GoingsOn.import = { openModal: openImportModal, selectPlugin, selectFile, executeImport, }; })();