mirror of
https://github.com/olehomelchenko/astrolabe-nvc.git
synced 2025-12-21 13:12:23 +00:00
refactor: add edit dataset button and enhance dataset form handling with schema warnings
This commit is contained in:
@@ -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.
|
||||
- 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.
|
||||
- Testing: The user always tests changes manually. Do not start local servers or attempt to run the application.
|
||||
|
||||
@@ -200,6 +200,7 @@
|
||||
<div class="dataset-details" id="dataset-details" style="display: none;">
|
||||
<div class="dataset-detail-section">
|
||||
<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" 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>
|
||||
|
||||
@@ -216,6 +216,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const datasetsLink = document.getElementById('datasets-link');
|
||||
const toggleDatasetsBtn = document.getElementById('toggle-datasets');
|
||||
const newDatasetBtn = document.getElementById('new-dataset-btn');
|
||||
const editDatasetBtn = document.getElementById('edit-dataset-btn');
|
||||
const cancelDatasetBtn = document.getElementById('cancel-dataset-btn');
|
||||
const saveDatasetBtn = document.getElementById('save-dataset-btn');
|
||||
const deleteDatasetBtn = document.getElementById('delete-dataset-btn');
|
||||
@@ -234,6 +235,15 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
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
|
||||
const importDatasetBtn = document.getElementById('import-dataset-btn');
|
||||
const importDatasetFile = document.getElementById('import-dataset-file');
|
||||
@@ -375,10 +385,18 @@ function handleURLStateChange() {
|
||||
if (state.datasetId === 'new') {
|
||||
// Show new dataset form
|
||||
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) {
|
||||
// Extract numeric ID from "dataset-123456"
|
||||
const numericId = parseFloat(state.datasetId.replace('dataset-', ''));
|
||||
selectDataset(numericId, false);
|
||||
if (!isNaN(numericId)) {
|
||||
selectDataset(numericId, false);
|
||||
}
|
||||
}
|
||||
} else if (state.snippetId) {
|
||||
// Close dataset modal if open
|
||||
|
||||
@@ -997,10 +997,31 @@ function showDetectionConfirmation(detection, originalInput) {
|
||||
detectedConfidenceEl.className = `detected-confidence ${confidenceClass}`;
|
||||
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 = '';
|
||||
|
||||
if (detection.source === 'url') {
|
||||
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) {
|
||||
const lines = detection.content.split('\n');
|
||||
previewText += `Preview (first 10 lines):\n${lines.slice(0, 10).join('\n')}`;
|
||||
@@ -1009,8 +1030,12 @@ function showDetectionConfirmation(detection, originalInput) {
|
||||
}
|
||||
}
|
||||
} 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');
|
||||
previewText = lines.slice(0, 15).join('\n');
|
||||
previewText += lines.slice(0, 15).join('\n');
|
||||
if (lines.length > 15) {
|
||||
previewText += `\n... (${lines.length - 15} more lines)`;
|
||||
}
|
||||
@@ -1020,7 +1045,8 @@ function showDetectionConfirmation(detection, originalInput) {
|
||||
// Store detection data for later use
|
||||
window.currentDetection = {
|
||||
...detection,
|
||||
originalInput
|
||||
originalInput,
|
||||
metadata
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1031,8 +1057,80 @@ function hideDetectionConfirmation() {
|
||||
window.currentDetection = null;
|
||||
}
|
||||
|
||||
// Setup input handler for dataset form (handles both create and edit)
|
||||
function setupDatasetInputHandler() {
|
||||
const inputEl = document.getElementById('dataset-form-input');
|
||||
|
||||
// Remove existing listener if any
|
||||
if (inputEl._datasetInputHandler) {
|
||||
inputEl.removeEventListener('input', inputEl._datasetInputHandler);
|
||||
}
|
||||
|
||||
// Create new handler
|
||||
const handler = async function () {
|
||||
const text = this.value.trim();
|
||||
if (!text) {
|
||||
hideDetectionConfirmation();
|
||||
hideSchemaWarning();
|
||||
return;
|
||||
}
|
||||
|
||||
const errorEl = document.getElementById('dataset-form-error');
|
||||
errorEl.textContent = '';
|
||||
|
||||
// Check if it's a URL
|
||||
if (isURL(text)) {
|
||||
errorEl.textContent = 'Fetching and analyzing URL...';
|
||||
|
||||
try {
|
||||
const detection = await fetchAndDetectURL(text);
|
||||
errorEl.textContent = '';
|
||||
|
||||
if (detection.format) {
|
||||
showDetectionConfirmation(detection, text);
|
||||
checkSchemaChanges();
|
||||
} else {
|
||||
errorEl.textContent = 'Could not detect data format from URL. Please check the URL or try pasting the data directly.';
|
||||
hideDetectionConfirmation();
|
||||
hideSchemaWarning();
|
||||
}
|
||||
} catch (error) {
|
||||
errorEl.textContent = error.message;
|
||||
hideDetectionConfirmation();
|
||||
hideSchemaWarning();
|
||||
}
|
||||
} else {
|
||||
// Inline data - detect format
|
||||
const detection = detectDataFormat(text);
|
||||
|
||||
if (detection.format) {
|
||||
showDetectionConfirmation({
|
||||
...detection,
|
||||
source: 'inline'
|
||||
}, text);
|
||||
checkSchemaChanges();
|
||||
} else {
|
||||
errorEl.textContent = 'Could not detect data format. Please ensure your data is valid JSON, CSV, or TSV.';
|
||||
hideDetectionConfirmation();
|
||||
hideSchemaWarning();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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 = '';
|
||||
@@ -1040,6 +1138,12 @@ function showNewDatasetForm(updateURL = true) {
|
||||
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();
|
||||
|
||||
@@ -1048,56 +1152,156 @@ function showNewDatasetForm(updateURL = true) {
|
||||
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');
|
||||
// Setup input handler for detection
|
||||
setupDatasetInputHandler();
|
||||
}
|
||||
|
||||
// Handle paste/input with auto-detection
|
||||
inputEl.addEventListener('input', async function () {
|
||||
const text = this.value.trim();
|
||||
if (!text) {
|
||||
hideDetectionConfirmation();
|
||||
return;
|
||||
}
|
||||
// Show edit dataset form
|
||||
async function showEditDatasetForm(datasetId, updateURL = true) {
|
||||
const dataset = await DatasetStorage.getDataset(datasetId);
|
||||
if (!dataset) return;
|
||||
|
||||
const errorEl = document.getElementById('dataset-form-error');
|
||||
errorEl.textContent = '';
|
||||
// Set mode to edit
|
||||
window.datasetFormMode = 'edit';
|
||||
window.editingDatasetId = datasetId;
|
||||
|
||||
// Check if it's a URL
|
||||
if (isURL(text)) {
|
||||
errorEl.textContent = 'Fetching and analyzing URL...';
|
||||
// Store original schema for comparison
|
||||
window.originalSchema = dataset.columns ? [...dataset.columns] : [];
|
||||
|
||||
try {
|
||||
const detection = await fetchAndDetectURL(text);
|
||||
errorEl.textContent = '';
|
||||
document.getElementById('dataset-list-view').style.display = 'none';
|
||||
document.getElementById('dataset-form-view').style.display = 'block';
|
||||
|
||||
if (detection.format) {
|
||||
showDetectionConfirmation(detection, text);
|
||||
} else {
|
||||
errorEl.textContent = 'Could not detect data format from URL. Please check the URL or try pasting the data directly.';
|
||||
hideDetectionConfirmation();
|
||||
}
|
||||
} catch (error) {
|
||||
errorEl.textContent = error.message;
|
||||
hideDetectionConfirmation();
|
||||
}
|
||||
} else {
|
||||
// Inline data - detect format
|
||||
const detection = detectDataFormat(text);
|
||||
// Populate form with current values
|
||||
document.getElementById('dataset-form-name').value = dataset.name;
|
||||
document.getElementById('dataset-form-comment').value = dataset.comment || '';
|
||||
|
||||
if (detection.format) {
|
||||
showDetectionConfirmation({
|
||||
...detection,
|
||||
source: 'inline'
|
||||
}, text);
|
||||
} else {
|
||||
errorEl.textContent = 'Could not detect data format. Please ensure your data is valid JSON, CSV, or TSV.';
|
||||
hideDetectionConfirmation();
|
||||
}
|
||||
}
|
||||
});
|
||||
// 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;
|
||||
|
||||
window.datasetListenersAdded = true;
|
||||
// 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() {
|
||||
document.getElementById('dataset-list-view').style.display = 'block';
|
||||
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() {
|
||||
const name = document.getElementById('dataset-form-name').value.trim();
|
||||
const comment = document.getElementById('dataset-form-comment').value.trim();
|
||||
@@ -1173,29 +1381,65 @@ async function saveNewDataset() {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if name already exists
|
||||
if (await DatasetStorage.nameExists(name)) {
|
||||
errorEl.textContent = 'A dataset with this name already exists';
|
||||
return;
|
||||
}
|
||||
|
||||
// Create dataset
|
||||
try {
|
||||
const dataset = await DatasetStorage.createDataset(name, data, format, source, comment);
|
||||
if (window.datasetFormMode === 'edit') {
|
||||
// Edit mode - update existing dataset
|
||||
const datasetId = window.editingDatasetId;
|
||||
|
||||
// If we have metadata from URL fetch, update the dataset
|
||||
if (metadata) {
|
||||
await DatasetStorage.updateDataset(dataset.id, {
|
||||
data: data,
|
||||
...metadata
|
||||
});
|
||||
// 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
|
||||
if (await DatasetStorage.nameExists(name)) {
|
||||
errorEl.textContent = 'A dataset with this name already exists';
|
||||
return;
|
||||
}
|
||||
|
||||
const dataset = await DatasetStorage.createDataset(name, data, format, source, comment);
|
||||
|
||||
// If we have metadata from URL fetch, update the dataset
|
||||
if (metadata) {
|
||||
await DatasetStorage.updateDataset(dataset.id, {
|
||||
data: data,
|
||||
...metadata
|
||||
});
|
||||
}
|
||||
|
||||
hideNewDatasetForm();
|
||||
await renderDatasetList();
|
||||
|
||||
// Track event
|
||||
Analytics.track('dataset-create', `Create dataset (${source})`);
|
||||
}
|
||||
|
||||
hideNewDatasetForm();
|
||||
await renderDatasetList();
|
||||
|
||||
// Track event
|
||||
Analytics.track('dataset-create', `Create dataset (${source})`);
|
||||
} catch (error) {
|
||||
errorEl.textContent = `Failed to save dataset: ${error.message}`;
|
||||
}
|
||||
|
||||
@@ -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-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; }
|
||||
|
||||
/* 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; }
|
||||
:root[data-theme="experimental"] .detected-confidence.high { background: #3a6a3a; color: #88ff88; border-color: #66dd66; }
|
||||
.detected-confidence.medium { background: #ffff90; border: 1px solid #d0d060; }
|
||||
|
||||
Reference in New Issue
Block a user