refactor: add edit dataset button and enhance dataset form handling with schema warnings

This commit is contained in:
2025-10-19 19:27:22 +03:00
parent 65be336812
commit 011fac5a47
5 changed files with 338 additions and 68 deletions

View File

@@ -67,3 +67,4 @@ When implementing changes:
- Pay attention to the existing code base style and approaches and try to adhere to the existing style instead of bringing your own vision. - Pay attention to the existing code base style and approaches and try to adhere to the existing style instead of bringing your own vision.
- When updating documentation, do not record intermediate changes - write them always as a matter-of-fact information. - When updating documentation, do not record intermediate changes - write them always as a matter-of-fact information.
- When working on the code, if you notice any opportunities to better bring the project to the state above - bring this to user's attention and ask for approval to implement the suggested changes. - When working on the code, if you notice any opportunities to better bring the project to the state above - bring this to user's attention and ask for approval to implement the suggested changes.
- Testing: The user always tests changes manually. Do not start local servers or attempt to run the application.

View File

@@ -200,6 +200,7 @@
<div class="dataset-details" id="dataset-details" style="display: none;"> <div class="dataset-details" id="dataset-details" style="display: none;">
<div class="dataset-detail-section"> <div class="dataset-detail-section">
<div class="dataset-actions"> <div class="dataset-actions">
<button class="btn btn-modal primary" id="edit-dataset-btn" title="Edit this dataset">Edit</button>
<button class="btn btn-modal primary" id="new-snippet-btn" title="Create a new snippet using this dataset">New Snippet</button> <button class="btn btn-modal primary" id="new-snippet-btn" title="Create a new snippet using this dataset">New Snippet</button>
<button class="btn btn-modal" id="export-dataset-btn" title="Export this dataset to file">Export</button> <button class="btn btn-modal" id="export-dataset-btn" title="Export this dataset to file">Export</button>
<button class="btn btn-modal" id="copy-reference-btn" title="Copy dataset reference to clipboard">Copy Reference</button> <button class="btn btn-modal" id="copy-reference-btn" title="Copy dataset reference to clipboard">Copy Reference</button>

View File

@@ -216,6 +216,7 @@ document.addEventListener('DOMContentLoaded', function () {
const datasetsLink = document.getElementById('datasets-link'); const datasetsLink = document.getElementById('datasets-link');
const toggleDatasetsBtn = document.getElementById('toggle-datasets'); const toggleDatasetsBtn = document.getElementById('toggle-datasets');
const newDatasetBtn = document.getElementById('new-dataset-btn'); const newDatasetBtn = document.getElementById('new-dataset-btn');
const editDatasetBtn = document.getElementById('edit-dataset-btn');
const cancelDatasetBtn = document.getElementById('cancel-dataset-btn'); const cancelDatasetBtn = document.getElementById('cancel-dataset-btn');
const saveDatasetBtn = document.getElementById('save-dataset-btn'); const saveDatasetBtn = document.getElementById('save-dataset-btn');
const deleteDatasetBtn = document.getElementById('delete-dataset-btn'); const deleteDatasetBtn = document.getElementById('delete-dataset-btn');
@@ -234,6 +235,15 @@ document.addEventListener('DOMContentLoaded', function () {
newDatasetBtn.addEventListener('click', showNewDatasetForm); newDatasetBtn.addEventListener('click', showNewDatasetForm);
} }
// Edit dataset button
if (editDatasetBtn) {
editDatasetBtn.addEventListener('click', async function() {
if (window.currentDatasetId) {
await showEditDatasetForm(window.currentDatasetId);
}
});
}
// Import dataset button and file input // Import dataset button and file input
const importDatasetBtn = document.getElementById('import-dataset-btn'); const importDatasetBtn = document.getElementById('import-dataset-btn');
const importDatasetFile = document.getElementById('import-dataset-file'); const importDatasetFile = document.getElementById('import-dataset-file');
@@ -375,11 +385,19 @@ function handleURLStateChange() {
if (state.datasetId === 'new') { if (state.datasetId === 'new') {
// Show new dataset form // Show new dataset form
showNewDatasetForm(false); showNewDatasetForm(false);
} else if (state.datasetId && state.datasetId.startsWith('edit-')) {
// Show edit dataset form - extract numeric ID from "edit-123456"
const numericId = parseFloat(state.datasetId.replace('edit-', ''));
if (!isNaN(numericId)) {
showEditDatasetForm(numericId, false);
}
} else if (state.datasetId) { } else if (state.datasetId) {
// Extract numeric ID from "dataset-123456" // Extract numeric ID from "dataset-123456"
const numericId = parseFloat(state.datasetId.replace('dataset-', '')); const numericId = parseFloat(state.datasetId.replace('dataset-', ''));
if (!isNaN(numericId)) {
selectDataset(numericId, false); selectDataset(numericId, false);
} }
}
} else if (state.snippetId) { } else if (state.snippetId) {
// Close dataset modal if open // Close dataset modal if open
const modal = document.getElementById('dataset-modal'); const modal = document.getElementById('dataset-modal');

View File

@@ -997,10 +997,31 @@ function showDetectionConfirmation(detection, originalInput) {
detectedConfidenceEl.className = `detected-confidence ${confidenceClass}`; detectedConfidenceEl.className = `detected-confidence ${confidenceClass}`;
detectedConfidenceEl.textContent = `${detection.confidence} confidence`; detectedConfidenceEl.textContent = `${detection.confidence} confidence`;
// Show preview // Calculate metadata for the detected data
let metadata = null;
if (detection.source === 'url' && detection.content) {
metadata = calculateDatasetStats(
detection.parsed || detection.content,
detection.format,
'inline'
);
} else if (detection.source === 'inline') {
metadata = calculateDatasetStats(
detection.parsed || originalInput,
detection.format,
'inline'
);
}
// Show preview with metadata
let previewText = ''; let previewText = '';
if (detection.source === 'url') { if (detection.source === 'url') {
previewText = `URL: ${originalInput}\n\n`; previewText = `URL: ${originalInput}\n\n`;
if (metadata && metadata.columns && metadata.columns.length > 0) {
previewText += `Columns (${metadata.columnCount}): ${metadata.columns.join(', ')}\n`;
previewText += `Rows: ${metadata.rowCount}\n\n`;
}
if (detection.content) { if (detection.content) {
const lines = detection.content.split('\n'); const lines = detection.content.split('\n');
previewText += `Preview (first 10 lines):\n${lines.slice(0, 10).join('\n')}`; previewText += `Preview (first 10 lines):\n${lines.slice(0, 10).join('\n')}`;
@@ -1009,8 +1030,12 @@ function showDetectionConfirmation(detection, originalInput) {
} }
} }
} else { } else {
if (metadata && metadata.columns && metadata.columns.length > 0) {
previewText = `Columns (${metadata.columnCount}): ${metadata.columns.join(', ')}\n`;
previewText += `Rows: ${metadata.rowCount}\n\n`;
}
const lines = originalInput.split('\n'); const lines = originalInput.split('\n');
previewText = lines.slice(0, 15).join('\n'); previewText += lines.slice(0, 15).join('\n');
if (lines.length > 15) { if (lines.length > 15) {
previewText += `\n... (${lines.length - 15} more lines)`; previewText += `\n... (${lines.length - 15} more lines)`;
} }
@@ -1020,7 +1045,8 @@ function showDetectionConfirmation(detection, originalInput) {
// Store detection data for later use // Store detection data for later use
window.currentDetection = { window.currentDetection = {
...detection, ...detection,
originalInput originalInput,
metadata
}; };
} }
@@ -1031,32 +1057,21 @@ function hideDetectionConfirmation() {
window.currentDetection = null; window.currentDetection = null;
} }
// Show new dataset form // Setup input handler for dataset form (handles both create and edit)
function showNewDatasetForm(updateURL = true) { function setupDatasetInputHandler() {
document.getElementById('dataset-list-view').style.display = 'none';
document.getElementById('dataset-form-view').style.display = 'block';
document.getElementById('dataset-form-name').value = '';
document.getElementById('dataset-form-input').value = '';
document.getElementById('dataset-form-comment').value = '';
document.getElementById('dataset-form-error').textContent = '';
// Hide detection confirmation
hideDetectionConfirmation();
// Update URL state
if (updateURL) {
URLState.update({ view: 'datasets', snippetId: null, datasetId: 'new' });
}
// Add paste handler if not already added
if (!window.datasetListenersAdded) {
const inputEl = document.getElementById('dataset-form-input'); const inputEl = document.getElementById('dataset-form-input');
// Handle paste/input with auto-detection // Remove existing listener if any
inputEl.addEventListener('input', async function () { if (inputEl._datasetInputHandler) {
inputEl.removeEventListener('input', inputEl._datasetInputHandler);
}
// Create new handler
const handler = async function () {
const text = this.value.trim(); const text = this.value.trim();
if (!text) { if (!text) {
hideDetectionConfirmation(); hideDetectionConfirmation();
hideSchemaWarning();
return; return;
} }
@@ -1073,13 +1088,16 @@ function showNewDatasetForm(updateURL = true) {
if (detection.format) { if (detection.format) {
showDetectionConfirmation(detection, text); showDetectionConfirmation(detection, text);
checkSchemaChanges();
} else { } else {
errorEl.textContent = 'Could not detect data format from URL. Please check the URL or try pasting the data directly.'; errorEl.textContent = 'Could not detect data format from URL. Please check the URL or try pasting the data directly.';
hideDetectionConfirmation(); hideDetectionConfirmation();
hideSchemaWarning();
} }
} catch (error) { } catch (error) {
errorEl.textContent = error.message; errorEl.textContent = error.message;
hideDetectionConfirmation(); hideDetectionConfirmation();
hideSchemaWarning();
} }
} else { } else {
// Inline data - detect format // Inline data - detect format
@@ -1090,14 +1108,200 @@ function showNewDatasetForm(updateURL = true) {
...detection, ...detection,
source: 'inline' source: 'inline'
}, text); }, text);
checkSchemaChanges();
} else { } else {
errorEl.textContent = 'Could not detect data format. Please ensure your data is valid JSON, CSV, or TSV.'; errorEl.textContent = 'Could not detect data format. Please ensure your data is valid JSON, CSV, or TSV.';
hideDetectionConfirmation(); hideDetectionConfirmation();
hideSchemaWarning();
} }
} }
}); };
window.datasetListenersAdded = true; // Store reference to handler for cleanup
inputEl._datasetInputHandler = handler;
// Attach listener
inputEl.addEventListener('input', handler);
}
// Show new dataset form
function showNewDatasetForm(updateURL = true) {
// Set mode to create
window.datasetFormMode = 'create';
window.editingDatasetId = null;
window.originalSchema = null;
document.getElementById('dataset-list-view').style.display = 'none';
document.getElementById('dataset-form-view').style.display = 'block';
document.getElementById('dataset-form-name').value = '';
document.getElementById('dataset-form-input').value = '';
document.getElementById('dataset-form-comment').value = '';
document.getElementById('dataset-form-error').textContent = '';
// Update form header
document.querySelector('.dataset-form-header').textContent = 'Create New Dataset';
// Hide schema warning
hideSchemaWarning();
// Hide detection confirmation
hideDetectionConfirmation();
// Update URL state
if (updateURL) {
URLState.update({ view: 'datasets', snippetId: null, datasetId: 'new' });
}
// Setup input handler for detection
setupDatasetInputHandler();
}
// Show edit dataset form
async function showEditDatasetForm(datasetId, updateURL = true) {
const dataset = await DatasetStorage.getDataset(datasetId);
if (!dataset) return;
// Set mode to edit
window.datasetFormMode = 'edit';
window.editingDatasetId = datasetId;
// Store original schema for comparison
window.originalSchema = dataset.columns ? [...dataset.columns] : [];
document.getElementById('dataset-list-view').style.display = 'none';
document.getElementById('dataset-form-view').style.display = 'block';
// Populate form with current values
document.getElementById('dataset-form-name').value = dataset.name;
document.getElementById('dataset-form-comment').value = dataset.comment || '';
// Populate data input based on source
let inputValue;
if (dataset.source === 'url') {
inputValue = dataset.data; // The URL string
} else if (dataset.format === 'json' || dataset.format === 'topojson') {
inputValue = JSON.stringify(dataset.data, null, 2);
} else if (dataset.format === 'csv' || dataset.format === 'tsv') {
inputValue = dataset.data;
}
document.getElementById('dataset-form-input').value = inputValue;
// Trigger detection to show current state
const inputEl = document.getElementById('dataset-form-input');
// Use setTimeout to ensure the value is set before triggering
setTimeout(() => {
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
}, 0);
document.getElementById('dataset-form-error').textContent = '';
// Update form header
document.querySelector('.dataset-form-header').textContent = 'Edit Dataset';
// Hide schema warning initially
hideSchemaWarning();
// Update URL state
if (updateURL) {
URLState.update({ view: 'datasets', snippetId: null, datasetId: `edit-${datasetId}` });
}
// Setup input handler for detection
setupDatasetInputHandler();
}
// Hide schema warning
function hideSchemaWarning() {
const warningEl = document.getElementById('dataset-schema-warning');
if (warningEl) {
warningEl.style.display = 'none';
}
}
// Show schema warning with details
function showSchemaWarning(oldSchema, newSchema) {
let warningEl = document.getElementById('dataset-schema-warning');
// Create warning element if it doesn't exist
if (!warningEl) {
const confirmEl = document.getElementById('dataset-detection-confirm');
warningEl = document.createElement('div');
warningEl.id = 'dataset-schema-warning';
warningEl.className = 'dataset-schema-warning';
confirmEl.parentNode.insertBefore(warningEl, confirmEl.nextSibling);
}
// Determine what changed
const added = newSchema.filter(col => !oldSchema.includes(col));
const removed = oldSchema.filter(col => !newSchema.includes(col));
let message = '⚠️ Schema Change Detected<br/>';
if (removed.length > 0) {
message += `<strong>Removed columns:</strong> ${removed.join(', ')}<br/>`;
}
if (added.length > 0) {
message += `<strong>Added columns:</strong> ${added.join(', ')}<br/>`;
}
message += '<em>Saving will update the dataset schema. Linked snippets may be affected.</em>';
warningEl.innerHTML = message;
warningEl.style.display = 'block';
}
// Check for schema changes
function checkSchemaChanges() {
// Only check in edit mode
if (window.datasetFormMode !== 'edit' || !window.originalSchema) {
return;
}
// Get current detection
const detection = window.currentDetection;
if (!detection || !detection.format) {
hideSchemaWarning();
return;
}
// Extract new schema from detected data
let newSchema = [];
if (detection.source === 'url' && detection.content) {
// Parse content to get schema
const tempStats = calculateDatasetStats(
detection.parsed || detection.content,
detection.format,
'inline'
);
newSchema = tempStats.columns || [];
} else if (detection.source === 'inline') {
const tempStats = calculateDatasetStats(
detection.parsed || detection.originalInput,
detection.format,
'inline'
);
newSchema = tempStats.columns || [];
}
// Compare schemas
const originalSchema = window.originalSchema || [];
if (newSchema.length === 0 && originalSchema.length === 0) {
hideSchemaWarning();
return;
}
// Check if schemas are different
const schemaChanged =
originalSchema.length !== newSchema.length ||
!originalSchema.every(col => newSchema.includes(col)) ||
!newSchema.every(col => originalSchema.includes(col));
if (schemaChanged) {
showSchemaWarning(originalSchema, newSchema);
} else {
hideSchemaWarning();
} }
} }
@@ -1105,9 +1309,13 @@ function showNewDatasetForm(updateURL = true) {
function hideNewDatasetForm() { function hideNewDatasetForm() {
document.getElementById('dataset-list-view').style.display = 'block'; document.getElementById('dataset-list-view').style.display = 'block';
document.getElementById('dataset-form-view').style.display = 'none'; document.getElementById('dataset-form-view').style.display = 'none';
window.datasetFormMode = null;
window.editingDatasetId = null;
window.originalSchema = null;
hideSchemaWarning();
} }
// Save new dataset // Save new dataset (handles both create and edit modes)
async function saveNewDataset() { async function saveNewDataset() {
const name = document.getElementById('dataset-form-name').value.trim(); const name = document.getElementById('dataset-form-name').value.trim();
const comment = document.getElementById('dataset-form-comment').value.trim(); const comment = document.getElementById('dataset-form-comment').value.trim();
@@ -1173,14 +1381,49 @@ async function saveNewDataset() {
} }
} }
try {
if (window.datasetFormMode === 'edit') {
// Edit mode - update existing dataset
const datasetId = window.editingDatasetId;
// Check if name already exists (excluding current dataset)
if (await DatasetStorage.nameExists(name, datasetId)) {
errorEl.textContent = 'A dataset with this name already exists';
return;
}
// Update dataset
const updates = {
name,
data,
format,
source,
comment
};
// Add metadata if available (for URL sources)
if (metadata) {
Object.assign(updates, metadata);
}
await DatasetStorage.updateDataset(datasetId, updates);
hideNewDatasetForm();
await renderDatasetList();
await selectDataset(datasetId);
Toast.success('Dataset updated successfully');
// Track event
Analytics.track('dataset-update', `Update dataset (${source})`);
} else {
// Create mode - create new dataset
// Check if name already exists // Check if name already exists
if (await DatasetStorage.nameExists(name)) { if (await DatasetStorage.nameExists(name)) {
errorEl.textContent = 'A dataset with this name already exists'; errorEl.textContent = 'A dataset with this name already exists';
return; return;
} }
// Create dataset
try {
const dataset = await DatasetStorage.createDataset(name, data, format, source, comment); const dataset = await DatasetStorage.createDataset(name, data, format, source, comment);
// If we have metadata from URL fetch, update the dataset // If we have metadata from URL fetch, update the dataset
@@ -1196,6 +1439,7 @@ async function saveNewDataset() {
// Track event // Track event
Analytics.track('dataset-create', `Create dataset (${source})`); Analytics.track('dataset-create', `Create dataset (${source})`);
}
} catch (error) { } catch (error) {
errorEl.textContent = `Failed to save dataset: ${error.message}`; errorEl.textContent = `Failed to save dataset: ${error.message}`;
} }

View File

@@ -374,6 +374,12 @@ body { font-family: var(--font-main); height: 100vh; overflow: hidden; backgroun
.detection-badges { display: flex; gap: 6px; align-items: center; } .detection-badges { display: flex; gap: 6px; align-items: center; }
.detection-badge { background: var(--win-blue); color: var(--bg-white); padding: 2px 8px; font-size: 10px; font-weight: bold; border: 1px solid var(--win-blue-dark); border-radius: 2px; } .detection-badge { background: var(--win-blue); color: var(--bg-white); padding: 2px 8px; font-size: 10px; font-weight: bold; border: 1px solid var(--win-blue-dark); border-radius: 2px; }
.detected-confidence { font-size: 9px; padding: 2px 6px; border-radius: 2px; } .detected-confidence { font-size: 9px; padding: 2px 6px; border-radius: 2px; }
/* Schema Warning */
.dataset-schema-warning { margin: 12px 0; padding: 12px; background: #fff8dc; border: 2px solid #ffa500; border-radius: 4px; font-size: 11px; line-height: 1.6; color: #000; }
:root[data-theme="experimental"] .dataset-schema-warning { background: #3a3020; border-color: #cc8800; color: #ffd966; }
.dataset-schema-warning strong { font-weight: 600; }
.dataset-schema-warning em { font-style: italic; opacity: 0.9; }
.detected-confidence.high { background: #90ee90; border: 1px solid #60c060; } .detected-confidence.high { background: #90ee90; border: 1px solid #60c060; }
:root[data-theme="experimental"] .detected-confidence.high { background: #3a6a3a; color: #88ff88; border-color: #66dd66; } :root[data-theme="experimental"] .detected-confidence.high { background: #3a6a3a; color: #88ff88; border-color: #66dd66; }
.detected-confidence.medium { background: #ffff90; border: 1px solid #d0d060; } .detected-confidence.medium { background: #ffff90; border: 1px solid #d0d060; }