mirror of
https://github.com/olehomelchenko/astrolabe-nvc.git
synced 2025-12-21 21:22:23 +00:00
refactor: Migrate preview fit mode state management and UI updates to an Alpine.js store.
This commit is contained in:
583
index.html
583
index.html
@@ -52,25 +52,19 @@
|
|||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<!-- Toggle Button Strip -->
|
<!-- Toggle Button Strip -->
|
||||||
<div class="toggle-strip" x-data>
|
<div class="toggle-strip" x-data>
|
||||||
<button class="btn btn-icon xlarge"
|
<button class="btn btn-icon xlarge" id="toggle-snippet-panel"
|
||||||
id="toggle-snippet-panel"
|
:class="{ 'active': $store.panels.snippetVisible }" @click="togglePanel('snippet-panel')"
|
||||||
:class="{ 'active': $store.panels.snippetVisible }"
|
title="Toggle Snippets Panel">
|
||||||
@click="togglePanel('snippet-panel')"
|
|
||||||
title="Toggle Snippets Panel">
|
|
||||||
📄
|
📄
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-icon xlarge"
|
<button class="btn btn-icon xlarge" id="toggle-editor-panel"
|
||||||
id="toggle-editor-panel"
|
:class="{ 'active': $store.panels.editorVisible }" @click="togglePanel('editor-panel')"
|
||||||
:class="{ 'active': $store.panels.editorVisible }"
|
title="Toggle Editor Panel">
|
||||||
@click="togglePanel('editor-panel')"
|
|
||||||
title="Toggle Editor Panel">
|
|
||||||
✏️
|
✏️
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-icon xlarge"
|
<button class="btn btn-icon xlarge" id="toggle-preview-panel"
|
||||||
id="toggle-preview-panel"
|
:class="{ 'active': $store.panels.previewVisible }" @click="togglePanel('preview-panel')"
|
||||||
:class="{ 'active': $store.panels.previewVisible }"
|
title="Toggle Preview Panel">
|
||||||
@click="togglePanel('preview-panel')"
|
|
||||||
title="Toggle Preview Panel">
|
|
||||||
👁️
|
👁️
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-icon xlarge" id="toggle-datasets" title="Datasets">
|
<button class="btn btn-icon xlarge" id="toggle-datasets" title="Datasets">
|
||||||
@@ -86,98 +80,72 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="sort-controls">
|
<div class="sort-controls">
|
||||||
<span class="sort-label">Sort by:</span>
|
<span class="sort-label">Sort by:</span>
|
||||||
<button class="sort-btn"
|
<button class="sort-btn" :class="{ 'active': sortBy === 'modified' }"
|
||||||
:class="{ 'active': sortBy === 'modified' }"
|
@click="toggleSort('modified')" title="Sort by last modified date">
|
||||||
@click="toggleSort('modified')"
|
|
||||||
title="Sort by last modified date">
|
|
||||||
<span class="sort-text">Modified</span>
|
<span class="sort-text">Modified</span>
|
||||||
<span class="sort-arrow" x-text="sortBy === 'modified' && sortOrder === 'desc' ? '⬇' : '⬆'">⬇</span>
|
<span class="sort-arrow"
|
||||||
|
x-text="sortBy === 'modified' && sortOrder === 'desc' ? '⬇' : '⬆'">⬇</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="sort-btn"
|
<button class="sort-btn" :class="{ 'active': sortBy === 'created' }" @click="toggleSort('created')"
|
||||||
:class="{ 'active': sortBy === 'created' }"
|
title="Sort by creation date">
|
||||||
@click="toggleSort('created')"
|
|
||||||
title="Sort by creation date">
|
|
||||||
<span class="sort-text">Created</span>
|
<span class="sort-text">Created</span>
|
||||||
<span class="sort-arrow" x-text="sortBy === 'created' && sortOrder === 'desc' ? '⬇' : '⬆'">⬇</span>
|
<span class="sort-arrow"
|
||||||
|
x-text="sortBy === 'created' && sortOrder === 'desc' ? '⬇' : '⬆'">⬇</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="sort-btn"
|
<button class="sort-btn" :class="{ 'active': sortBy === 'name' }" @click="toggleSort('name')"
|
||||||
:class="{ 'active': sortBy === 'name' }"
|
title="Sort alphabetically by name">
|
||||||
@click="toggleSort('name')"
|
|
||||||
title="Sort alphabetically by name">
|
|
||||||
<span class="sort-text">Name</span>
|
<span class="sort-text">Name</span>
|
||||||
<span class="sort-arrow" x-text="sortBy === 'name' && sortOrder === 'desc' ? '⬇' : '⬆'">⬇</span>
|
<span class="sort-arrow" x-text="sortBy === 'name' && sortOrder === 'desc' ? '⬇' : '⬆'">⬇</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="sort-btn"
|
<button class="sort-btn" :class="{ 'active': sortBy === 'size' }" @click="toggleSort('size')"
|
||||||
:class="{ 'active': sortBy === 'size' }"
|
title="Sort by snippet size">
|
||||||
@click="toggleSort('size')"
|
|
||||||
title="Sort by snippet size">
|
|
||||||
<span class="sort-text">Size</span>
|
<span class="sort-text">Size</span>
|
||||||
<span class="sort-arrow" x-text="sortBy === 'size' && sortOrder === 'desc' ? '⬇' : '⬆'">⬇</span>
|
<span class="sort-arrow" x-text="sortBy === 'size' && sortOrder === 'desc' ? '⬇' : '⬆'">⬇</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-controls">
|
<div class="search-controls">
|
||||||
<input type="text"
|
<input type="text" id="snippet-search" x-model="searchQuery" placeholder="Search snippets..." />
|
||||||
id="snippet-search"
|
<button class="btn btn-icon" @click="clearSearch()" title="Clear search">×</button>
|
||||||
x-model="searchQuery"
|
|
||||||
placeholder="Search snippets..." />
|
|
||||||
<button class="btn btn-icon"
|
|
||||||
@click="clearSearch()"
|
|
||||||
title="Clear search">×</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-content">
|
<div class="panel-content">
|
||||||
<ul class="snippet-list" id="snippet-list">
|
<ul class="snippet-list" id="snippet-list">
|
||||||
<!-- Ghost card for creating new snippets -->
|
<!-- Ghost card for creating new snippets -->
|
||||||
<li class="snippet-item ghost-card"
|
<li class="snippet-item ghost-card" id="new-snippet-card" @click="createNewSnippet()">
|
||||||
id="new-snippet-card"
|
|
||||||
@click="createNewSnippet()">
|
|
||||||
<div class="snippet-name">+ Create New Snippet</div>
|
<div class="snippet-name">+ Create New Snippet</div>
|
||||||
<div class="snippet-date">Click to create</div>
|
<div class="snippet-date">Click to create</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<!-- Snippet items -->
|
<!-- Snippet items -->
|
||||||
<template x-for="snippet in filteredSnippets" :key="snippet.id">
|
<template x-for="snippet in filteredSnippets" :key="snippet.id">
|
||||||
<li class="snippet-item"
|
<li class="snippet-item" :data-item-id="snippet.id"
|
||||||
:data-item-id="snippet.id"
|
|
||||||
:class="{ 'selected': $store.snippets.currentSnippetId === snippet.id }"
|
:class="{ 'selected': $store.snippets.currentSnippetId === snippet.id }"
|
||||||
@click="selectSnippet(snippet.id)">
|
@click="selectSnippet(snippet.id)">
|
||||||
<div class="snippet-info">
|
<div class="snippet-info">
|
||||||
<div class="snippet-name">
|
<div class="snippet-name">
|
||||||
<span x-text="snippet.name"></span>
|
<span x-text="snippet.name"></span>
|
||||||
<span x-show="snippet.datasetRefs && snippet.datasetRefs.length > 0"
|
<span x-show="snippet.datasetRefs && snippet.datasetRefs.length > 0"
|
||||||
class="snippet-dataset-icon"
|
class="snippet-dataset-icon" title="Uses external dataset">📁</span>
|
||||||
title="Uses external dataset">📁</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="snippet-date" x-text="formatDate(snippet)"></div>
|
<div class="snippet-date" x-text="formatDate(snippet)"></div>
|
||||||
</div>
|
</div>
|
||||||
<span x-show="getSize(snippet) >= 1"
|
<span x-show="getSize(snippet) >= 1" class="snippet-size"
|
||||||
class="snippet-size"
|
x-text="getSize(snippet).toFixed(0) + ' KB'"></span>
|
||||||
x-text="getSize(snippet).toFixed(0) + ' KB'"></span>
|
<div class="snippet-status" :class="hasDraft(snippet) ? 'draft' : 'published'"></div>
|
||||||
<div class="snippet-status"
|
|
||||||
:class="hasDraft(snippet) ? 'draft' : 'published'"></div>
|
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="placeholder"
|
<div class="placeholder" x-show="filteredSnippets.length === 0"
|
||||||
x-show="filteredSnippets.length === 0"
|
x-text="searchQuery.trim() ? 'No snippets match your search' : 'No snippets found'">
|
||||||
x-text="searchQuery.trim() ? 'No snippets match your search' : 'No snippets found'">
|
|
||||||
Click to select a snippet
|
Click to select a snippet
|
||||||
</div>
|
</div>
|
||||||
<div class="snippet-meta" id="snippet-meta" style="display: none;">
|
<div class="snippet-meta" id="snippet-meta" style="display: none;">
|
||||||
<div class="meta-header">Name</div>
|
<div class="meta-header">Name</div>
|
||||||
<input type="text"
|
<input type="text" id="snippet-name" class="input small" placeholder="Snippet name..."
|
||||||
id="snippet-name"
|
x-model="snippetName" @input="saveMetaDebounced()" />
|
||||||
class="input small"
|
|
||||||
placeholder="Snippet name..."
|
|
||||||
x-model="snippetName"
|
|
||||||
@input="saveMetaDebounced()" />
|
|
||||||
|
|
||||||
<div class="meta-header">Comment</div>
|
<div class="meta-header">Comment</div>
|
||||||
<textarea id="snippet-comment"
|
<textarea id="snippet-comment" class="input textarea medium" placeholder="Add a comment..."
|
||||||
class="input textarea medium"
|
rows="3" x-model="snippetComment" @input="saveMetaDebounced()"></textarea>
|
||||||
placeholder="Add a comment..."
|
|
||||||
rows="3"
|
|
||||||
x-model="snippetComment"
|
|
||||||
@input="saveMetaDebounced()"></textarea>
|
|
||||||
|
|
||||||
<div class="meta-info">
|
<div class="meta-info">
|
||||||
<div class="meta-info-item">
|
<div class="meta-info-item">
|
||||||
@@ -198,8 +166,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="meta-actions">
|
<div class="meta-actions">
|
||||||
<button class="btn btn-standard flex" id="duplicate-btn" title="Create a copy of this snippet">Duplicate</button>
|
<button class="btn btn-standard flex" id="duplicate-btn"
|
||||||
<button class="btn btn-standard flex danger" id="delete-btn" title="Delete this snippet permanently">Delete</button>
|
title="Create a copy of this snippet">Duplicate</button>
|
||||||
|
<button class="btn btn-standard flex danger" id="delete-btn"
|
||||||
|
title="Delete this snippet permanently">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="storage-monitor" id="storage-monitor">
|
<div class="storage-monitor" id="storage-monitor">
|
||||||
@@ -222,21 +192,22 @@
|
|||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<span>Editor</span>
|
<span>Editor</span>
|
||||||
<div class="editor-controls">
|
<div class="editor-controls">
|
||||||
<button class="btn btn-action" id="extract-btn" style="display: none; background: #87CEEB;" title="Extract inline data to a reusable dataset">Extract to Dataset</button>
|
<button class="btn btn-action" id="extract-btn" style="display: none; background: #87CEEB;"
|
||||||
<button class="btn btn-action publish" id="publish-btn" title="Publish draft changes (Cmd/Ctrl+S)">Publish</button>
|
title="Extract inline data to a reusable dataset">Extract to Dataset</button>
|
||||||
<button class="btn btn-action revert" id="revert-btn" title="Discard draft and revert to published version">Revert</button>
|
<button class="btn btn-action publish" id="publish-btn"
|
||||||
|
title="Publish draft changes (Cmd/Ctrl+S)">Publish</button>
|
||||||
|
<button class="btn btn-action revert" id="revert-btn"
|
||||||
|
title="Discard draft and revert to published version">Revert</button>
|
||||||
<span class="view-label">View:</span>
|
<span class="view-label">View:</span>
|
||||||
<div class="view-toggle-group">
|
<div class="view-toggle-group">
|
||||||
<button class="btn btn-toggle"
|
<button class="btn btn-toggle" id="view-draft"
|
||||||
id="view-draft"
|
:class="{ 'active': $store.snippets.viewMode === 'draft' }"
|
||||||
:class="{ 'active': $store.snippets.viewMode === 'draft' }"
|
@click="$store.snippets.viewMode = 'draft'; switchViewMode('draft')"
|
||||||
@click="$store.snippets.viewMode = 'draft'; switchViewMode('draft')"
|
title="View and edit draft version">Draft</button>
|
||||||
title="View and edit draft version">Draft</button>
|
<button class="btn btn-toggle" id="view-published"
|
||||||
<button class="btn btn-toggle"
|
:class="{ 'active': $store.snippets.viewMode === 'published' }"
|
||||||
id="view-published"
|
@click="$store.snippets.viewMode = 'published'; switchViewMode('published')"
|
||||||
:class="{ 'active': $store.snippets.viewMode === 'published' }"
|
title="View published version (read-only if draft exists)">Published</button>
|
||||||
@click="$store.snippets.viewMode = 'published'; switchViewMode('published')"
|
|
||||||
title="View published version (read-only if draft exists)">Published</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -252,17 +223,27 @@
|
|||||||
<div class="panel preview-panel" id="preview-panel">
|
<div class="panel preview-panel" id="preview-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<span>Preview</span>
|
<span>Preview</span>
|
||||||
<div class="preview-controls">
|
<div class="preview-controls" x-data>
|
||||||
<span class="view-label">Fit:</span>
|
<span class="view-label">Fit:</span>
|
||||||
<div class="view-toggle-group">
|
<div class="view-toggle-group">
|
||||||
<button class="btn btn-toggle active" id="preview-fit-default" title="Display at original spec dimensions">Original</button>
|
<button class="btn btn-toggle" :class="{ 'active': $store.preview.fitMode === 'default' }"
|
||||||
<button class="btn btn-toggle" id="preview-fit-width" title="Scale to fit preview pane width (⚠️ for faceted specs, applies to each facet)">Width</button>
|
@click="$store.preview.fitMode = 'default'; setPreviewFitMode('default')"
|
||||||
<button class="btn btn-toggle" id="preview-fit-full" title="Scale to fit entire preview pane (⚠️ for faceted specs, applies to each facet)">Full</button>
|
id="preview-fit-default" title="Display at original spec dimensions">Original</button>
|
||||||
|
<button class="btn btn-toggle" :class="{ 'active': $store.preview.fitMode === 'width' }"
|
||||||
|
@click="$store.preview.fitMode = 'width'; setPreviewFitMode('width')"
|
||||||
|
id="preview-fit-width"
|
||||||
|
title="Scale to fit preview pane width (⚠️ for faceted specs, applies to each facet)">Width</button>
|
||||||
|
<button class="btn btn-toggle" :class="{ 'active': $store.preview.fitMode === 'full' }"
|
||||||
|
@click="$store.preview.fitMode = 'full'; setPreviewFitMode('full')"
|
||||||
|
id="preview-fit-full"
|
||||||
|
title="Scale to fit entire preview pane (⚠️ for faceted specs, applies to each facet)">Full</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-content" style="position: relative;">
|
<div class="panel-content" style="position: relative;">
|
||||||
<div id="vega-preview" style="height: 100%; width: 100%; overflow: auto; display: flex; align-items: center; justify-content: center;"></div>
|
<div id="vega-preview"
|
||||||
|
style="height: 100%; width: 100%; overflow: auto; display: flex; align-items: center; justify-content: center;">
|
||||||
|
</div>
|
||||||
<div id="preview-overlay" class="preview-overlay" style="display: none;"></div>
|
<div id="preview-overlay" class="preview-overlay" style="display: none;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -280,26 +261,27 @@
|
|||||||
<!-- List View (default) -->
|
<!-- List View (default) -->
|
||||||
<div id="dataset-list-view" class="dataset-view" x-data="datasetList()">
|
<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
|
||||||
<button class="btn btn-modal" id="import-dataset-btn" title="Import dataset from file">Import</button>
|
Dataset</button>
|
||||||
<input type="file" id="import-dataset-file" accept=".json,.csv,.tsv,.txt" style="display: none;" />
|
<button class="btn btn-modal" id="import-dataset-btn"
|
||||||
|
title="Import dataset from file">Import</button>
|
||||||
|
<input type="file" id="import-dataset-file" accept=".json,.csv,.tsv,.txt"
|
||||||
|
style="display: none;" />
|
||||||
</div>
|
</div>
|
||||||
<div class="dataset-container">
|
<div class="dataset-container">
|
||||||
<div class="dataset-list" id="dataset-list">
|
<div class="dataset-list" id="dataset-list">
|
||||||
<!-- Dataset items rendered by Alpine.js -->
|
<!-- Dataset items rendered by Alpine.js -->
|
||||||
<template x-for="dataset in datasets" :key="dataset.id">
|
<template x-for="dataset in datasets" :key="dataset.id">
|
||||||
<div class="dataset-item"
|
<div class="dataset-item" :data-item-id="dataset.id"
|
||||||
:data-item-id="dataset.id"
|
:class="{ 'selected': $store.datasets.currentDatasetId === dataset.id }"
|
||||||
:class="{ 'selected': $store.datasets.currentDatasetId === dataset.id }"
|
@click="selectDataset(dataset.id)">
|
||||||
@click="selectDataset(dataset.id)">
|
|
||||||
<div class="dataset-info">
|
<div class="dataset-info">
|
||||||
<div class="dataset-name" x-text="dataset.name"></div>
|
<div class="dataset-name" x-text="dataset.name"></div>
|
||||||
<div class="dataset-meta" x-text="formatMeta(dataset)"></div>
|
<div class="dataset-meta" x-text="formatMeta(dataset)"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dataset-usage-badge"
|
<div class="dataset-usage-badge" x-show="getUsageCount(dataset) > 0"
|
||||||
x-show="getUsageCount(dataset) > 0"
|
:title="getUsageCount(dataset) + ' snippet' + (getUsageCount(dataset) !== 1 ? 's' : '') + ' using this dataset'"
|
||||||
:title="getUsageCount(dataset) + ' snippet' + (getUsageCount(dataset) !== 1 ? 's' : '') + ' using this dataset'"
|
x-text="'📄 ' + getUsageCount(dataset)">
|
||||||
x-text="'📄 ' + getUsageCount(dataset)">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -310,23 +292,32 @@
|
|||||||
<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">
|
||||||
<div class="dataset-actions">
|
<div class="dataset-actions">
|
||||||
<button class="btn btn-modal primary" id="edit-dataset-btn" title="Edit this dataset contents">Edit Contents</button>
|
<button class="btn btn-modal primary" id="edit-dataset-btn"
|
||||||
<button class="btn btn-modal primary" id="build-chart-btn" title="Build a chart using this dataset">Build Chart</button>
|
title="Edit this dataset contents">Edit Contents</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 primary" id="build-chart-btn"
|
||||||
<button class="btn btn-modal" id="export-dataset-btn" title="Export this dataset to file">Export</button>
|
title="Build a chart using this dataset">Build Chart</button>
|
||||||
<button class="btn btn-modal" id="copy-reference-btn" title="Copy dataset reference to clipboard">Copy Reference</button>
|
<button class="btn btn-modal primary" id="new-snippet-btn"
|
||||||
<button class="btn btn-modal danger" id="delete-dataset-btn" title="Delete this dataset permanently">Delete</button>
|
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>
|
||||||
|
<button class="btn btn-modal danger" id="delete-dataset-btn"
|
||||||
|
title="Delete this dataset permanently">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dataset-detail-header">Name</div>
|
<div class="dataset-detail-header">Name</div>
|
||||||
<input type="text" id="dataset-detail-name" class="input" placeholder="Dataset name..." />
|
<input type="text" id="dataset-detail-name" class="input"
|
||||||
|
placeholder="Dataset name..." />
|
||||||
|
|
||||||
<div class="dataset-detail-header">Comment</div>
|
<div class="dataset-detail-header">Comment</div>
|
||||||
<textarea id="dataset-detail-comment" class="input textarea" placeholder="Add a comment..." rows="3"></textarea>
|
<textarea id="dataset-detail-comment" class="input textarea"
|
||||||
|
placeholder="Add a comment..." rows="3"></textarea>
|
||||||
|
|
||||||
<div class="dataset-detail-header-row">
|
<div class="dataset-detail-header-row">
|
||||||
<span class="dataset-detail-header">Overview</span>
|
<span class="dataset-detail-header">Overview</span>
|
||||||
<button class="btn btn-icon large" id="refresh-metadata-btn" style="display: none;" title="Refresh metadata from URL">🔄</button>
|
<button class="btn btn-icon large" id="refresh-metadata-btn" style="display: none;"
|
||||||
|
title="Refresh metadata from URL">🔄</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="dataset-overview-grid">
|
<div class="dataset-overview-grid">
|
||||||
<div class="overview-section">
|
<div class="overview-section">
|
||||||
@@ -372,12 +363,15 @@
|
|||||||
<div class="dataset-detail-header-row">
|
<div class="dataset-detail-header-row">
|
||||||
<span class="dataset-detail-header">Preview</span>
|
<span class="dataset-detail-header">Preview</span>
|
||||||
<div class="preview-toggle-group" id="preview-toggle-group" style="display: none;">
|
<div class="preview-toggle-group" id="preview-toggle-group" style="display: none;">
|
||||||
<button class="btn btn-toggle small active" id="preview-raw-btn" title="Show raw data preview">Raw</button>
|
<button class="btn btn-toggle small active" id="preview-raw-btn"
|
||||||
<button class="btn btn-toggle small" id="preview-table-btn" title="Show data in table format with type detection">Table</button>
|
title="Show raw data preview">Raw</button>
|
||||||
|
<button class="btn btn-toggle small" id="preview-table-btn"
|
||||||
|
title="Show data in table format with type detection">Table</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<pre id="dataset-preview" class="preview-box large"></pre>
|
<pre id="dataset-preview" class="preview-box large"></pre>
|
||||||
<div id="dataset-preview-table" class="preview-table-container" style="display: none;"></div>
|
<div id="dataset-preview-table" class="preview-table-container" style="display: none;">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="dataset-snippets-section" style="display: none;">
|
<div id="dataset-snippets-section" style="display: none;">
|
||||||
<div class="dataset-detail-header">Linked Snippets</div>
|
<div class="dataset-detail-header">Linked Snippets</div>
|
||||||
@@ -397,7 +391,8 @@
|
|||||||
|
|
||||||
<div class="dataset-form-group">
|
<div class="dataset-form-group">
|
||||||
<label class="dataset-form-label">Name *</label>
|
<label class="dataset-form-label">Name *</label>
|
||||||
<input type="text" id="dataset-form-name" class="input" placeholder="Enter dataset name..." />
|
<input type="text" id="dataset-form-name" class="input"
|
||||||
|
placeholder="Enter dataset name..." />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dataset-form-group">
|
<div class="dataset-form-group">
|
||||||
@@ -405,7 +400,8 @@
|
|||||||
<div class="dataset-format-hint">
|
<div class="dataset-format-hint">
|
||||||
Paste your data (JSON, CSV, or TSV) or a URL. Format will be detected automatically.
|
Paste your data (JSON, CSV, or TSV) or a URL. Format will be detected automatically.
|
||||||
</div>
|
</div>
|
||||||
<textarea id="dataset-form-input" class="input textarea" placeholder="Paste data or URL here..." rows="12"></textarea>
|
<textarea id="dataset-form-input" class="input textarea"
|
||||||
|
placeholder="Paste data or URL here..." rows="12"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Detection Confirmation UI -->
|
<!-- Detection Confirmation UI -->
|
||||||
@@ -415,7 +411,8 @@
|
|||||||
<div class="detection-badges">
|
<div class="detection-badges">
|
||||||
<span class="detection-badge" id="detected-format">JSON</span>
|
<span class="detection-badge" id="detected-format">JSON</span>
|
||||||
<span class="detection-badge" id="detected-source">Inline</span>
|
<span class="detection-badge" id="detected-source">Inline</span>
|
||||||
<span class="detected-confidence high" id="detected-confidence">high confidence</span>
|
<span class="detected-confidence high" id="detected-confidence">high
|
||||||
|
confidence</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="detection-preview-label">Preview:</div>
|
<div class="detection-preview-label">Preview:</div>
|
||||||
@@ -424,14 +421,17 @@
|
|||||||
|
|
||||||
<div class="dataset-form-group">
|
<div class="dataset-form-group">
|
||||||
<label class="dataset-form-label">Comment</label>
|
<label class="dataset-form-label">Comment</label>
|
||||||
<textarea id="dataset-form-comment" class="input textarea" placeholder="Optional description..." rows="3"></textarea>
|
<textarea id="dataset-form-comment" class="input textarea"
|
||||||
|
placeholder="Optional description..." rows="3"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dataset-form-error" id="dataset-form-error"></div>
|
<div class="dataset-form-error" id="dataset-form-error"></div>
|
||||||
|
|
||||||
<div class="dataset-form-actions">
|
<div class="dataset-form-actions">
|
||||||
<button class="btn btn-modal primary" id="save-dataset-btn" title="Save this dataset">Save Dataset</button>
|
<button class="btn btn-modal primary" id="save-dataset-btn" title="Save this dataset">Save
|
||||||
<button class="btn btn-modal" id="cancel-dataset-btn" title="Cancel and return to dataset list">Cancel</button>
|
Dataset</button>
|
||||||
|
<button class="btn btn-modal" id="cancel-dataset-btn"
|
||||||
|
title="Cancel and return to dataset list">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -450,7 +450,8 @@
|
|||||||
<div style="padding: 16px;">
|
<div style="padding: 16px;">
|
||||||
<div class="dataset-form-group">
|
<div class="dataset-form-group">
|
||||||
<label class="dataset-form-label">Dataset Name *</label>
|
<label class="dataset-form-label">Dataset Name *</label>
|
||||||
<input type="text" id="extract-dataset-name" class="input" placeholder="Enter dataset name..." />
|
<input type="text" id="extract-dataset-name" class="input"
|
||||||
|
placeholder="Enter dataset name..." />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dataset-form-group">
|
<div class="dataset-form-group">
|
||||||
@@ -461,7 +462,8 @@
|
|||||||
<div class="dataset-form-error" id="extract-form-error"></div>
|
<div class="dataset-form-error" id="extract-form-error"></div>
|
||||||
|
|
||||||
<div class="dataset-form-actions">
|
<div class="dataset-form-actions">
|
||||||
<button class="btn btn-modal primary" id="extract-create-btn" title="Create dataset and update snippet reference">Create Dataset</button>
|
<button class="btn btn-modal primary" id="extract-create-btn"
|
||||||
|
title="Create dataset and update snippet reference">Create Dataset</button>
|
||||||
<button class="btn btn-modal" id="extract-cancel-btn" title="Cancel extraction">Cancel</button>
|
<button class="btn btn-modal" id="extract-cancel-btn" title="Cancel extraction">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -474,7 +476,9 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<span class="modal-title">Build Chart</span>
|
<span class="modal-title">Build Chart</span>
|
||||||
<button class="btn btn-icon" id="chart-builder-modal-close" @click="$el.closest('#chart-builder-view')._x_dataStack[0].close()" title="Close chart builder (Escape)">×</button>
|
<button class="btn btn-icon" id="chart-builder-modal-close"
|
||||||
|
@click="$el.closest('#chart-builder-view')._x_dataStack[0].close()"
|
||||||
|
title="Close chart builder (Escape)">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<!-- Chart Builder View -->
|
<!-- Chart Builder View -->
|
||||||
@@ -483,18 +487,28 @@
|
|||||||
<!-- Left Panel: Configuration -->
|
<!-- Left Panel: Configuration -->
|
||||||
<div class="chart-builder-config">
|
<div class="chart-builder-config">
|
||||||
<div class="chart-builder-header">
|
<div class="chart-builder-header">
|
||||||
<button class="btn btn-modal" id="chart-builder-back-btn" @click="close()" title="Back to dataset details">← Back to Dataset</button>
|
<button class="btn btn-modal" id="chart-builder-back-btn" @click="close()"
|
||||||
|
title="Back to dataset details">← Back to Dataset</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart-builder-section">
|
<div class="chart-builder-section">
|
||||||
<div class="mark-type-row">
|
<div class="mark-type-row">
|
||||||
<label class="chart-builder-label">Mark Type*</label>
|
<label class="chart-builder-label">Mark Type*</label>
|
||||||
<div class="mark-toggle-group">
|
<div class="mark-toggle-group">
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': markType === 'bar' }" @click="setMarkType('bar')" data-mark="bar" title="Bar chart">Bar</button>
|
<button class="btn btn-toggle small" :class="{ 'active': markType === 'bar' }"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': markType === 'line' }" @click="setMarkType('line')" data-mark="line" title="Line chart">Line</button>
|
@click="setMarkType('bar')" data-mark="bar" title="Bar chart">Bar</button>
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': markType === 'point' }" @click="setMarkType('point')" data-mark="point" title="Point chart">Point</button>
|
<button class="btn btn-toggle small" :class="{ 'active': markType === 'line' }"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': markType === 'area' }" @click="setMarkType('area')" data-mark="area" title="Area chart">Area</button>
|
@click="setMarkType('line')" data-mark="line"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': markType === 'circle' }" @click="setMarkType('circle')" data-mark="circle" title="Circle chart">Circle</button>
|
title="Line chart">Line</button>
|
||||||
|
<button class="btn btn-toggle small" :class="{ 'active': markType === 'point' }"
|
||||||
|
@click="setMarkType('point')" data-mark="point"
|
||||||
|
title="Point chart">Point</button>
|
||||||
|
<button class="btn btn-toggle small" :class="{ 'active': markType === 'area' }"
|
||||||
|
@click="setMarkType('area')" data-mark="area"
|
||||||
|
title="Area chart">Area</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': markType === 'circle' }" @click="setMarkType('circle')"
|
||||||
|
data-mark="circle" title="Circle chart">Circle</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -506,17 +520,30 @@
|
|||||||
<div class="encoding-group">
|
<div class="encoding-group">
|
||||||
<div class="encoding-row">
|
<div class="encoding-row">
|
||||||
<label class="encoding-header">X Axis</label>
|
<label class="encoding-header">X Axis</label>
|
||||||
<select id="encoding-x-field" class="input" x-model="encodings.x.field" @change="setEncodingField('x', $event.target.value)">
|
<select id="encoding-x-field" class="input" x-model="encodings.x.field"
|
||||||
|
@change="setEncodingField('x', $event.target.value)">
|
||||||
<option value="">None</option>
|
<option value="">None</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="encoding-type">
|
<div class="encoding-type">
|
||||||
<label class="encoding-type-label">Type</label>
|
<label class="encoding-type-label">Type</label>
|
||||||
<div class="type-toggle-group">
|
<div class="type-toggle-group">
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.x.type === 'quantitative' }" @click="setEncodingType('x', 'quantitative')" data-encoding="x" data-type="quantitative" title="Quantitative">Q</button>
|
<button class="btn btn-toggle small"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.x.type === 'ordinal' }" @click="setEncodingType('x', 'ordinal')" data-encoding="x" data-type="ordinal" title="Ordinal">O</button>
|
:class="{ 'active': encodings.x.type === 'quantitative' }"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.x.type === 'nominal' }" @click="setEncodingType('x', 'nominal')" data-encoding="x" data-type="nominal" title="Nominal">N</button>
|
@click="setEncodingType('x', 'quantitative')" data-encoding="x"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.x.type === 'temporal' }" @click="setEncodingType('x', 'temporal')" data-encoding="x" data-type="temporal" title="Temporal">T</button>
|
data-type="quantitative" title="Quantitative">Q</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.x.type === 'ordinal' }"
|
||||||
|
@click="setEncodingType('x', 'ordinal')" data-encoding="x"
|
||||||
|
data-type="ordinal" title="Ordinal">O</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.x.type === 'nominal' }"
|
||||||
|
@click="setEncodingType('x', 'nominal')" data-encoding="x"
|
||||||
|
data-type="nominal" title="Nominal">N</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.x.type === 'temporal' }"
|
||||||
|
@click="setEncodingType('x', 'temporal')" data-encoding="x"
|
||||||
|
data-type="temporal" title="Temporal">T</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -525,17 +552,30 @@
|
|||||||
<div class="encoding-group">
|
<div class="encoding-group">
|
||||||
<div class="encoding-row">
|
<div class="encoding-row">
|
||||||
<label class="encoding-header">Y Axis</label>
|
<label class="encoding-header">Y Axis</label>
|
||||||
<select id="encoding-y-field" class="input" x-model="encodings.y.field" @change="setEncodingField('y', $event.target.value)">
|
<select id="encoding-y-field" class="input" x-model="encodings.y.field"
|
||||||
|
@change="setEncodingField('y', $event.target.value)">
|
||||||
<option value="">None</option>
|
<option value="">None</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="encoding-type">
|
<div class="encoding-type">
|
||||||
<label class="encoding-type-label">Type</label>
|
<label class="encoding-type-label">Type</label>
|
||||||
<div class="type-toggle-group">
|
<div class="type-toggle-group">
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.y.type === 'quantitative' }" @click="setEncodingType('y', 'quantitative')" data-encoding="y" data-type="quantitative" title="Quantitative">Q</button>
|
<button class="btn btn-toggle small"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.y.type === 'ordinal' }" @click="setEncodingType('y', 'ordinal')" data-encoding="y" data-type="ordinal" title="Ordinal">O</button>
|
:class="{ 'active': encodings.y.type === 'quantitative' }"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.y.type === 'nominal' }" @click="setEncodingType('y', 'nominal')" data-encoding="y" data-type="nominal" title="Nominal">N</button>
|
@click="setEncodingType('y', 'quantitative')" data-encoding="y"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.y.type === 'temporal' }" @click="setEncodingType('y', 'temporal')" data-encoding="y" data-type="temporal" title="Temporal">T</button>
|
data-type="quantitative" title="Quantitative">Q</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.y.type === 'ordinal' }"
|
||||||
|
@click="setEncodingType('y', 'ordinal')" data-encoding="y"
|
||||||
|
data-type="ordinal" title="Ordinal">O</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.y.type === 'nominal' }"
|
||||||
|
@click="setEncodingType('y', 'nominal')" data-encoding="y"
|
||||||
|
data-type="nominal" title="Nominal">N</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.y.type === 'temporal' }"
|
||||||
|
@click="setEncodingType('y', 'temporal')" data-encoding="y"
|
||||||
|
data-type="temporal" title="Temporal">T</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -544,17 +584,30 @@
|
|||||||
<div class="encoding-group">
|
<div class="encoding-group">
|
||||||
<div class="encoding-row">
|
<div class="encoding-row">
|
||||||
<label class="encoding-header">Color</label>
|
<label class="encoding-header">Color</label>
|
||||||
<select id="encoding-color-field" class="input" x-model="encodings.color.field" @change="setEncodingField('color', $event.target.value)">
|
<select id="encoding-color-field" class="input" x-model="encodings.color.field"
|
||||||
|
@change="setEncodingField('color', $event.target.value)">
|
||||||
<option value="">None</option>
|
<option value="">None</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="encoding-type">
|
<div class="encoding-type">
|
||||||
<label class="encoding-type-label">Type</label>
|
<label class="encoding-type-label">Type</label>
|
||||||
<div class="type-toggle-group">
|
<div class="type-toggle-group">
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.color.type === 'quantitative' }" @click="setEncodingType('color', 'quantitative')" data-encoding="color" data-type="quantitative" title="Quantitative">Q</button>
|
<button class="btn btn-toggle small"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.color.type === 'ordinal' }" @click="setEncodingType('color', 'ordinal')" data-encoding="color" data-type="ordinal" title="Ordinal">O</button>
|
:class="{ 'active': encodings.color.type === 'quantitative' }"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.color.type === 'nominal' }" @click="setEncodingType('color', 'nominal')" data-encoding="color" data-type="nominal" title="Nominal">N</button>
|
@click="setEncodingType('color', 'quantitative')" data-encoding="color"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.color.type === 'temporal' }" @click="setEncodingType('color', 'temporal')" data-encoding="color" data-type="temporal" title="Temporal">T</button>
|
data-type="quantitative" title="Quantitative">Q</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.color.type === 'ordinal' }"
|
||||||
|
@click="setEncodingType('color', 'ordinal')" data-encoding="color"
|
||||||
|
data-type="ordinal" title="Ordinal">O</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.color.type === 'nominal' }"
|
||||||
|
@click="setEncodingType('color', 'nominal')" data-encoding="color"
|
||||||
|
data-type="nominal" title="Nominal">N</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.color.type === 'temporal' }"
|
||||||
|
@click="setEncodingType('color', 'temporal')" data-encoding="color"
|
||||||
|
data-type="temporal" title="Temporal">T</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -563,17 +616,30 @@
|
|||||||
<div class="encoding-group">
|
<div class="encoding-group">
|
||||||
<div class="encoding-row">
|
<div class="encoding-row">
|
||||||
<label class="encoding-header">Size</label>
|
<label class="encoding-header">Size</label>
|
||||||
<select id="encoding-size-field" class="input" x-model="encodings.size.field" @change="setEncodingField('size', $event.target.value)">
|
<select id="encoding-size-field" class="input" x-model="encodings.size.field"
|
||||||
|
@change="setEncodingField('size', $event.target.value)">
|
||||||
<option value="">None</option>
|
<option value="">None</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="encoding-type">
|
<div class="encoding-type">
|
||||||
<label class="encoding-type-label">Type</label>
|
<label class="encoding-type-label">Type</label>
|
||||||
<div class="type-toggle-group">
|
<div class="type-toggle-group">
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.size.type === 'quantitative' }" @click="setEncodingType('size', 'quantitative')" data-encoding="size" data-type="quantitative" title="Quantitative">Q</button>
|
<button class="btn btn-toggle small"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.size.type === 'ordinal' }" @click="setEncodingType('size', 'ordinal')" data-encoding="size" data-type="ordinal" title="Ordinal">O</button>
|
:class="{ 'active': encodings.size.type === 'quantitative' }"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.size.type === 'nominal' }" @click="setEncodingType('size', 'nominal')" data-encoding="size" data-type="nominal" title="Nominal">N</button>
|
@click="setEncodingType('size', 'quantitative')" data-encoding="size"
|
||||||
<button class="btn btn-toggle small" :class="{ 'active': encodings.size.type === 'temporal' }" @click="setEncodingType('size', 'temporal')" data-encoding="size" data-type="temporal" title="Temporal">T</button>
|
data-type="quantitative" title="Quantitative">Q</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.size.type === 'ordinal' }"
|
||||||
|
@click="setEncodingType('size', 'ordinal')" data-encoding="size"
|
||||||
|
data-type="ordinal" title="Ordinal">O</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.size.type === 'nominal' }"
|
||||||
|
@click="setEncodingType('size', 'nominal')" data-encoding="size"
|
||||||
|
data-type="nominal" title="Nominal">N</button>
|
||||||
|
<button class="btn btn-toggle small"
|
||||||
|
:class="{ 'active': encodings.size.type === 'temporal' }"
|
||||||
|
@click="setEncodingType('size', 'temporal')" data-encoding="size"
|
||||||
|
data-type="temporal" title="Temporal">T</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -584,11 +650,14 @@
|
|||||||
<div class="chart-dimensions-group">
|
<div class="chart-dimensions-group">
|
||||||
<div class="dimension-input-group">
|
<div class="dimension-input-group">
|
||||||
<label class="dimension-label">Width</label>
|
<label class="dimension-label">Width</label>
|
||||||
<input type="number" id="chart-width" class="input small" x-model.number="width" @input="updatePreview()" placeholder="auto" min="1" />
|
<input type="number" id="chart-width" class="input small" x-model.number="width"
|
||||||
|
@input="updatePreview()" placeholder="auto" min="1" />
|
||||||
</div>
|
</div>
|
||||||
<div class="dimension-input-group">
|
<div class="dimension-input-group">
|
||||||
<label class="dimension-label">Height</label>
|
<label class="dimension-label">Height</label>
|
||||||
<input type="number" id="chart-height" class="input small" x-model.number="height" @input="updatePreview()" placeholder="auto" min="1" />
|
<input type="number" id="chart-height" class="input small"
|
||||||
|
x-model.number="height" @input="updatePreview()" placeholder="auto"
|
||||||
|
min="1" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -596,8 +665,11 @@
|
|||||||
<div class="chart-builder-error" id="chart-builder-error"></div>
|
<div class="chart-builder-error" id="chart-builder-error"></div>
|
||||||
|
|
||||||
<div class="chart-builder-actions">
|
<div class="chart-builder-actions">
|
||||||
<button class="btn btn-modal primary" id="chart-builder-create-btn" @click="createSnippet()" :disabled="!isValid" title="Create snippet from chart">Create Snippet</button>
|
<button class="btn btn-modal primary" id="chart-builder-create-btn"
|
||||||
<button class="btn btn-modal" id="chart-builder-cancel-btn" @click="close()" title="Cancel and close">Cancel</button>
|
@click="createSnippet()" :disabled="!isValid"
|
||||||
|
title="Create snippet from chart">Create Snippet</button>
|
||||||
|
<button class="btn btn-modal" id="chart-builder-cancel-btn" @click="close()"
|
||||||
|
title="Cancel and close">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -630,13 +702,16 @@
|
|||||||
<h3 class="help-heading">About Astrolabe</h3>
|
<h3 class="help-heading">About Astrolabe</h3>
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
Astrolabe is a lightweight, browser-based snippet manager for Vega-Lite visualizations.
|
Astrolabe is a lightweight, browser-based snippet manager for Vega-Lite visualizations.
|
||||||
It's designed to help you quickly create, organize, and iterate on visualization specs without
|
It's designed to help you quickly create, organize, and iterate on visualization specs
|
||||||
|
without
|
||||||
the overhead of a full development environment.
|
the overhead of a full development environment.
|
||||||
</p>
|
</p>
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
Everything runs locally in your browser—no server, no signup, no data leaving your machine.
|
Everything runs locally in your browser—no server, no signup, no data leaving your machine.
|
||||||
Your snippets and datasets are stored using browser storage, so they persist across sessions.
|
Your snippets and datasets are stored using browser storage, so they persist across
|
||||||
As a Progressive Web App, Astrolabe works fully offline after your first visit and can be installed
|
sessions.
|
||||||
|
As a Progressive Web App, Astrolabe works fully offline after your first visit and can be
|
||||||
|
installed
|
||||||
as a standalone application.
|
as a standalone application.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
@@ -645,13 +720,19 @@
|
|||||||
<section class="help-section">
|
<section class="help-section">
|
||||||
<h3 class="help-heading">Key Features</h3>
|
<h3 class="help-heading">Key Features</h3>
|
||||||
<ul class="help-list">
|
<ul class="help-list">
|
||||||
<li><strong>Three-panel workspace</strong> — Snippet library, Monaco code editor with Vega-Lite schema validation, and live preview</li>
|
<li><strong>Three-panel workspace</strong> — Snippet library, Monaco code editor with
|
||||||
<li><strong>Draft/published workflow</strong> — Experiment safely without losing your working version</li>
|
Vega-Lite schema validation, and live preview</li>
|
||||||
<li><strong>Dataset library</strong> — Store and reuse datasets across multiple visualizations (supports JSON, CSV, TSV, TopoJSON)</li>
|
<li><strong>Draft/published workflow</strong> — Experiment safely without losing your
|
||||||
<li><strong>Offline-capable</strong> — Works without internet connection after first visit; install as standalone app</li>
|
working version</li>
|
||||||
|
<li><strong>Dataset library</strong> — Store and reuse datasets across multiple
|
||||||
|
visualizations (supports JSON, CSV, TSV, TopoJSON)</li>
|
||||||
|
<li><strong>Offline-capable</strong> — Works without internet connection after first visit;
|
||||||
|
install as standalone app</li>
|
||||||
<li><strong>Import/export</strong> — Back up your work or move it between browsers</li>
|
<li><strong>Import/export</strong> — Back up your work or move it between browsers</li>
|
||||||
<li><strong>Inline data extraction</strong> — Convert hardcoded data into reusable datasets</li>
|
<li><strong>Inline data extraction</strong> — Convert hardcoded data into reusable datasets
|
||||||
<li><strong>Search and sorting</strong> — Find snippets by name, comment, or spec content</li>
|
</li>
|
||||||
|
<li><strong>Search and sorting</strong> — Find snippets by name, comment, or spec content
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -661,19 +742,23 @@
|
|||||||
<div class="help-workflow">
|
<div class="help-workflow">
|
||||||
<div class="help-step">
|
<div class="help-step">
|
||||||
<strong>1. Create a snippet</strong>
|
<strong>1. Create a snippet</strong>
|
||||||
<p>Click the "Create New Snippet" ghost card at the top of the snippet list. A sample Vega-Lite spec loads automatically.</p>
|
<p>Click the "Create New Snippet" ghost card at the top of the snippet list. A sample
|
||||||
|
Vega-Lite spec loads automatically.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="help-step">
|
<div class="help-step">
|
||||||
<strong>2. Edit in the Draft view</strong>
|
<strong>2. Edit in the Draft view</strong>
|
||||||
<p>Changes auto-save as you type. The preview updates automatically. Your published version stays safe until you're ready.</p>
|
<p>Changes auto-save as you type. The preview updates automatically. Your published
|
||||||
|
version stays safe until you're ready.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="help-step">
|
<div class="help-step">
|
||||||
<strong>3. Publish when ready</strong>
|
<strong>3. Publish when ready</strong>
|
||||||
<p>Click "Publish" (or Cmd/Ctrl+S) to save your draft as the official version. Use "Revert" if you want to discard changes.</p>
|
<p>Click "Publish" (or Cmd/Ctrl+S) to save your draft as the official version. Use
|
||||||
|
"Revert" if you want to discard changes.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="help-step">
|
<div class="help-step">
|
||||||
<strong>4. Organize with datasets</strong>
|
<strong>4. Organize with datasets</strong>
|
||||||
<p>Open the Dataset Manager to create reusable datasets. Reference them in your specs using <code>{"data": {"name": "dataset-name"}}</code>.</p>
|
<p>Open the Dataset Manager to create reusable datasets. Reference them in your specs
|
||||||
|
using <code>{"data": {"name": "dataset-name"}}</code>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -712,14 +797,19 @@
|
|||||||
<h3 class="help-heading">Storage & Limits</h3>
|
<h3 class="help-heading">Storage & Limits</h3>
|
||||||
|
|
||||||
<div class="help-warning">
|
<div class="help-warning">
|
||||||
<strong>⚠️ Important:</strong> All data is stored in your browser's local storage. If you clear your browser cache or site data,
|
<strong>⚠️ Important:</strong> All data is stored in your browser's local storage. If you
|
||||||
your snippets and datasets will be permanently deleted. Regular exports are recommended as backups.
|
clear your browser cache or site data,
|
||||||
|
your snippets and datasets will be permanently deleted. Regular exports are recommended as
|
||||||
|
backups.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="help-list">
|
<ul class="help-list">
|
||||||
<li><strong>Snippets</strong> — Stored in localStorage with a 5 MB limit (shared across all snippets). The storage monitor shows current usage.</li>
|
<li><strong>Snippets</strong> — Stored in localStorage with a 5 MB limit (shared across all
|
||||||
<li><strong>Datasets</strong> — Stored in IndexedDB with effectively unlimited space (browser-dependent, typically 50 MB+).</li>
|
snippets). The storage monitor shows current usage.</li>
|
||||||
<li><strong>Backup</strong> — Use Import/Export to save your work as JSON files. Datasets can be exported individually from the Dataset Manager.</li>
|
<li><strong>Datasets</strong> — Stored in IndexedDB with effectively unlimited space
|
||||||
|
(browser-dependent, typically 50 MB+).</li>
|
||||||
|
<li><strong>Backup</strong> — Use Import/Export to save your work as JSON files. Datasets
|
||||||
|
can be exported individually from the Dataset Manager.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -727,12 +817,18 @@
|
|||||||
<section class="help-section">
|
<section class="help-section">
|
||||||
<h3 class="help-heading">Tips & Tricks</h3>
|
<h3 class="help-heading">Tips & Tricks</h3>
|
||||||
<ul class="help-list">
|
<ul class="help-list">
|
||||||
<li><strong>Sort snippets</strong> — Use the sort buttons to organize by Modified, Created, Name, or Size. Click a button twice to reverse the sort order (⬇ becomes ⬆).</li>
|
<li><strong>Sort snippets</strong> — Use the sort buttons to organize by Modified, Created,
|
||||||
<li><strong>Extract inline data</strong> — When editing a spec with inline data, click "Extract to Dataset" to create a reusable dataset automatically.</li>
|
Name, or Size. Click a button twice to reverse the sort order (⬇ becomes ⬆).</li>
|
||||||
<li><strong>Dataset references</strong> — Astrolabe resolves dataset references at render time, so you can freely switch between inline and referenced data.</li>
|
<li><strong>Extract inline data</strong> — When editing a spec with inline data, click
|
||||||
<li><strong>Search across specs</strong> — The search box looks inside snippet names, comments, and the spec content itself.</li>
|
"Extract to Dataset" to create a reusable dataset automatically.</li>
|
||||||
<li><strong>Linked datasets</strong> — The metadata panel shows which datasets a snippet uses, and the Dataset Manager shows which snippets reference each dataset.</li>
|
<li><strong>Dataset references</strong> — Astrolabe resolves dataset references at render
|
||||||
<li><strong>URL datasets</strong> — Reference remote data by URL. Astrolabe fetches and caches it for preview, but the URL is what gets stored.</li>
|
time, so you can freely switch between inline and referenced data.</li>
|
||||||
|
<li><strong>Search across specs</strong> — The search box looks inside snippet names,
|
||||||
|
comments, and the spec content itself.</li>
|
||||||
|
<li><strong>Linked datasets</strong> — The metadata panel shows which datasets a snippet
|
||||||
|
uses, and the Dataset Manager shows which snippets reference each dataset.</li>
|
||||||
|
<li><strong>URL datasets</strong> — Reference remote data by URL. Astrolabe fetches and
|
||||||
|
caches it for preview, but the URL is what gets stored.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -743,17 +839,28 @@
|
|||||||
<strong>Your data stays yours.</strong> Astrolabe is built with privacy as a core principle:
|
<strong>Your data stays yours.</strong> Astrolabe is built with privacy as a core principle:
|
||||||
</p>
|
</p>
|
||||||
<ul class="help-list">
|
<ul class="help-list">
|
||||||
<li><strong>Local-first architecture</strong> — All snippets and datasets are stored in your browser (localStorage and IndexedDB). Nothing is sent to any server.</li>
|
<li><strong>Local-first architecture</strong> — All snippets and datasets are stored in your
|
||||||
<li><strong>No accounts, no signup</strong> — There's no authentication system, no user profiles, no cloud sync. Your work exists only on your machine.</li>
|
browser (localStorage and IndexedDB). Nothing is sent to any server.</li>
|
||||||
<li><strong>No cookies</strong> — Astrolabe doesn't use cookies or any persistent tracking identifiers.</li>
|
<li><strong>No accounts, no signup</strong> — There's no authentication system, no user
|
||||||
<li><strong>Privacy-friendly analytics</strong> — We use GoatCounter (privacy-focused, GDPR-compliant) to track basic usage patterns like "snippet created" or "dataset exported." We collect <strong>zero personal information</strong>: no snippet names, no dataset content, no IP addresses, no user identifiers. Just aggregate counts to understand which features are used.</li>
|
profiles, no cloud sync. Your work exists only on your machine.</li>
|
||||||
<li><strong>Data portability</strong> — Export all your snippets and datasets anytime as standard JSON/CSV/TSV files. No vendor lock-in.</li>
|
<li><strong>No cookies</strong> — Astrolabe doesn't use cookies or any persistent tracking
|
||||||
|
identifiers.</li>
|
||||||
|
<li><strong>Privacy-friendly analytics</strong> — We use GoatCounter (privacy-focused,
|
||||||
|
GDPR-compliant) to track basic usage patterns like "snippet created" or "dataset
|
||||||
|
exported." We collect <strong>zero personal information</strong>: no snippet names, no
|
||||||
|
dataset content, no IP addresses, no user identifiers. Just aggregate counts to
|
||||||
|
understand which features are used.</li>
|
||||||
|
<li><strong>Data portability</strong> — Export all your snippets and datasets anytime as
|
||||||
|
standard JSON/CSV/TSV files. No vendor lock-in.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
<strong>What analytics we collect:</strong> Action types (e.g., "snippet-create", "dataset-export"), generic metadata (e.g., format types like JSON/CSV, counts like "5 snippets"). That's it.
|
<strong>What analytics we collect:</strong> Action types (e.g., "snippet-create",
|
||||||
|
"dataset-export"), generic metadata (e.g., format types like JSON/CSV, counts like "5
|
||||||
|
snippets"). That's it.
|
||||||
</p>
|
</p>
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
<strong>What we DON'T collect:</strong> Snippet names, dataset names, actual data content, URLs, email addresses, or any personally identifiable information.
|
<strong>What we DON'T collect:</strong> Snippet names, dataset names, actual data content,
|
||||||
|
URLs, email addresses, or any personally identifiable information.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -773,15 +880,18 @@
|
|||||||
<section class="help-section">
|
<section class="help-section">
|
||||||
<h3 class="help-heading">Why Donate?</h3>
|
<h3 class="help-heading">Why Donate?</h3>
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
If you are reading this, you probably found Astrolabe to be useful enough to support its creators.
|
If you are reading this, you probably found Astrolabe to be useful enough to support its
|
||||||
|
creators.
|
||||||
It is a free open-source project built in Kyiv, Ukraine.
|
It is a free open-source project built in Kyiv, Ukraine.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
This passion project of mine is possible because my relatives, friends, and colleagues took arms and joined Armed Forces
|
This passion project of mine is possible because my relatives, friends, and colleagues took
|
||||||
|
arms and joined Armed Forces
|
||||||
to defend their country and loved ones against Russian invasion.
|
to defend their country and loved ones against Russian invasion.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
I feel deep gratitude to them, so I will humbly ask you to redirect your donations to the foundations - you'll find the links below.
|
I feel deep gratitude to them, so I will humbly ask you to redirect your donations to the
|
||||||
|
foundations - you'll find the links below.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
</section>
|
</section>
|
||||||
@@ -789,31 +899,39 @@
|
|||||||
<section class="help-section">
|
<section class="help-section">
|
||||||
<h3 class="help-heading">Where to Donate</h3>
|
<h3 class="help-heading">Where to Donate</h3>
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
<ul class="help-list">
|
<ul class="help-list">
|
||||||
<li>
|
<li>
|
||||||
|
|
||||||
<strong><a href="https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi">
|
<strong><a
|
||||||
National Bank of Ukraine's special account for Ukraine's Armed Forces
|
href="https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi">
|
||||||
</a> - Direct government channel</strong>
|
National Bank of Ukraine's special account for Ukraine's Armed Forces
|
||||||
</li>
|
</a> - Direct government channel</strong>
|
||||||
<li>
|
</li>
|
||||||
<strong><a href="https://savelife.in.ua/en/donate-en/#donate-army-card-once">
|
<li>
|
||||||
Come Back Alive Foundation</a></strong> - One of the biggest and most trusted. They've been around since 2014 and share <a href="https://savelife.in.ua/en/reporting-en/">detailed reports</a> of where money goes.
|
<strong><a href="https://savelife.in.ua/en/donate-en/#donate-army-card-once">
|
||||||
</li>
|
Come Back Alive Foundation</a></strong> - One of the biggest and most
|
||||||
<li>
|
trusted. They've been around since 2014 and share <a
|
||||||
<strong><a href="https://macpaw.foundation/">
|
href="https://savelife.in.ua/en/reporting-en/">detailed reports</a> of where
|
||||||
MacPaw Foundation</a></strong> - Founded by MacPaw (where I work). Started in 2016, shifted focus in 2022 to support the Defence Forces.
|
money goes.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong><a href="https://foundation.kse.ua/en/humanitarian-projects/">
|
<strong><a href="https://macpaw.foundation/">
|
||||||
KSE Foundation</a></strong>. Kyiv School of Economics (where I teach). Focuses on both education and humanitarian support for people and defenders
|
MacPaw Foundation</a></strong> - Founded by MacPaw (where I work). Started
|
||||||
</li>
|
in 2016, shifted focus in 2022 to support the Defence Forces.
|
||||||
<li>
|
</li>
|
||||||
<strong><a href="https://standforukraine.com/">
|
<li>
|
||||||
Stand for Ukraine</a></strong> - Not a foundation, but an aggregator of reliable organizations.
|
<strong><a href="https://foundation.kse.ua/en/humanitarian-projects/">
|
||||||
The list of fundraisers goes beyond military and covers recovery of veterans & victims of war, shelter for the refugees and many more.
|
KSE Foundation</a></strong>. Kyiv School of Economics (where I teach).
|
||||||
</li>
|
Focuses on both education and humanitarian support for people and defenders
|
||||||
</ul>
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong><a href="https://standforukraine.com/">
|
||||||
|
Stand for Ukraine</a></strong> - Not a foundation, but an aggregator of
|
||||||
|
reliable organizations.
|
||||||
|
The list of fundraisers goes beyond military and covers recovery of veterans &
|
||||||
|
victims of war, shelter for the refugees and many more.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -875,8 +993,10 @@
|
|||||||
<div class="settings-item">
|
<div class="settings-item">
|
||||||
<label class="settings-label" for="setting-font-size">Font Size</label>
|
<label class="settings-label" for="setting-font-size">Font Size</label>
|
||||||
<div class="settings-control">
|
<div class="settings-control">
|
||||||
<input type="range" id="setting-font-size" min="10" max="18" step="1" class="settings-slider" x-model.number="fontSize" />
|
<input type="range" id="setting-font-size" min="10" max="18" step="1"
|
||||||
<span class="settings-value" id="setting-font-size-value" x-text="fontSize + 'px'"></span>
|
class="settings-slider" x-model.number="fontSize" />
|
||||||
|
<span class="settings-value" id="setting-font-size-value"
|
||||||
|
x-text="fontSize + 'px'"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -904,21 +1024,24 @@
|
|||||||
|
|
||||||
<div class="settings-item">
|
<div class="settings-item">
|
||||||
<label class="settings-label">
|
<label class="settings-label">
|
||||||
<input type="checkbox" id="setting-minimap" class="settings-checkbox" x-model="minimap" />
|
<input type="checkbox" id="setting-minimap" class="settings-checkbox"
|
||||||
|
x-model="minimap" />
|
||||||
Show minimap
|
Show minimap
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-item">
|
<div class="settings-item">
|
||||||
<label class="settings-label">
|
<label class="settings-label">
|
||||||
<input type="checkbox" id="setting-word-wrap" class="settings-checkbox" x-model="wordWrap" />
|
<input type="checkbox" id="setting-word-wrap" class="settings-checkbox"
|
||||||
|
x-model="wordWrap" />
|
||||||
Enable word wrap
|
Enable word wrap
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-item">
|
<div class="settings-item">
|
||||||
<label class="settings-label">
|
<label class="settings-label">
|
||||||
<input type="checkbox" id="setting-line-numbers" class="settings-checkbox" x-model="lineNumbers" />
|
<input type="checkbox" id="setting-line-numbers" class="settings-checkbox"
|
||||||
|
x-model="lineNumbers" />
|
||||||
Show line numbers
|
Show line numbers
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -931,8 +1054,10 @@
|
|||||||
<div class="settings-item">
|
<div class="settings-item">
|
||||||
<label class="settings-label" for="setting-render-debounce">Render Delay</label>
|
<label class="settings-label" for="setting-render-debounce">Render Delay</label>
|
||||||
<div class="settings-control">
|
<div class="settings-control">
|
||||||
<input type="range" id="setting-render-debounce" min="300" max="3000" step="100" class="settings-slider" x-model.number="renderDebounce" />
|
<input type="range" id="setting-render-debounce" min="300" max="3000" step="100"
|
||||||
<span class="settings-value" id="setting-render-debounce-value" x-text="renderDebounce + 'ms'"></span>
|
class="settings-slider" x-model.number="renderDebounce" />
|
||||||
|
<span class="settings-value" id="setting-render-debounce-value"
|
||||||
|
x-text="renderDebounce + 'ms'"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-hint">Delay before visualization updates while typing</div>
|
<div class="settings-hint">Delay before visualization updates while typing</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -957,29 +1082,24 @@
|
|||||||
<div class="settings-item" id="custom-date-format-item" x-show="showCustomDateFormat">
|
<div class="settings-item" id="custom-date-format-item" x-show="showCustomDateFormat">
|
||||||
<label class="settings-label" for="setting-custom-date-format">Custom Format</label>
|
<label class="settings-label" for="setting-custom-date-format">Custom Format</label>
|
||||||
<div class="settings-control">
|
<div class="settings-control">
|
||||||
<input type="text" id="setting-custom-date-format" class="settings-input" placeholder="yyyy-MM-dd HH:mm" x-model="customDateFormat" />
|
<input type="text" id="setting-custom-date-format" class="settings-input"
|
||||||
|
placeholder="yyyy-MM-dd HH:mm" x-model="customDateFormat" />
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-hint">
|
<div class="settings-hint">
|
||||||
Tokens: yyyy (year), MM (month), dd (day), HH (24h), hh (12h), mm (min), ss (sec), a/A (am/pm)
|
Tokens: yyyy (year), MM (month), dd (day), HH (24h), hh (12h), mm (min), ss (sec), a/A
|
||||||
|
(am/pm)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div class="settings-actions">
|
<div class="settings-actions">
|
||||||
<button class="btn btn-modal primary"
|
<button class="btn btn-modal primary" id="settings-apply-btn" @click="apply()"
|
||||||
id="settings-apply-btn"
|
:disabled="!isDirty" title="Apply and save settings">Apply</button>
|
||||||
@click="apply()"
|
<button class="btn btn-modal" id="settings-reset-btn" @click="reset()"
|
||||||
:disabled="!isDirty"
|
title="Reset to default settings">Reset to Defaults</button>
|
||||||
title="Apply and save settings">Apply</button>
|
<button class="btn btn-modal" id="settings-cancel-btn" @click="cancel()"
|
||||||
<button class="btn btn-modal"
|
title="Cancel without saving">Cancel</button>
|
||||||
id="settings-reset-btn"
|
|
||||||
@click="reset()"
|
|
||||||
title="Reset to default settings">Reset to Defaults</button>
|
|
||||||
<button class="btn btn-modal"
|
|
||||||
id="settings-cancel-btn"
|
|
||||||
@click="cancel()"
|
|
||||||
title="Cancel without saving">Cancel</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -998,8 +1118,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="src/js/user-settings.js"></script>
|
<script src="src/js/user-settings.js"></script>
|
||||||
<script src="src/js/config.js"></script>
|
|
||||||
<script src="src/js/generic-storage-ui.js"></script>
|
<script src="src/js/generic-storage-ui.js"></script>
|
||||||
|
<script src="src/js/config.js"></script>
|
||||||
<script src="src/js/snippet-manager.js"></script>
|
<script src="src/js/snippet-manager.js"></script>
|
||||||
<script src="src/js/dataset-manager.js"></script>
|
<script src="src/js/dataset-manager.js"></script>
|
||||||
<script src="src/js/chart-builder.js"></script>
|
<script src="src/js/chart-builder.js"></script>
|
||||||
@@ -1008,8 +1128,7 @@
|
|||||||
<script src="src/js/app.js"></script>
|
<script src="src/js/app.js"></script>
|
||||||
|
|
||||||
<!-- GoatCounter Analytics -->
|
<!-- GoatCounter Analytics -->
|
||||||
<script data-goatcounter="https://astrolabe.goatcounter.com/count"
|
<script data-goatcounter="https://astrolabe.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>
|
||||||
async src="//gc.zgo.at/count.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -303,20 +303,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// View mode toggle buttons (now handled by Alpine.js in index.html)
|
|
||||||
|
|
||||||
// Preview fit mode buttons
|
|
||||||
document.getElementById('preview-fit-default').addEventListener('click', () => {
|
|
||||||
setPreviewFitMode('default');
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('preview-fit-width').addEventListener('click', () => {
|
|
||||||
setPreviewFitMode('width');
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('preview-fit-full').addEventListener('click', () => {
|
|
||||||
setPreviewFitMode('full');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Publish and Revert buttons
|
// Publish and Revert buttons
|
||||||
document.getElementById('publish-btn').addEventListener('click', publishDraft);
|
document.getElementById('publish-btn').addEventListener('click', publishDraft);
|
||||||
|
|||||||
@@ -187,6 +187,11 @@ document.addEventListener('alpine:init', () => {
|
|||||||
return icons[type] || icons.info;
|
return icons[type] || icons.info;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Preview panel fit mode store
|
||||||
|
Alpine.store('preview', {
|
||||||
|
fitMode: 'default' // 'default' | 'width' | 'full'
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toast Notification System (now backed by Alpine store)
|
// Toast Notification System (now backed by Alpine store)
|
||||||
@@ -327,4 +332,3 @@ const sampleSpec = {
|
|||||||
"y": { "field": "value", "type": "quantitative" }
|
"y": { "field": "value", "type": "quantitative" }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -194,15 +194,14 @@ function updateRenderDebounce(newDebounce) {
|
|||||||
function setPreviewFitMode(mode) {
|
function setPreviewFitMode(mode) {
|
||||||
previewFitMode = mode;
|
previewFitMode = mode;
|
||||||
|
|
||||||
// Update button states
|
|
||||||
document.getElementById('preview-fit-default').classList.toggle('active', mode === 'default');
|
// Sync with Alpine store if available
|
||||||
document.getElementById('preview-fit-width').classList.toggle('active', mode === 'width');
|
if (typeof Alpine !== 'undefined' && Alpine.store('preview')) {
|
||||||
document.getElementById('preview-fit-full').classList.toggle('active', mode === 'full');
|
Alpine.store('preview').fitMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
// Save to settings
|
// Save to settings
|
||||||
if (typeof updateSetting === 'function') {
|
updateSetting('ui.previewFitMode', mode);
|
||||||
updateSetting('ui.previewFitMode', mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-render with new fit mode
|
// Re-render with new fit mode
|
||||||
renderVisualization();
|
renderVisualization();
|
||||||
|
|||||||
Reference in New Issue
Block a user