feat: implement Alpine.js for dataset management and rendering in dataset manager

This commit is contained in:
2025-11-24 18:21:06 +02:00
parent ebdade0c7e
commit d08f995feb
3 changed files with 176 additions and 999 deletions

View File

@@ -248,7 +248,7 @@
</div>
<div class="modal-body">
<!-- 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">
<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>
@@ -256,7 +256,26 @@
</div>
<div class="dataset-container">
<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 class="dataset-details" id="dataset-details" style="display: none;">
<div class="dataset-detail-section">

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,50 @@
// 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_VERSION = 1;
const STORE_NAME = 'datasets';
@@ -348,55 +393,15 @@ async function fetchURLMetadata(url, format) {
}
// Render dataset list in modal
// Alpine.js now handles rendering, this just triggers a refresh
async function renderDatasetList() {
const datasets = await DatasetStorage.listDatasets();
if (datasets.length === 0) {
document.getElementById('dataset-list').innerHTML = '<div class="dataset-empty">No datasets yet. Click "New Dataset" to create one.</div>';
return;
const listView = document.getElementById('dataset-list-view');
if (listView && listView.__x) {
const component = Alpine.$data(listView);
if (component && component.loadDatasets) {
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
@@ -404,13 +409,9 @@ async function selectDataset(datasetId, updateURL = true) {
const dataset = await DatasetStorage.getDataset(datasetId);
if (!dataset) return;
// Update selection state
document.querySelectorAll('.dataset-item').forEach(item => {
item.classList.remove('selected');
});
const selectedItem = document.querySelector(`[data-item-id="${datasetId}"]`);
if (selectedItem) {
selectedItem.classList.add('selected');
// Update Alpine store selection (Alpine handles highlighting via :class binding)
if (typeof Alpine !== 'undefined' && Alpine.store('datasets')) {
Alpine.store('datasets').currentDatasetId = datasetId;
}
// Show details panel