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>
<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

View File

@@ -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