feat: improve editor layout

This commit is contained in:
2025-10-12 17:02:47 +02:00
parent 01350ac233
commit 9d5d0e4d67
4 changed files with 267 additions and 86 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 226 KiB

View File

@@ -90,61 +90,80 @@
<div class="editor-layout-modern">
<!-- Left Panel: Preset Info & Layers -->
<div class="editor-left-panel">
<!-- Preset Info -->
<div class="editor-section-compact">
<h3 class="section-title-compact">Preset Info</h3>
<!-- Preset -->
<div class="editor-section-compact editor-preset-section">
<div class="preset-header">
<h3 class="section-title-compact">Preset</h3>
<div class="preset-header-buttons">
<button class="btn btn-icon" id="editor-new-preset" title="New Preset">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14,2 14,8 20,8"/>
<line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/>
<polyline points="10,9 9,9 8,9"/>
</svg>
</button>
<button class="btn btn-icon" id="editor-save-preset" title="Save Preset">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17,21 17,13 7,13 7,21"/>
<polyline points="7,3 7,8 15,8"/>
</svg>
</button>
<button class="btn btn-icon" id="editor-delete-preset" title="Delete Preset">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3,6 5,6 21,6"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<line x1="10" y1="11" x2="10" y2="17"/>
<line x1="14" y1="11" x2="14" y2="17"/>
</svg>
</button>
<button class="btn btn-icon" id="editor-export-json" title="Export JSON">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7,10 12,15 17,10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>
</button>
<label class="btn btn-icon" title="Import JSON">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="17,8 12,3 7,8"/>
<line x1="12" y1="3" x2="12" y2="15"/>
</svg>
<input type="file" id="editor-import-json" accept=".json" style="display: none;" />
</label>
</div>
</div>
<!-- Preset Name -->
<div class="editor-input-wrapper">
<label>Name</label>
<input type="text" id="editor-preset-name" value="New Custom Preset" />
</div>
<div class="editor-input-wrapper">
<label>Description</label>
<textarea id="editor-preset-desc" rows="3">A custom configurable preset</textarea>
</div>
</div>
<!-- Add Layer Section -->
<div class="editor-section-compact">
<h3 class="section-title-compact">Add Layer</h3>
<div class="editor-input-wrapper">
<label>Layer Type</label>
<select id="editor-layer-type">
<option value="shape">Shape</option>
<option value="pattern">Pattern</option>
</select>
<!-- Preset Actions -->
<div class="preset-actions">
<div class="preset-actions-row">
<select class="preset-select" id="editor-load-preset">
<option value="">Load saved preset...</option>
</select>
</div>
</div>
<button class="btn btn-primary btn-full-width" id="editor-add-layer"> Add Layer</button>
</div>
<!-- Layers List -->
<div class="editor-section-compact editor-layers-section">
<h3 class="section-title-compact">Layers</h3>
<div class="section-title-with-button">
<h3 class="section-title-compact">Layers</h3>
<button class="btn btn-primary btn-small" id="editor-add-layer"></button>
</div>
<div id="editor-layer-list" class="editor-layer-list-expandable">
<p class="editor-empty-state">No layers yet. Click "Add Layer" to get started!</p>
</div>
</div>
<!-- Manage Section -->
<div class="editor-section-compact">
<h3 class="section-title-compact">Manage</h3>
<div class="editor-actions">
<button class="btn btn-primary" id="editor-new-preset">📄 New</button>
<button class="btn btn-success" id="editor-save-preset">💾 Save</button>
<button class="btn btn-danger" id="editor-delete-preset">🗑️ Delete</button>
</div>
<div class="editor-actions">
<select class="preset-select" id="editor-load-preset">
<option value="">Load saved preset...</option>
</select>
</div>
<div class="editor-actions">
<button class="btn btn-secondary" id="editor-export-json">📤 Export JSON</button>
<label class="btn btn-secondary" style="margin: 0; text-align: center;">
📥 Import JSON
<input type="file" id="editor-import-json" accept=".json" style="display: none;" />
</label>
</div>
</div>
</div>
<!-- Right Panel: Preview & Controls -->

View File

@@ -67,7 +67,6 @@ class PresetEditor {
setupEventListeners() {
// Preset metadata
const nameInput = document.getElementById('editor-preset-name');
const descInput = document.getElementById('editor-preset-desc');
if (nameInput) {
nameInput.addEventListener('input', (e) => {
@@ -75,26 +74,12 @@ class PresetEditor {
});
}
if (descInput) {
descInput.addEventListener('input', (e) => {
this.configuration.description = e.target.value;
});
}
// Add layer button
const addLayerBtn = document.getElementById('editor-add-layer');
if (addLayerBtn) {
addLayerBtn.addEventListener('click', () => this.addLayer());
}
// Building block type selector
const typeSelector = document.getElementById('editor-layer-type');
if (typeSelector) {
typeSelector.addEventListener('change', (e) => {
this.showLayerTypeOptions(e.target.value);
});
}
// Save/Load buttons
const saveBtn = document.getElementById('editor-save-preset');
if (saveBtn) {
@@ -157,14 +142,8 @@ class PresetEditor {
}
addLayer() {
const layerType = document.getElementById('editor-layer-type')?.value || 'shape';
let layer;
if (layerType === 'pattern') {
layer = this.createPatternLayer();
} else {
layer = this.createShapeLayer();
}
// Default to shape layer type
const layer = this.createShapeLayer();
this.configuration.layers.push(layer);
@@ -464,6 +443,14 @@ class PresetEditor {
}
renderShapeEditor(container, layer, index) {
// Layer type selector
this.addSelect(container, 'Layer Type', layer.type,
['shape', 'pattern'],
(value) => {
this.changeLayerType(index, value);
}
);
// Shape type
this.addSelect(container, 'Shape', layer.shape,
['circle', 'rectangle', 'triangle', 'blob', 'point', 'line'],
@@ -517,6 +504,14 @@ class PresetEditor {
}
renderPatternEditor(container, layer, index) {
// Layer type selector
this.addSelect(container, 'Layer Type', layer.type,
['shape', 'pattern'],
(value) => {
this.changeLayerType(index, value);
}
);
// Pattern type
this.addSelect(container, 'Pattern', layer.pattern,
['trail', 'radial', 'spiral'],
@@ -774,6 +769,31 @@ class PresetEditor {
this.renderLayerList();
}
changeLayerType(index, newType) {
const layer = this.configuration.layers[index];
if (layer.type === newType) return;
// Create a new layer of the specified type
let newLayer;
if (newType === 'pattern') {
newLayer = this.createPatternLayer();
} else {
newLayer = this.createShapeLayer();
}
// Preserve some properties if they exist
if (layer.intensity !== undefined) {
newLayer.intensity = layer.intensity;
}
// Replace the layer
this.configuration.layers[index] = newLayer;
this.renderLayerList();
this.refreshPreviewIfActive();
}
deleteLayer(index) {
if (confirm('Delete this layer?')) {
this.configuration.layers.splice(index, 1);
@@ -808,16 +828,11 @@ class PresetEditor {
// Update UI
const nameInput = document.getElementById('editor-preset-name');
const descInput = document.getElementById('editor-preset-desc');
if (nameInput) {
nameInput.value = this.configuration.name;
}
if (descInput) {
descInput.value = this.configuration.description;
}
// Clear and re-render layer list
this.renderLayerList();
@@ -850,7 +865,6 @@ class PresetEditor {
this.configuration = JSON.parse(JSON.stringify(preset));
document.getElementById('editor-preset-name').value = this.configuration.name;
document.getElementById('editor-preset-desc').value = this.configuration.description;
this.renderLayerList();
this.refreshPreviewIfActive();
@@ -868,7 +882,6 @@ class PresetEditor {
this.configuration = this.getDefaultConfiguration();
document.getElementById('editor-preset-name').value = this.configuration.name;
document.getElementById('editor-preset-desc').value = this.configuration.description;
this.loadSavedPresets();
this.renderLayerList();
@@ -924,7 +937,6 @@ class PresetEditor {
this.configuration = imported;
document.getElementById('editor-preset-name').value = this.configuration.name;
document.getElementById('editor-preset-desc').value = this.configuration.description;
this.renderLayerList();
this.refreshPreviewIfActive();

View File

@@ -955,23 +955,23 @@ body {
}
.btn-primary {
background: linear-gradient(135deg, var(--accent-primary) 0%, #22c55e 100%);
background: linear-gradient(135deg, rgba(74, 222, 128, 0.8) 0%, rgba(34, 197, 94, 0.8) 100%);
color: white;
border-color: var(--accent-primary);
box-shadow: 0 2px 8px rgba(74, 222, 128, 0.25);
border-color: rgba(74, 222, 128, 0.6);
box-shadow: 0 2px 8px rgba(74, 222, 128, 0.15);
}
.btn-primary:hover {
background: linear-gradient(135deg, #22c55e 0%, var(--accent-primary) 100%);
border-color: #22c55e;
background: linear-gradient(135deg, rgba(34, 197, 94, 0.9) 0%, rgba(74, 222, 128, 0.9) 100%);
border-color: rgba(34, 197, 94, 0.8);
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(74, 222, 128, 0.35);
transform: translateY(-1px);
box-shadow: 0 3px 10px rgba(74, 222, 128, 0.2);
}
.btn-primary:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(74, 222, 128, 0.25);
box-shadow: 0 1px 4px rgba(74, 222, 128, 0.15);
}
.btn-stop {
@@ -1004,15 +1004,18 @@ body {
}
.btn-secondary {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%);
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.04) 100%);
color: var(--text-secondary);
border-color: var(--border-secondary);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.btn-secondary:hover {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
background: linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.08) 100%);
color: var(--text-primary);
border-color: var(--border-tertiary);
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.btn-small {
@@ -1806,6 +1809,17 @@ body {
opacity: 0.85;
}
.section-title-with-button {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.65rem;
}
.section-title-with-button .section-title-compact {
margin-bottom: 0;
}
.editor-section {
margin-bottom: 1.5rem;
}
@@ -2012,6 +2026,132 @@ body {
margin-top: 0.15rem;
}
.editor-preset-section {
background: var(--bg-secondary);
border: 1px solid var(--border-primary);
border-radius: 6px;
padding: 0.75rem;
margin-bottom: 0.75rem;
}
.preset-actions {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: 0.75rem;
}
.preset-actions-row {
display: flex;
gap: 0.375rem;
align-items: center;
}
.preset-actions-row:first-child {
justify-content: stretch;
}
.preset-actions-row .preset-select {
flex: 1;
margin: 0;
}
.editor-preset-section .editor-input-wrapper {
margin-bottom: 0.5rem;
}
.editor-preset-section .editor-input-wrapper input {
padding: 0.5rem 0.75rem;
font-size: 0.85rem;
min-height: 2rem;
}
.editor-preset-section .section-title-compact {
margin-bottom: 0.5rem;
font-size: 0.8rem;
}
.preset-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.preset-header .section-title-compact {
margin-bottom: 0;
}
.preset-header-buttons {
display: flex;
gap: 0.25rem;
}
.btn-icon {
padding: 0.5rem;
min-width: 2.5rem;
width: 2.5rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-tertiary);
border: 1px solid var(--border-secondary);
border-radius: 6px;
color: var(--text-secondary);
transition: all 0.2s ease;
cursor: pointer;
position: relative;
overflow: hidden;
}
.btn-icon:hover {
background: var(--bg-hover);
border-color: var(--border-hover);
color: var(--text-primary);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.btn-icon:active {
transform: translateY(0);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.btn-icon svg {
transition: all 0.2s ease;
}
.btn-icon:hover svg {
transform: scale(1.05);
}
.btn-icon:active svg {
transform: scale(0.95);
}
/* Icon button color variations */
.btn-icon[id="editor-new-preset"]:hover {
border-color: rgba(74, 222, 128, 0.4);
color: var(--accent-primary);
}
.btn-icon[id="editor-save-preset"]:hover {
border-color: rgba(76, 175, 80, 0.4);
color: var(--accent-success);
}
.btn-icon[id="editor-delete-preset"]:hover {
border-color: rgba(248, 113, 113, 0.4);
color: var(--accent-error);
}
.btn-icon[id="editor-export-json"]:hover,
.btn-icon[for="editor-import-json"]:hover {
border-color: rgba(96, 165, 250, 0.4);
color: var(--accent-secondary);
}
.editor-layer-buttons {
display: flex;
gap: 0.3rem;
@@ -2103,21 +2243,31 @@ body {
/* Button Variants */
.btn-success {
background: linear-gradient(135deg, var(--accent-success) 0%, #45a049 100%);
background: linear-gradient(135deg, rgba(76, 175, 80, 0.8) 0%, rgba(69, 160, 73, 0.8) 100%);
color: white;
border-color: rgba(76, 175, 80, 0.6);
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.15);
}
.btn-success:hover {
background: linear-gradient(135deg, #45a049 0%, #3d8b40 100%);
background: linear-gradient(135deg, rgba(69, 160, 73, 0.9) 0%, rgba(61, 139, 64, 0.9) 100%);
border-color: rgba(69, 160, 73, 0.8);
transform: translateY(-1px);
box-shadow: 0 3px 10px rgba(76, 175, 80, 0.2);
}
.btn-danger {
background: linear-gradient(135deg, var(--accent-error) 0%, #dc2626 100%);
background: linear-gradient(135deg, rgba(248, 113, 113, 0.8) 0%, rgba(220, 38, 38, 0.8) 100%);
color: white;
border-color: rgba(248, 113, 113, 0.6);
box-shadow: 0 2px 8px rgba(248, 113, 113, 0.15);
}
.btn-danger:hover {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
background: linear-gradient(135deg, rgba(220, 38, 38, 0.9) 0%, rgba(185, 28, 28, 0.9) 100%);
border-color: rgba(220, 38, 38, 0.8);
transform: translateY(-1px);
box-shadow: 0 3px 10px rgba(248, 113, 113, 0.2);
}
/* Notifications */