Add tags to all entity types with chip-based input and autocomplete
- Add `tags: List[str]` field to all 13 entity types (devices, output targets, CSS sources, picture sources, audio sources, value sources, sync clocks, automations, scene presets, capture/audio/PP/pattern templates) - Update all stores, schemas, and route handlers for tag CRUD - Add GET /api/v1/tags endpoint aggregating unique tags across all stores - Create TagInput component with chip display, autocomplete dropdown, keyboard navigation, and API-backed suggestions - Display tag chips on all entity cards (searchable via existing text filter) - Add tag input to all 14 editor modals with dirty check support - Add CSS styles and i18n keys (en/ru/zh) for tag UI - Also includes code review fixes: thread safety, perf, store dedup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,15 @@
|
||||
<small class="input-hint" style="display:none" data-i18n="audio_source.description.hint">Optional notes about this audio source</small>
|
||||
<input type="text" id="audio-source-description" data-i18n-placeholder="audio_source.description.placeholder" placeholder="Describe this audio source...">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</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="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="audio-source-tags-container"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -34,6 +34,15 @@
|
||||
<div id="audio-engine-config-fields"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="audio-template-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="audio-template-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -85,6 +85,15 @@
|
||||
<select id="automation-fallback-scene-id"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="automation-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="automation-editor-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -34,6 +34,15 @@
|
||||
<div id="engine-config-fields"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="capture-template-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="template-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -576,6 +576,15 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="css-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="css-editor-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -136,6 +136,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="device-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="settings-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -80,6 +80,15 @@
|
||||
<input type="range" id="kc-editor-smoothing" min="0.0" max="1.0" step="0.05" value="0.3" oninput="document.getElementById('kc-editor-smoothing-value').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="kc-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="kc-editor-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -56,6 +56,15 @@
|
||||
<div id="pattern-rect-list" class="pattern-rect-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="pattern-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="pattern-template-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -28,6 +28,15 @@
|
||||
<input type="text" id="pp-template-description" data-i18n-placeholder="postprocessing.description_placeholder" placeholder="Describe this template...">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="pp-template-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="pp-template-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,15 @@
|
||||
<button type="button" id="scene-target-add-btn" class="btn btn-sm btn-secondary" onclick="addSceneTarget()" style="margin-top: 6px;">+ <span data-i18n="scenes.targets.add">Add Target</span></button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="scene-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="scene-preset-editor-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -96,6 +96,15 @@
|
||||
<input type="text" id="stream-description" data-i18n-placeholder="streams.description_placeholder" placeholder="Describe this source...">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="stream-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="stream-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -41,6 +41,15 @@
|
||||
<small class="input-hint" style="display:none" data-i18n="sync_clock.description.hint">Optional notes about this clock's purpose</small>
|
||||
<input type="text" id="sync-clock-description" data-i18n-placeholder="sync_clock.description.placeholder" placeholder="">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</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="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="sync-clock-tags-container"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -113,6 +113,15 @@
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="target-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="target-editor-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -267,6 +267,15 @@
|
||||
<small class="input-hint" style="display:none" data-i18n="value_source.description.hint">Optional notes about this value source</small>
|
||||
<input type="text" id="value-source-description" data-i18n-placeholder="value_source.description.placeholder" placeholder="Describe this value source...">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="tags.label">Tags:</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="tags.hint">Assign tags for grouping and filtering cards</small>
|
||||
<div id="value-source-tags-container"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
Reference in New Issue
Block a user