/**
* 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('')}
2. Select File
`;
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:
${previewData.warnings.map(w => `- ${esc(w)}
`).join('')}
`;
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 => `| ${c.label} | `).join('')}
${displayItems.map((item, idx) => renderPreviewRow(item, columns, idx)).join('')}
${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,
};
})();