mirror of
https://github.com/olehomelchenko/astrolabe-nvc.git
synced 2025-12-21 21:22:23 +00:00
feat: implement Alpine.js for dataset management and rendering in dataset manager
This commit is contained in:
23
index.html
23
index.html
@@ -248,7 +248,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<!-- List View (default) -->
|
<!-- List View (default) -->
|
||||||
<div id="dataset-list-view" class="dataset-view">
|
<div id="dataset-list-view" class="dataset-view" x-data="datasetList()">
|
||||||
<div class="dataset-list-header">
|
<div class="dataset-list-header">
|
||||||
<button class="btn btn-modal primary" id="new-dataset-btn" title="Create a new dataset">New Dataset</button>
|
<button class="btn btn-modal primary" id="new-dataset-btn" title="Create a new dataset">New Dataset</button>
|
||||||
<button class="btn btn-modal" id="import-dataset-btn" title="Import dataset from file">Import</button>
|
<button class="btn btn-modal" id="import-dataset-btn" title="Import dataset from file">Import</button>
|
||||||
@@ -256,7 +256,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="dataset-container">
|
<div class="dataset-container">
|
||||||
<div class="dataset-list" id="dataset-list">
|
<div class="dataset-list" id="dataset-list">
|
||||||
<!-- Dynamically populated by renderDatasetList() -->
|
<!-- Dataset items rendered by Alpine.js -->
|
||||||
|
<template x-for="dataset in datasets" :key="dataset.id">
|
||||||
|
<div class="dataset-item"
|
||||||
|
:data-item-id="dataset.id"
|
||||||
|
:class="{ 'selected': $store.datasets.currentDatasetId === dataset.id }"
|
||||||
|
@click="selectDataset(dataset.id)">
|
||||||
|
<div class="dataset-info">
|
||||||
|
<div class="dataset-name" x-text="dataset.name"></div>
|
||||||
|
<div class="dataset-meta" x-text="formatMeta(dataset)"></div>
|
||||||
|
</div>
|
||||||
|
<div class="dataset-usage-badge"
|
||||||
|
x-show="getUsageCount(dataset) > 0"
|
||||||
|
:title="getUsageCount(dataset) + ' snippet' + (getUsageCount(dataset) !== 1 ? 's' : '') + ' using this dataset'"
|
||||||
|
x-text="'📄 ' + getUsageCount(dataset)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="dataset-empty" x-show="datasets.length === 0">
|
||||||
|
No datasets yet. Click "New Dataset" to create one.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,50 @@
|
|||||||
// Dataset management with IndexedDB
|
// Dataset management with IndexedDB
|
||||||
|
|
||||||
|
// Alpine.js store for dataset UI state
|
||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.store('datasets', {
|
||||||
|
currentDatasetId: null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Alpine.js component for dataset list - thin wrapper around existing logic
|
||||||
|
function datasetList() {
|
||||||
|
return {
|
||||||
|
datasets: [],
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
await this.loadDatasets();
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadDatasets() {
|
||||||
|
this.datasets = await DatasetStorage.listDatasets();
|
||||||
|
// Sort by modified date (most recent first) - keeping existing behavior
|
||||||
|
this.datasets.sort((a, b) => new Date(b.modified) - new Date(a.modified));
|
||||||
|
},
|
||||||
|
|
||||||
|
formatMeta(dataset) {
|
||||||
|
const formatLabel = dataset.format ? dataset.format.toUpperCase() : 'UNKNOWN';
|
||||||
|
if (dataset.source === 'url') {
|
||||||
|
if (dataset.rowCount !== null && dataset.size !== null) {
|
||||||
|
return `URL • ${dataset.rowCount} rows • ${formatLabel} • ${formatBytes(dataset.size)}`;
|
||||||
|
} else {
|
||||||
|
return `URL • ${formatLabel}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `${dataset.rowCount} rows • ${formatLabel} • ${formatBytes(dataset.size)}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getUsageCount(dataset) {
|
||||||
|
return countSnippetUsage(dataset.name);
|
||||||
|
},
|
||||||
|
|
||||||
|
selectDataset(datasetId) {
|
||||||
|
window.selectDataset(datasetId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const DB_NAME = 'astrolabe-datasets';
|
const DB_NAME = 'astrolabe-datasets';
|
||||||
const DB_VERSION = 1;
|
const DB_VERSION = 1;
|
||||||
const STORE_NAME = 'datasets';
|
const STORE_NAME = 'datasets';
|
||||||
@@ -348,55 +393,15 @@ async function fetchURLMetadata(url, format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render dataset list in modal
|
// Render dataset list in modal
|
||||||
|
// Alpine.js now handles rendering, this just triggers a refresh
|
||||||
async function renderDatasetList() {
|
async function renderDatasetList() {
|
||||||
const datasets = await DatasetStorage.listDatasets();
|
const listView = document.getElementById('dataset-list-view');
|
||||||
|
if (listView && listView.__x) {
|
||||||
if (datasets.length === 0) {
|
const component = Alpine.$data(listView);
|
||||||
document.getElementById('dataset-list').innerHTML = '<div class="dataset-empty">No datasets yet. Click "New Dataset" to create one.</div>';
|
if (component && component.loadDatasets) {
|
||||||
return;
|
await component.loadDatasets();
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by modified date (most recent first)
|
|
||||||
datasets.sort((a, b) => new Date(b.modified) - new Date(a.modified));
|
|
||||||
|
|
||||||
// Format individual dataset items
|
|
||||||
const formatDatasetItem = (dataset) => {
|
|
||||||
let metaText;
|
|
||||||
const formatLabel = dataset.format ? dataset.format.toUpperCase() : 'UNKNOWN';
|
|
||||||
|
|
||||||
if (dataset.source === 'url') {
|
|
||||||
// Show metadata if available, otherwise just URL and format
|
|
||||||
if (dataset.rowCount !== null && dataset.size !== null) {
|
|
||||||
metaText = `URL • ${dataset.rowCount} rows • ${formatLabel} • ${formatBytes(dataset.size)}`;
|
|
||||||
} else {
|
|
||||||
metaText = `URL • ${formatLabel}`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
metaText = `${dataset.rowCount} rows • ${formatLabel} • ${formatBytes(dataset.size)}`;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Count snippet usage and create badge
|
|
||||||
const usageCount = countSnippetUsage(dataset.name);
|
|
||||||
const usageBadge = usageCount > 0
|
|
||||||
? `<div class="dataset-usage-badge" title="${usageCount} snippet${usageCount !== 1 ? 's' : ''} using this dataset">📄 ${usageCount}</div>`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="dataset-item" data-item-id="${dataset.id}">
|
|
||||||
<div class="dataset-info">
|
|
||||||
<div class="dataset-name">${dataset.name}</div>
|
|
||||||
<div class="dataset-meta">${metaText}</div>
|
|
||||||
</div>
|
|
||||||
${usageBadge}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use generic list renderer
|
|
||||||
renderGenericList('dataset-list', datasets, formatDatasetItem, selectDataset, {
|
|
||||||
emptyMessage: 'No datasets yet. Click "New Dataset" to create one.',
|
|
||||||
itemSelector: '.dataset-item'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select a dataset and show details
|
// Select a dataset and show details
|
||||||
@@ -404,13 +409,9 @@ async function selectDataset(datasetId, updateURL = true) {
|
|||||||
const dataset = await DatasetStorage.getDataset(datasetId);
|
const dataset = await DatasetStorage.getDataset(datasetId);
|
||||||
if (!dataset) return;
|
if (!dataset) return;
|
||||||
|
|
||||||
// Update selection state
|
// Update Alpine store selection (Alpine handles highlighting via :class binding)
|
||||||
document.querySelectorAll('.dataset-item').forEach(item => {
|
if (typeof Alpine !== 'undefined' && Alpine.store('datasets')) {
|
||||||
item.classList.remove('selected');
|
Alpine.store('datasets').currentDatasetId = datasetId;
|
||||||
});
|
|
||||||
const selectedItem = document.querySelector(`[data-item-id="${datasetId}"]`);
|
|
||||||
if (selectedItem) {
|
|
||||||
selectedItem.classList.add('selected');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show details panel
|
// Show details panel
|
||||||
|
|||||||
Reference in New Issue
Block a user