feat: add gradient entity modal and fix color picker clipping

Add full gradient editor modal with name, description, visual stop
editor, tags, and dirty checking. Gradient editor now supports ID
prefix to avoid DOM conflicts between CSS editor and standalone modal.

Fix color picker popover clipped by template-card overflow:hidden.
Fix gradient canvas not sizing correctly in standalone modal.
This commit is contained in:
2026-03-24 13:58:51 +03:00
parent 227b82f522
commit c0d0d839dc
14 changed files with 465 additions and 161 deletions

View File

@@ -180,6 +180,7 @@
{% include 'modals/device-settings.html' %}
{% include 'modals/target-editor.html' %}
{% include 'modals/css-editor.html' %}
{% include 'modals/gradient-editor.html' %}
{% include 'modals/test-css-source.html' %}
{% include 'modals/notification-history.html' %}
{% include 'modals/kc-editor.html' %}

View File

@@ -212,16 +212,6 @@
</select>
</div>
<div id="css-editor-effect-custom-palette-group" class="form-group" style="display:none">
<div class="label-row">
<label for="css-editor-effect-custom-palette" data-i18n="color_strip.effect.custom_palette">Custom Palette:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="color_strip.effect.custom_palette.hint">JSON array of [position, R, G, B] stops.</small>
<textarea id="css-editor-effect-custom-palette" rows="3"
placeholder='[[0,0,0,0],[0.5,255,0,0],[1,255,255,0]]'></textarea>
</div>
<div id="css-editor-effect-color-group" class="form-group" style="display:none">
<div class="label-row">
<label for="css-editor-effect-color" data-i18n="color_strip.effect.color">Color:</label>

View File

@@ -0,0 +1,61 @@
<!-- Gradient entity editor modal -->
<div id="gradient-editor-modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="gradient-editor-title">
<div class="modal-content">
<div class="modal-header">
<h2 id="gradient-editor-title" data-i18n="gradient.add">Add Gradient</h2>
<button class="modal-close-btn" onclick="closeGradientEditor()" data-i18n-aria-label="aria.close">&times;</button>
</div>
<div class="modal-body">
<input type="hidden" id="gradient-editor-id">
<div id="gradient-editor-error" class="modal-error" style="display:none"></div>
<!-- Name + Tags (same form-group, matching other entities) -->
<div class="form-group">
<div class="label-row">
<label for="gradient-editor-name" data-i18n="gradient.name">Name:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="gradient.name.hint">A descriptive name for this gradient.</small>
<input type="text" id="gradient-editor-name" required>
<div id="gradient-editor-tags-container"></div>
</div>
<!-- Description -->
<div class="form-group">
<div class="label-row">
<label for="gradient-editor-description" data-i18n="gradient.description">Description:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="gradient.description.hint">Optional description for this gradient.</small>
<input type="text" id="gradient-editor-description" maxlength="500">
</div>
<!-- Gradient preview + markers (unique IDs prefixed ge-) -->
<div class="form-group">
<div class="label-row">
<label data-i18n="color_strip.gradient.preview">Gradient:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="color_strip.gradient.preview.hint">Visual preview. Click the marker track below to add a stop. Drag markers to reposition.</small>
<div class="gradient-editor">
<canvas id="ge-gradient-canvas" height="44"></canvas>
<div id="ge-gradient-markers-track" class="gradient-markers-track"></div>
</div>
</div>
<!-- Color stops list -->
<div class="form-group">
<div class="label-row">
<label data-i18n="color_strip.gradient.stops">Color Stops:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="color_strip.gradient.stops.hint">Each stop defines a color at a relative position (0.0 = start, 1.0 = end). The ↔ button adds a right-side color to create a hard edge at that stop.</small>
<div id="ge-gradient-stops-list"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-icon btn-secondary" onclick="closeGradientEditor()" title="Cancel" data-i18n-title="settings.button.cancel" data-i18n-aria-label="aria.cancel">&#x2715;</button>
<button class="btn btn-icon btn-primary" onclick="saveGradientEntity()" title="Save" data-i18n-title="settings.button.save" data-i18n-aria-label="aria.save">&#x2713;</button>
</div>
</div>
</div>