feat: improve editor layout
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 226 KiB |
@@ -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>
|
||||
<!-- 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>
|
||||
<button class="btn btn-primary btn-full-width" id="editor-add-layer">➕ Add Layer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layers List -->
|
||||
<div class="editor-section-compact editor-layers-section">
|
||||
<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 -->
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user