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
This commit is contained in:
@@ -649,18 +649,18 @@ function renderDashboardAutomation(automation: Automation, sceneMap: Map<string,
|
||||
const isDisabled = !automation.enabled;
|
||||
|
||||
let condSummary = '';
|
||||
if (automation.conditions.length > 0) {
|
||||
const parts = automation.conditions.map(c => {
|
||||
if (c.condition_type === 'application') {
|
||||
const apps = (c.apps || []).join(', ');
|
||||
const matchLabel = c.match_type === 'topmost' ? t('automations.condition.application.match_type.topmost') : t('automations.condition.application.match_type.running');
|
||||
if (automation.rules.length > 0) {
|
||||
const parts = automation.rules.map(r => {
|
||||
if (r.rule_type === 'application') {
|
||||
const apps = (r.apps || []).join(', ');
|
||||
const matchLabel = r.match_type === 'topmost' ? t('automations.rule.application.match_type.topmost') : t('automations.rule.application.match_type.running');
|
||||
return `${apps} (${matchLabel})`;
|
||||
}
|
||||
if (c.condition_type === 'startup') return t('automations.condition.startup');
|
||||
if (c.condition_type === 'time_of_day') return t('automations.condition.time_of_day');
|
||||
return t(`automations.condition.${c.condition_type}`) || c.condition_type;
|
||||
if (r.rule_type === 'startup') return t('automations.rule.startup');
|
||||
if (r.rule_type === 'time_of_day') return t('automations.rule.time_of_day');
|
||||
return t(`automations.rule.${r.rule_type}`) || r.rule_type;
|
||||
});
|
||||
const logic = automation.condition_logic === 'and' ? ' & ' : ' | ';
|
||||
const logic = automation.rule_logic === 'and' ? ' & ' : ' | ';
|
||||
condSummary = parts.join(logic);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user