Lumi/plugins/now_playing/views/admin.ejs
2026-07-24 14:44:27 +02:00

87 lines
10 KiB
Plaintext

<%- include("../../../src/web/views/partials/layout-top", { title }) %>
<link rel="stylesheet" href="/plugins/now_playing/assets/admin.css?v=<%= assetVersion %>" />
<section class="card">
<%- include("../../../src/web/views/partials/page-header", {
eyebrow: "Streaming media",
pageTitle: "Song Overlay",
description: "Send event-driven song information from Lumi Companion to chat and a customizable Lumi Overlay source.",
actions: ''
}) %>
<div class="np-status-grid">
<div><span>Companion</span><strong class="<%= current.stale ? 'tone-warning' : 'tone-success' %>"><%= current.stale ? 'Waiting for updates' : 'Connected recently' %></strong></div>
<div><span>Playback</span><strong><%= current.playback?.status || 'Unavailable' %></strong></div>
<div><span>Current song</span><strong><%= current.track?.name || 'Nothing reported' %></strong></div>
<div><span>Overlay source</span><strong><%= moduleStatus.exists ? `${moduleStatus.overlayName} / ${moduleStatus.sceneName}` : 'Not installed' %></strong></div>
</div>
</section>
<section class="card">
<div class="section-header"><div><span class="eyebrow">Connection</span><h2>Companion authentication</h2><p class="hint">Song Overlay automatically uses the same paired-device authentication as Lumi Companion. There is no plugin-specific key to generate or maintain.</p></div></div>
<div class="callout success"><strong>Shared Companion trust</strong><p>Pair the Companion once. Revoking that device in Lumi immediately removes access for transcription, Song Overlay, and other Companion plugins.</p></div>
</section>
<form method="post" action="/plugins/now_playing/settings" data-lumi-settings-form>
<section class="card">
<div class="section-header"><div><span class="eyebrow">Overlay</span><h2>Song card</h2><p class="hint">These settings update the rendered source. Position and source bounds remain editable in Lumi Overlays.</p></div><label class="switch"><input class="switch-input" type="checkbox" name="enabled" <%= config.enabled ? 'checked' : '' %> /><span class="switch-track"></span><span class="switch-text">Enabled</span></label></div>
<div class="form-grid">
<label>Visibility after song change<select name="display_mode"><option value="always" <%= config.display_mode === 'always' ? 'selected' : '' %>>Always while music is active</option><option value="timed" <%= config.display_mode === 'timed' ? 'selected' : '' %>>Show temporarily</option></select></label>
<label>Temporary display time (seconds)<input type="number" name="display_seconds" min="1" max="600" value="<%= config.display_seconds %>" /></label>
<label>Card scale<input type="number" name="scale" min="0.5" max="2.5" step="0.05" value="<%= config.scale %>" /></label>
<label>Corner radius<input type="number" name="corner_radius" min="0" max="80" value="<%= config.corner_radius %>" /></label>
<div class="field full np-check-grid">
<label><input type="checkbox" name="show_cover" <%= config.show_cover ? 'checked' : '' %> /> Cover art</label>
<label><input type="checkbox" name="show_title" <%= config.show_title ? 'checked' : '' %> /> Song name</label>
<label><input type="checkbox" name="show_artist" <%= config.show_artist ? 'checked' : '' %> /> Artist</label>
<label><input type="checkbox" name="show_album" <%= config.show_album ? 'checked' : '' %> /> Album</label>
<label><input type="checkbox" name="show_release_year" <%= config.show_release_year ? 'checked' : '' %> /> Release year</label>
<label><input type="checkbox" name="show_timeline" <%= config.show_timeline ? 'checked' : '' %> /> Timeline</label>
<label><input type="checkbox" name="show_provider" <%= config.show_provider ? 'checked' : '' %> /> Provider label</label>
</div>
<label>Entry animation<select name="entry_animation"><% ['none','fade','slide-left','slide-right','slide-up','slide-down','jump','drop'].forEach((value) => { %><option value="<%= value %>" <%= config.entry_animation === value ? 'selected' : '' %>><%= value.replaceAll('-', ' ') %></option><% }) %></select></label>
<label>Exit animation<select name="exit_animation"><% ['none','fade','slide-left','slide-right','slide-up','slide-down','jump','drop'].forEach((value) => { %><option value="<%= value %>" <%= config.exit_animation === value ? 'selected' : '' %>><%= value.replaceAll('-', ' ') %></option><% }) %></select></label>
<label>Animation duration (ms)<input type="number" name="animation_ms" min="0" max="3000" step="25" value="<%= config.animation_ms %>" /></label>
<label>Transparency<input type="number" name="opacity" min="0" max="1" step="0.05" value="<%= config.opacity %>" /></label>
<label>Transparency affects<select name="transparency_scope"><option value="everything" <%= config.transparency_scope === 'everything' ? 'selected' : '' %>>Everything</option><option value="background" <%= config.transparency_scope === 'background' ? 'selected' : '' %>>Background only</option><option value="everything_except_image_text" <%= config.transparency_scope === 'everything_except_image_text' ? 'selected' : '' %>>Everything except image and text</option><option value="everything_except_text" <%= config.transparency_scope === 'everything_except_text' ? 'selected' : '' %>>Everything except text</option></select></label>
<label>Background<select name="background_mode"><option value="cover-solid" <%= config.background_mode === 'cover-solid' ? 'selected' : '' %>>Solid from cover art</option><option value="cover-gradient" <%= config.background_mode === 'cover-gradient' ? 'selected' : '' %>>Gradient from cover art</option><option value="custom-solid" <%= config.background_mode === 'custom-solid' ? 'selected' : '' %>>Custom solid</option><option value="custom-gradient" <%= config.background_mode === 'custom-gradient' ? 'selected' : '' %>>Custom gradient</option></select></label>
<label>Custom primary color<input type="color" name="custom_color_primary" value="<%= config.custom_color_primary %>" /></label>
<label>Custom secondary color<input type="color" name="custom_color_secondary" value="<%= config.custom_color_secondary %>" /></label>
</div>
</section>
<section class="card">
<div class="section-header"><div><span class="eyebrow">Chat</span><h2>Song announcements</h2><p class="hint">Announcements are sent only when a different song starts. Pause, resume, seek, and heartbeat events never announce again.</p></div><label class="switch"><input class="switch-input" type="checkbox" name="announce_enabled" <%= config.announce_enabled ? 'checked' : '' %> /><span class="switch-track"></span><span class="switch-text">Enabled</span></label></div>
<div class="form-grid">
<div class="field full np-check-grid"><label><input type="checkbox" name="announce_twitch" <%= config.announce_twitch ? 'checked' : '' %> /> Twitch</label><label><input type="checkbox" name="announce_youtube" <%= config.announce_youtube ? 'checked' : '' %> /> YouTube</label><label><input type="checkbox" name="announce_only_when_live" <%= config.announce_only_when_live ? 'checked' : '' %> /> Only when the platform is live</label></div>
<label class="field full">Announcement text<textarea name="announcement_template" rows="3" maxlength="500" data-placeholder-field="plugin.song_overlay.announcement_template" data-placeholder-output-audience="user"><%= config.announcement_template %></textarea></label>
<label>Duplicate cooldown (seconds)<input type="number" name="announcement_cooldown_seconds" min="0" max="3600" value="<%= config.announcement_cooldown_seconds %>" /></label>
<details class="field full"><summary>Available placeholders</summary><div class="np-placeholder-list"><% placeholders.forEach((placeholder) => { %><code>{{<%= placeholder %>}}</code><% }) %></div></details>
<div class="form-actions"><button class="button subtle" type="submit" formaction="/plugins/now_playing/test-announcement">Send test announcement</button></div>
</div>
</section>
<section class="card">
<div class="section-header"><div><span class="eyebrow">Reliability</span><h2>Connection health</h2></div></div>
<div class="form-grid"><label>Mark Companion stale after (seconds)<input type="number" name="heartbeat_stale_seconds" min="15" max="600" value="<%= config.heartbeat_stale_seconds %>" /></label></div>
</section>
<div class="sticky-save"><button class="button" type="submit">Save Song Overlay settings</button></div>
</form>
<section class="card">
<div class="section-header"><div><span class="eyebrow">Lumi Overlays</span><h2>Install the source</h2><p class="hint">The plugin creates a normal Lumi website source and keeps its URL synchronized. You can move and resize it in the existing overlay editor.</p></div></div>
<% if (moduleStatus.exists) { %>
<div class="callout success"><strong>Installed</strong><p><%= moduleStatus.overlayName %> → <%= moduleStatus.sceneName %> → <%= moduleStatus.moduleName %></p></div>
<form method="post" action="/plugins/now_playing/overlay/remove" data-confirm-mode="modal" data-confirm-title="Remove Song Overlay source?" data-confirm-text="The source will be removed from Lumi Overlays. Plugin settings and current song state will remain." data-confirm-label="Remove source"><button class="button danger" type="submit">Remove overlay source</button></form>
<% } else { %>
<form method="post" action="/plugins/now_playing/overlay/install" class="input-action-form">
<label class="field">Target scene<select name="scene_id" required><option value="">Choose overlay scene</option><% overlayTargets.forEach((target) => { %><option value="<%= target.sceneId %>"><%= target.overlayName %> — <%= target.sceneName %><%= target.enabled ? '' : ' (disabled)' %></option><% }) %></select></label>
<button class="button" type="submit" <%= overlayTargets.length ? '' : 'disabled' %>>Add Song Overlay source</button>
</form>
<% if (!overlayTargets.length) { %><div class="empty-state">Create a Lumi Overlay and scene first.</div><% } %>
<% } %>
</section>
<script src="/plugins/now_playing/assets/admin.js?v=<%= assetVersion %>" defer></script>
<%- include("../../../src/web/views/partials/layout-bottom") %>