Files
ledgrab/server/src/wled_controller/templates/modals/game-integration-editor.html
T
alexei.dolgolyov 492bdb95e3 feat: game integration system
Receive real-time events from games (CS2, Dota 2, LoL, etc.) and drive
LED effects through the existing color strip and value source pipelines.

Core:
- GameEventBus (thread-safe pub/sub) with standardized 23-type event vocabulary
- GameAdapter ABC + AdapterRegistry + MappingAdapter (YAML-driven)
- Built-in adapters: CS2 GSI, Dota 2 GSI, LoL Live Client, Generic Webhook
- Community YAML adapters: Minecraft, Valorant, Rocket League
- GameEventColorStripStream with 5 effects (flash/pulse/sweep/color_shift/breathing)
- GameEventValueSource with EMA smoothing and timeout
- 4 built-in effect presets (FPS Combat, MOBA Health, Racing, Generic Alert)
- Auto-setup for Valve GSI games (Steam path detection, cfg file writing)
- Demo capture engine exposed to non-demo mode

Frontend:
- Game tab in Streams tree navigation with integration cards
- Game integration editor modal with adapter picker, config fields, event mappings
- game_event source type in CSS and ValueSource editors
- Setup instructions overlay (markdown rendered)
- Live event monitor and connection test

API:
- Full CRUD for game integrations
- Event ingestion endpoint (adapter-level auth)
- Adapter metadata, presets, auto-setup, status/diagnostics endpoints
2026-03-31 13:17:52 +03:00

113 lines
6.4 KiB
HTML

<div id="game-integration-modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="gi-title">
<div class="modal-content modal-lg">
<div class="modal-header">
<h2 id="gi-title" data-i18n="game_integration.add">Add Game Integration</h2>
<button class="modal-close-btn" onclick="closeGameIntegrationModal()" data-i18n-aria-label="aria.close">&times;</button>
</div>
<div class="modal-body">
<input type="hidden" id="gi-id">
<div id="gi-error" class="modal-error" style="display:none"></div>
<!-- Name + Tags -->
<div class="form-group">
<div class="label-row">
<label for="gi-name" data-i18n="game_integration.name">Name:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="game_integration.name.hint">A descriptive name for this game integration</small>
<input type="text" id="gi-name" required>
<div id="gi-tags-container"></div>
</div>
<!-- Description -->
<div class="form-group">
<div class="label-row">
<label for="gi-description" data-i18n="game_integration.description">Description:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="game_integration.description.hint">Optional description of what this integration does</small>
<input type="text" id="gi-description">
</div>
<!-- Enabled -->
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="gi-enabled" checked>
<span data-i18n="game_integration.enabled">Enabled</span>
</label>
</div>
<!-- Game / Adapter picker -->
<div class="form-group">
<div class="label-row">
<label for="gi-adapter-type" data-i18n="game_integration.adapter_type">Game / Adapter:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="game_integration.adapter_type.hint">Select the game or adapter type for this integration</small>
<select id="gi-adapter-type"></select>
</div>
<!-- Adapter config (auto-generated) -->
<div class="form-group">
<div class="label-row">
<label data-i18n="game_integration.adapter_config">Adapter Configuration</label>
</div>
<div id="gi-adapter-config-fields"></div>
</div>
<!-- Setup instructions + Auto Setup buttons -->
<div id="gi-setup-instructions-btn-wrapper" style="display:none">
<button type="button" class="btn btn-secondary btn-sm" onclick="openSetupInstructions()" data-i18n="game_integration.setup_instructions">Setup Instructions</button>
<button type="button" id="gi-auto-setup-btn" class="btn btn-primary btn-sm" onclick="autoSetupGameIntegration()" style="display:none" data-i18n="game_integration.auto_setup">Auto Setup</button>
</div>
<!-- Event Mapping Editor -->
<div class="form-group">
<div class="label-row">
<label data-i18n="game_integration.event_mappings">Event Mappings</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="game_integration.event_mappings.hint">Map game events to LED effects. Each event type can trigger a different visual effect.</small>
<div class="gi-mapping-toolbar">
<select id="gi-mapping-preset" onchange="onMappingPresetChange()">
<option value="" data-i18n="game_integration.preset.select">Load preset...</option>
<option value="fps_combat" data-i18n="game_integration.preset.fps_combat">FPS Combat</option>
<option value="moba_health" data-i18n="game_integration.preset.moba_health">MOBA Health</option>
</select>
<button class="btn btn-secondary btn-sm" onclick="addGameMapping()" data-i18n="game_integration.mapping.add">+ Add Mapping</button>
</div>
<div id="gi-mappings-list" class="gi-mappings-list"></div>
</div>
<!-- Live Event Monitor -->
<div class="form-group">
<div class="label-row">
<label data-i18n="game_integration.events.title">Live Events</label>
</div>
<div id="gi-event-feed" class="gi-event-feed"></div>
</div>
<!-- Connection Test -->
<div class="form-group">
<button class="btn btn-secondary" onclick="testGameConnection()" data-i18n="game_integration.test.button">Test Connection</button>
<div id="gi-test-panel" style="display:none" class="gi-test-panel"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-icon btn-secondary" onclick="closeGameIntegrationModal()" title="Cancel" data-i18n-title="settings.button.cancel" data-i18n-aria-label="aria.cancel">&#x2715;</button>
<button class="btn btn-icon btn-primary" onclick="saveGameIntegration()" title="Save" data-i18n-title="settings.button.save" data-i18n-aria-label="aria.save">&#x2713;</button>
</div>
</div>
</div>
<!-- Setup Instructions Overlay (full-screen, same pattern as release notes) -->
<div id="gi-setup-overlay" class="log-overlay" style="display:none;">
<button class="log-overlay-close" onclick="closeSetupInstructions()" title="Close" data-i18n-aria-label="aria.close">&#x2715;</button>
<div class="log-overlay-toolbar">
<h3 id="gi-setup-overlay-title" data-i18n="game_integration.setup_instructions">Setup Instructions</h3>
</div>
<div id="gi-setup-overlay-content" class="release-notes-content"></div>
</div>