mirror of
https://github.com/olehomelchenko/astrolabe-nvc.git
synced 2025-12-21 13:12:23 +00:00
refactor: initialize snippets storage with sample data and improve import handling
This commit is contained in:
296
sample-data.json
Normal file
296
sample-data.json
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"exportedAt": "2025-10-19T17:08:27.358Z",
|
||||||
|
"exportedBy": "Astrolabe",
|
||||||
|
"snippets": [
|
||||||
|
{
|
||||||
|
"id": 1760877277665.0476,
|
||||||
|
"name": "Inline Data Bar Chart (Sample)",
|
||||||
|
"created": "2025-10-19T12:34:36.985Z",
|
||||||
|
"modified": "2025-10-19T17:07:38.044Z",
|
||||||
|
"spec": {
|
||||||
|
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
||||||
|
"description": "A simple bar chart with embedded data.",
|
||||||
|
"data": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"category": "A",
|
||||||
|
"value": 28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "B",
|
||||||
|
"value": 55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "C",
|
||||||
|
"value": 43
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "D",
|
||||||
|
"value": 91
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "E",
|
||||||
|
"value": 81
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "F",
|
||||||
|
"value": 53
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "G",
|
||||||
|
"value": 19
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "H",
|
||||||
|
"value": 87
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mark": "bar",
|
||||||
|
"encoding": {
|
||||||
|
"x": {
|
||||||
|
"field": "category",
|
||||||
|
"type": "nominal",
|
||||||
|
"axis": {
|
||||||
|
"labelAngle": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"field": "value",
|
||||||
|
"type": "quantitative"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"draftSpec": {
|
||||||
|
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
||||||
|
"description": "A simple bar chart with embedded data.",
|
||||||
|
"data": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"category": "A",
|
||||||
|
"value": 28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "B",
|
||||||
|
"value": 55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "C",
|
||||||
|
"value": 43
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "D",
|
||||||
|
"value": 91
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "E",
|
||||||
|
"value": 81
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "F",
|
||||||
|
"value": 53
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "G",
|
||||||
|
"value": 19
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "H",
|
||||||
|
"value": 87
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mark": "bar",
|
||||||
|
"encoding": {
|
||||||
|
"x": {
|
||||||
|
"field": "category",
|
||||||
|
"type": "nominal",
|
||||||
|
"axis": {
|
||||||
|
"labelAngle": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"field": "value",
|
||||||
|
"type": "quantitative"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"comment": "",
|
||||||
|
"tags": [],
|
||||||
|
"datasetRefs": [],
|
||||||
|
"meta": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1760891255058.404,
|
||||||
|
"name": "World Population Area Chart (Sample)",
|
||||||
|
"created": "2025-10-19T16:27:34.366Z",
|
||||||
|
"modified": "2025-10-19T16:44:48.633Z",
|
||||||
|
"spec": {
|
||||||
|
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
||||||
|
"data": {
|
||||||
|
"name": "World Population (Sample)"
|
||||||
|
},
|
||||||
|
"width": "container",
|
||||||
|
"height": "container",
|
||||||
|
"mark": "area",
|
||||||
|
"encoding": {
|
||||||
|
"x": {
|
||||||
|
"field": "Year",
|
||||||
|
"timeUnit": "year"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"field": "Growth",
|
||||||
|
"type": "quantitative"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"draftSpec": {
|
||||||
|
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
||||||
|
"data": {
|
||||||
|
"name": "World Population (Sample)"
|
||||||
|
},
|
||||||
|
"width": "container",
|
||||||
|
"height": "container",
|
||||||
|
"mark": "area",
|
||||||
|
"encoding": {
|
||||||
|
"x": {
|
||||||
|
"field": "Year",
|
||||||
|
"timeUnit": "year"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"field": "Growth",
|
||||||
|
"type": "quantitative"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"comment": "Visualization using dataset: World Population (Sample)",
|
||||||
|
"tags": [],
|
||||||
|
"datasetRefs": [
|
||||||
|
"World Population (Sample)"
|
||||||
|
],
|
||||||
|
"meta": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1760891802060.8884,
|
||||||
|
"name": "CO2 Emissions Line Chart (Sample)",
|
||||||
|
"created": "2025-10-19T16:36:41.432Z",
|
||||||
|
"modified": "2025-10-19T16:45:32.295Z",
|
||||||
|
"spec": {
|
||||||
|
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
||||||
|
"data": {
|
||||||
|
"name": "CO2 Concentration (Sample)"
|
||||||
|
},
|
||||||
|
"width": 200,
|
||||||
|
"height": 600,
|
||||||
|
"mark": "line",
|
||||||
|
"encoding": {
|
||||||
|
"x": {
|
||||||
|
"field": "Date",
|
||||||
|
"timeUnit": "month"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"field": "Date",
|
||||||
|
"timeUnit": "year",
|
||||||
|
"type": "ordinal"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"field": "CO2",
|
||||||
|
"type": "quantitative",
|
||||||
|
"scale": {
|
||||||
|
"zero": false
|
||||||
|
},
|
||||||
|
"aggregate": "average"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"draftSpec": {
|
||||||
|
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
||||||
|
"data": {
|
||||||
|
"name": "CO2 Concentration (Sample)"
|
||||||
|
},
|
||||||
|
"width": 200,
|
||||||
|
"height": 600,
|
||||||
|
"mark": "line",
|
||||||
|
"encoding": {
|
||||||
|
"x": {
|
||||||
|
"field": "Date",
|
||||||
|
"timeUnit": "month"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"field": "Date",
|
||||||
|
"timeUnit": "year",
|
||||||
|
"type": "ordinal"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"field": "CO2",
|
||||||
|
"type": "quantitative",
|
||||||
|
"scale": {
|
||||||
|
"zero": false
|
||||||
|
},
|
||||||
|
"aggregate": "average"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"comment": "Visualization using dataset: CO2 Concentration (Sample)",
|
||||||
|
"tags": [],
|
||||||
|
"datasetRefs": [
|
||||||
|
"CO2 Concentration (Sample)"
|
||||||
|
],
|
||||||
|
"meta": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"datasets": [
|
||||||
|
{
|
||||||
|
"id": 1760891191833.647,
|
||||||
|
"name": "World Population (Sample)",
|
||||||
|
"created": "2025-10-19T16:26:31.174Z",
|
||||||
|
"modified": "2025-10-19T16:26:31.174Z",
|
||||||
|
"data": "Year\tPopulation\tGrowth\n1951\t2543130380\t0.0175\n1952\t2590270899\t0.0185\n1953\t2640278797\t0.0193\n1954\t2691979339\t0.0196\n1955\t2746072141\t0.0201\n1956\t2801002631\t0.0200\n1957\t2857866857\t0.0203\n1958\t2916108097\t0.0204\n1959\t2970292188\t0.0186\n1960\t3019233434\t0.0165\n1961\t3068370609\t0.0163\n1962\t3126686743\t0.0190\n1963\t3195779247\t0.0221\n1964\t3267212338\t0.0224\n1965\t3337111983\t0.0214\n1966\t3406417036\t0.0208\n1967\t3475448166\t0.0203\n1968\t3546810808\t0.0205\n1969\t3620655275\t0.0208\n1970\t3695390336\t0.0206\n1971\t3770163092\t0.0202\n1972\t3844800885\t0.0198\n1973\t3920251504\t0.0196\n1974\t3995517077\t0.0192\n1975\t4069437231\t0.0185\n1976\t4142505882\t0.0180\n1977\t4215772490\t0.0177\n1978\t4289657708\t0.0175\n1979\t4365582871\t0.0177\n1980\t4444007706\t0.0180\n1981\t4524627658\t0.0181\n1982\t4607984871\t0.0184\n1983\t4691884238\t0.0182\n1984\t4775836074\t0.0179\n1985\t4861730613\t0.0180\n1986\t4950063339\t0.0182\n1987\t5040984495\t0.0184\n1988\t5132293974\t0.0181\n1989\t5223704308\t0.0178\n1990\t5316175862\t0.0177\n1991\t5406245867\t0.0169\n1992\t5492686093\t0.0160\n1993\t5577433523\t0.0154\n1994\t5660727993\t0.0149\n1995\t5743219454\t0.0146\n1996\t5825145298\t0.0143\n1997\t5906481261\t0.0140\n1998\t5987312480\t0.0137\n1999\t6067758458\t0.0134\n2000\t6148898975\t0.0134\n2001\t6230746982\t0.0133\n2002\t6312407360\t0.0131\n2003\t6393898365\t0.0129\n2004\t6475751478\t0.0128\n2005\t6558176119\t0.0127\n2006\t6641416218\t0.0127\n2007\t6725948544\t0.0127\n2008\t6811597272\t0.0127\n2009\t6898305908\t0.0127\n2010\t6985603105\t0.0127\n2011\t7073125425\t0.0125\n2012\t7161697921\t0.0125\n2013\t7250593370\t0.0124\n2014\t7339013419\t0.0122\n2015\t7426597537\t0.0119\n2016\t7513474238\t0.0117\n2017\t7599822404\t0.0115\n2018\t7683789828\t0.0110\n2019\t7764951032\t0.0106\n2020\t7840952880\t0.0098\n2021\t7909295151\t0.0087\n2022\t7975105156\t0.0083\n2023\t8045311447\t0.0088",
|
||||||
|
"format": "tsv",
|
||||||
|
"source": "inline",
|
||||||
|
"comment": "Sample dataset from Wiki: https://en.wikipedia.org/wiki/World_population",
|
||||||
|
"rowCount": 73,
|
||||||
|
"columnCount": 3,
|
||||||
|
"columns": [
|
||||||
|
"Year",
|
||||||
|
"Population",
|
||||||
|
"Growth"
|
||||||
|
],
|
||||||
|
"columnTypes": [
|
||||||
|
{
|
||||||
|
"name": "Year",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Population",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Growth",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"size": 1701
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1760891542328.1973,
|
||||||
|
"name": "CO2 Concentration (Sample)",
|
||||||
|
"created": "2025-10-19T16:32:21.911Z",
|
||||||
|
"modified": "2025-10-19T16:32:46.230Z",
|
||||||
|
"data": "https://raw.githubusercontent.com/vega/vega-datasets/refs/heads/main/data/co2-concentration.csv",
|
||||||
|
"format": "csv",
|
||||||
|
"source": "url",
|
||||||
|
"comment": "",
|
||||||
|
"rowCount": 741,
|
||||||
|
"columnCount": 3,
|
||||||
|
"columns": [
|
||||||
|
"Date",
|
||||||
|
"CO2",
|
||||||
|
"adjusted CO2"
|
||||||
|
],
|
||||||
|
"columnTypes": [],
|
||||||
|
"size": 18547
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -8,27 +8,27 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
const theme = getSetting('ui.theme') || 'light';
|
const theme = getSetting('ui.theme') || 'light';
|
||||||
document.documentElement.setAttribute('data-theme', theme);
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
|
||||||
// Initialize snippet storage and render list
|
// Initialize snippet storage and render list (async)
|
||||||
initializeSnippetsStorage();
|
initializeSnippetsStorage().then(() => {
|
||||||
|
// Initialize sort controls
|
||||||
|
initializeSortControls();
|
||||||
|
|
||||||
// Initialize sort controls
|
// Initialize search controls
|
||||||
initializeSortControls();
|
initializeSearchControls();
|
||||||
|
|
||||||
// Initialize search controls
|
renderSnippetList();
|
||||||
initializeSearchControls();
|
|
||||||
|
|
||||||
renderSnippetList();
|
// Update storage monitor
|
||||||
|
updateStorageMonitor();
|
||||||
|
|
||||||
// Update storage monitor
|
// Auto-select first snippet on page load (only if no hash in URL)
|
||||||
updateStorageMonitor();
|
if (!window.location.hash) {
|
||||||
|
const firstSnippet = SnippetStorage.listSnippets()[0];
|
||||||
// Auto-select first snippet on page load (only if no hash in URL)
|
if (firstSnippet) {
|
||||||
if (!window.location.hash) {
|
selectSnippet(firstSnippet.id);
|
||||||
const firstSnippet = SnippetStorage.listSnippets()[0];
|
}
|
||||||
if (firstSnippet) {
|
|
||||||
selectSnippet(firstSnippet.id);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
// Load saved layout
|
// Load saved layout
|
||||||
loadLayoutFromStorage();
|
loadLayoutFromStorage();
|
||||||
|
|||||||
@@ -280,12 +280,27 @@ const SnippetStorage = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize storage with default snippet if empty
|
// Initialize storage with sample data from JSON file if empty
|
||||||
function initializeSnippetsStorage() {
|
async function initializeSnippetsStorage() {
|
||||||
const existingSnippets = SnippetStorage.loadSnippets();
|
const existingSnippets = SnippetStorage.loadSnippets();
|
||||||
|
|
||||||
if (existingSnippets.length === 0) {
|
if (existingSnippets.length === 0) {
|
||||||
// Create default snippet using the sample spec from config
|
// Try loading sample data from JSON file
|
||||||
|
try {
|
||||||
|
const response = await fetch('sample-data.json');
|
||||||
|
if (response.ok) {
|
||||||
|
const sampleData = await response.json();
|
||||||
|
const result = await processImportedData(sampleData, { silent: true });
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
return result.normalizedSnippets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to load sample-data.json, using fallback:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: create default snippet using the sample spec from config
|
||||||
const defaultSnippet = createSnippet(sampleSpec, "Sample Bar Chart");
|
const defaultSnippet = createSnippet(sampleSpec, "Sample Bar Chart");
|
||||||
defaultSnippet.comment = "A simple bar chart showing category values";
|
defaultSnippet.comment = "A simple bar chart showing category values";
|
||||||
|
|
||||||
@@ -1296,6 +1311,139 @@ function estimateImportFit(existingSnippets, newSnippets) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Core logic to process imported data (shared between file import and initial sample data)
|
||||||
|
async function processImportedData(importedData, options = {}) {
|
||||||
|
const { silent = false } = options;
|
||||||
|
|
||||||
|
// Detect format: legacy (array) or unified (object with version)
|
||||||
|
let snippetsToImport = [];
|
||||||
|
let datasetsToImport = [];
|
||||||
|
|
||||||
|
if (Array.isArray(importedData)) {
|
||||||
|
// Legacy format: array of snippets only
|
||||||
|
snippetsToImport = importedData;
|
||||||
|
} else if (importedData.version && importedData.snippets) {
|
||||||
|
// New unified format
|
||||||
|
snippetsToImport = importedData.snippets || [];
|
||||||
|
datasetsToImport = importedData.datasets || [];
|
||||||
|
} else {
|
||||||
|
// Single snippet object
|
||||||
|
snippetsToImport = [importedData];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snippetsToImport.length === 0) {
|
||||||
|
if (!silent) Toast.info('No snippets found in file');
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import datasets first (if any)
|
||||||
|
let datasetsImported = 0;
|
||||||
|
const renamedDatasets = []; // Track renamed datasets for warning
|
||||||
|
|
||||||
|
for (const datasetData of datasetsToImport) {
|
||||||
|
try {
|
||||||
|
let datasetName = datasetData.name;
|
||||||
|
const originalName = datasetName;
|
||||||
|
|
||||||
|
// Handle name conflicts by renaming
|
||||||
|
if (await DatasetStorage.nameExists(datasetName)) {
|
||||||
|
const timestamp = Date.now().toString().slice(-6);
|
||||||
|
datasetName = `${originalName}_${timestamp}`;
|
||||||
|
|
||||||
|
// Unlikely, but ensure uniqueness
|
||||||
|
let counter = 1;
|
||||||
|
while (await DatasetStorage.nameExists(datasetName)) {
|
||||||
|
datasetName = `${originalName}_${timestamp}_${counter}`;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
renamedDatasets.push({ from: originalName, to: datasetName });
|
||||||
|
}
|
||||||
|
|
||||||
|
await DatasetStorage.createDataset(
|
||||||
|
datasetName,
|
||||||
|
datasetData.data,
|
||||||
|
datasetData.format,
|
||||||
|
datasetData.source,
|
||||||
|
datasetData.comment || ''
|
||||||
|
);
|
||||||
|
datasetsImported++;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to import dataset ${datasetData.name}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import snippets (existing normalization logic)
|
||||||
|
const existingSnippets = SnippetStorage.loadSnippets();
|
||||||
|
const existingIds = new Set(existingSnippets.map(s => s.id));
|
||||||
|
|
||||||
|
let snippetsImported = 0;
|
||||||
|
const normalizedSnippets = [];
|
||||||
|
|
||||||
|
snippetsToImport.forEach(snippet => {
|
||||||
|
const normalized = normalizeSnippet(snippet);
|
||||||
|
|
||||||
|
// Ensure no ID conflicts
|
||||||
|
while (existingIds.has(normalized.id)) {
|
||||||
|
normalized.id = generateSnippetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedSnippets.push(normalized);
|
||||||
|
existingIds.add(normalized.id);
|
||||||
|
snippetsImported++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Estimate storage fit
|
||||||
|
const fit = estimateImportFit(existingSnippets, normalizedSnippets);
|
||||||
|
|
||||||
|
if (!fit.willFit && !silent) {
|
||||||
|
Toast.warning(
|
||||||
|
`⚠️ Import is ${formatBytes(fit.overageBytes)} over the 5 MB limit. Attempting to load...`,
|
||||||
|
5000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save snippets
|
||||||
|
const allSnippets = existingSnippets.concat(normalizedSnippets);
|
||||||
|
|
||||||
|
if (SnippetStorage.saveSnippets(allSnippets)) {
|
||||||
|
if (!silent) {
|
||||||
|
let message = `Imported ${snippetsImported} snippet${snippetsImported !== 1 ? 's' : ''}`;
|
||||||
|
if (datasetsImported > 0) {
|
||||||
|
message += ` and ${datasetsImported} dataset${datasetsImported !== 1 ? 's' : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn about renamed datasets
|
||||||
|
if (renamedDatasets.length > 0) {
|
||||||
|
const renameList = renamedDatasets.map(r => `"${r.from}" → "${r.to}"`).join(', ');
|
||||||
|
Toast.warning(
|
||||||
|
`${message}. Some datasets were renamed due to conflicts: ${renameList}. You may need to update dataset references in affected snippets.`,
|
||||||
|
8000
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Toast.success(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track event
|
||||||
|
Analytics.track('project-import', `Import ${snippetsImported} snippets, ${datasetsImported} datasets`);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSnippetList();
|
||||||
|
updateStorageMonitor();
|
||||||
|
|
||||||
|
return { success: true, snippetsImported, datasetsImported, normalizedSnippets };
|
||||||
|
} else {
|
||||||
|
const overageBytes = fit.overageBytes > 0 ? fit.overageBytes : calculateDataSize(allSnippets) - STORAGE_LIMIT_BYTES;
|
||||||
|
if (!silent) {
|
||||||
|
Toast.error(
|
||||||
|
`Storage quota exceeded by ${formatBytes(overageBytes)}. Please delete some snippets and try again.`,
|
||||||
|
6000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Import snippets and datasets from JSON file
|
// Import snippets and datasets from JSON file
|
||||||
function importSnippets(fileInput) {
|
function importSnippets(fileInput) {
|
||||||
const file = fileInput.files[0];
|
const file = fileInput.files[0];
|
||||||
@@ -1305,128 +1453,7 @@ function importSnippets(fileInput) {
|
|||||||
reader.onload = async function(e) {
|
reader.onload = async function(e) {
|
||||||
try {
|
try {
|
||||||
const importedData = JSON.parse(e.target.result);
|
const importedData = JSON.parse(e.target.result);
|
||||||
|
await processImportedData(importedData);
|
||||||
// Detect format: legacy (array) or unified (object with version)
|
|
||||||
let snippetsToImport = [];
|
|
||||||
let datasetsToImport = [];
|
|
||||||
|
|
||||||
if (Array.isArray(importedData)) {
|
|
||||||
// Legacy format: array of snippets only
|
|
||||||
snippetsToImport = importedData;
|
|
||||||
} else if (importedData.version && importedData.snippets) {
|
|
||||||
// New unified format
|
|
||||||
snippetsToImport = importedData.snippets || [];
|
|
||||||
datasetsToImport = importedData.datasets || [];
|
|
||||||
} else {
|
|
||||||
// Single snippet object
|
|
||||||
snippetsToImport = [importedData];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snippetsToImport.length === 0) {
|
|
||||||
Toast.info('No snippets found in file');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import datasets first (if any)
|
|
||||||
let datasetsImported = 0;
|
|
||||||
const renamedDatasets = []; // Track renamed datasets for warning
|
|
||||||
|
|
||||||
for (const datasetData of datasetsToImport) {
|
|
||||||
try {
|
|
||||||
let datasetName = datasetData.name;
|
|
||||||
const originalName = datasetName;
|
|
||||||
|
|
||||||
// Handle name conflicts by renaming
|
|
||||||
if (await DatasetStorage.nameExists(datasetName)) {
|
|
||||||
const timestamp = Date.now().toString().slice(-6);
|
|
||||||
datasetName = `${originalName}_${timestamp}`;
|
|
||||||
|
|
||||||
// Unlikely, but ensure uniqueness
|
|
||||||
let counter = 1;
|
|
||||||
while (await DatasetStorage.nameExists(datasetName)) {
|
|
||||||
datasetName = `${originalName}_${timestamp}_${counter}`;
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
renamedDatasets.push({ from: originalName, to: datasetName });
|
|
||||||
}
|
|
||||||
|
|
||||||
await DatasetStorage.createDataset(
|
|
||||||
datasetName,
|
|
||||||
datasetData.data,
|
|
||||||
datasetData.format,
|
|
||||||
datasetData.source,
|
|
||||||
datasetData.comment || ''
|
|
||||||
);
|
|
||||||
datasetsImported++;
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Failed to import dataset ${datasetData.name}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import snippets (existing normalization logic)
|
|
||||||
const existingSnippets = SnippetStorage.loadSnippets();
|
|
||||||
const existingIds = new Set(existingSnippets.map(s => s.id));
|
|
||||||
|
|
||||||
let snippetsImported = 0;
|
|
||||||
const normalizedSnippets = [];
|
|
||||||
|
|
||||||
snippetsToImport.forEach(snippet => {
|
|
||||||
const normalized = normalizeSnippet(snippet);
|
|
||||||
|
|
||||||
// Ensure no ID conflicts
|
|
||||||
while (existingIds.has(normalized.id)) {
|
|
||||||
normalized.id = generateSnippetId();
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizedSnippets.push(normalized);
|
|
||||||
existingIds.add(normalized.id);
|
|
||||||
snippetsImported++;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Estimate storage fit
|
|
||||||
const fit = estimateImportFit(existingSnippets, normalizedSnippets);
|
|
||||||
|
|
||||||
if (!fit.willFit) {
|
|
||||||
Toast.warning(
|
|
||||||
`⚠️ Import is ${formatBytes(fit.overageBytes)} over the 5 MB limit. Attempting to load...`,
|
|
||||||
5000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save snippets
|
|
||||||
const allSnippets = existingSnippets.concat(normalizedSnippets);
|
|
||||||
|
|
||||||
if (SnippetStorage.saveSnippets(allSnippets)) {
|
|
||||||
let message = `Imported ${snippetsImported} snippet${snippetsImported !== 1 ? 's' : ''}`;
|
|
||||||
if (datasetsImported > 0) {
|
|
||||||
message += ` and ${datasetsImported} dataset${datasetsImported !== 1 ? 's' : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn about renamed datasets
|
|
||||||
if (renamedDatasets.length > 0) {
|
|
||||||
const renameList = renamedDatasets.map(r => `"${r.from}" → "${r.to}"`).join(', ');
|
|
||||||
Toast.warning(
|
|
||||||
`${message}. Some datasets were renamed due to conflicts: ${renameList}. You may need to update dataset references in affected snippets.`,
|
|
||||||
8000
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Toast.success(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSnippetList();
|
|
||||||
updateStorageMonitor();
|
|
||||||
|
|
||||||
// Track event
|
|
||||||
Analytics.track('project-import', `Import ${snippetsImported} snippets, ${datasetsImported} datasets`);
|
|
||||||
} else {
|
|
||||||
const overageBytes = fit.overageBytes > 0 ? fit.overageBytes : calculateDataSize(allSnippets) - STORAGE_LIMIT_BYTES;
|
|
||||||
Toast.error(
|
|
||||||
`Storage quota exceeded by ${formatBytes(overageBytes)}. Please delete some snippets and try again.`,
|
|
||||||
6000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Import error:', error);
|
console.error('Import error:', error);
|
||||||
Toast.error('Failed to import. Please check that the file is valid JSON.');
|
Toast.error('Failed to import. Please check that the file is valid JSON.');
|
||||||
|
|||||||
Reference in New Issue
Block a user