装饰
This commit is contained in:
@@ -38,6 +38,12 @@ pub struct SongRequestSettings {
|
||||
pub font_family: FontFamilyId,
|
||||
#[serde(default = "default_font_brightness")]
|
||||
pub font_brightness: u16,
|
||||
/// Optional text-color overrides. `None` follows the selected theme so a
|
||||
/// later theme switch can update the palette without stale saved colors.
|
||||
#[serde(default)]
|
||||
pub requester_color: Option<String>,
|
||||
#[serde(default)]
|
||||
pub song_title_color: Option<String>,
|
||||
#[serde(default = "default_font_scale")]
|
||||
pub font_scale: u16,
|
||||
#[serde(default = "default_scroll_speed")]
|
||||
@@ -61,6 +67,8 @@ impl Default for SongRequestSettings {
|
||||
theme_id: OverlayThemeId::default(),
|
||||
font_family: FontFamilyId::default(),
|
||||
font_brightness: default_font_brightness(),
|
||||
requester_color: None,
|
||||
song_title_color: None,
|
||||
font_scale: default_font_scale(),
|
||||
scroll_speed_pixels_per_second: default_scroll_speed(),
|
||||
edge_pause_seconds: default_edge_pause(),
|
||||
@@ -75,6 +83,8 @@ impl SongRequestSettings {
|
||||
pub fn sanitize(mut self) -> Self {
|
||||
self.font_scale = self.font_scale.clamp(50, 250);
|
||||
self.font_brightness = sanitize_font_brightness(self.font_brightness);
|
||||
self.requester_color = sanitize_optional_hex_color(self.requester_color);
|
||||
self.song_title_color = sanitize_optional_hex_color(self.song_title_color);
|
||||
self.scroll_speed_pixels_per_second = self.scroll_speed_pixels_per_second.clamp(5, 200);
|
||||
self.edge_pause_seconds = self.edge_pause_seconds.min(15);
|
||||
self.max_queue_size = self.max_queue_size.min(10_000);
|
||||
@@ -84,6 +94,14 @@ impl SongRequestSettings {
|
||||
}
|
||||
}
|
||||
|
||||
fn sanitize_optional_hex_color(value: Option<String>) -> Option<String> {
|
||||
let value = value?.trim().to_ascii_uppercase();
|
||||
(value.len() == 7
|
||||
&& value.starts_with('#')
|
||||
&& value[1..].bytes().all(|byte| byte.is_ascii_hexdigit()))
|
||||
.then_some(value)
|
||||
}
|
||||
|
||||
const fn default_font_scale() -> u16 {
|
||||
100
|
||||
}
|
||||
@@ -465,6 +483,53 @@ impl SongRequestService {
|
||||
)
|
||||
}
|
||||
|
||||
/// Cancel every active request in one tenant-scoped transaction.
|
||||
///
|
||||
/// A single revision represents the whole reset. Publishing one reset
|
||||
/// event also avoids exposing a half-cleared queue to OBS clients while a
|
||||
/// long queue is being processed.
|
||||
pub async fn clear_active(
|
||||
&self,
|
||||
component: &ComponentInstance,
|
||||
room_id: &str,
|
||||
) -> Result<u64, SongRequestError> {
|
||||
let mut client = self.db.get().await?;
|
||||
let transaction = client.transaction().await?;
|
||||
Db::set_tenant(&transaction, component.owner_id).await?;
|
||||
lock_state(&transaction, component).await?;
|
||||
let cancelled_count = transaction
|
||||
.execute(
|
||||
"UPDATE song_requests SET status='cancelled',queue_position=0,finished_at=now() \
|
||||
WHERE owner_user_id=$1 AND component_instance_id=$2 \
|
||||
AND status IN ('current','queued')",
|
||||
&[&component.owner_id, &component.id],
|
||||
)
|
||||
.await?;
|
||||
if cancelled_count == 0 {
|
||||
transaction.commit().await?;
|
||||
return Ok(0);
|
||||
}
|
||||
let revision = bump_revision(&transaction, component.id).await?;
|
||||
transaction.commit().await?;
|
||||
self.hub.publish(
|
||||
component.id,
|
||||
Arc::new(ComponentMessage::new(
|
||||
component,
|
||||
room_id,
|
||||
"song.queue.changed",
|
||||
json!({
|
||||
"revision": revision,
|
||||
"operation": "cleared",
|
||||
"itemId": null,
|
||||
"item": null,
|
||||
"current": null,
|
||||
"cancelledCount": cancelled_count,
|
||||
}),
|
||||
)),
|
||||
);
|
||||
Ok(cancelled_count)
|
||||
}
|
||||
|
||||
async fn request_song(
|
||||
&self,
|
||||
component: &ComponentInstance,
|
||||
@@ -1031,6 +1096,8 @@ mod tests {
|
||||
let bounded = SongRequestSettings {
|
||||
font_scale: 999,
|
||||
font_brightness: u16::MAX,
|
||||
requester_color: Some(" #a1b2c3 ".into()),
|
||||
song_title_color: Some("transparent".into()),
|
||||
scroll_speed_pixels_per_second: 0,
|
||||
edge_pause_seconds: 200,
|
||||
max_queue_size: u32::MAX,
|
||||
@@ -1041,6 +1108,8 @@ mod tests {
|
||||
.sanitize();
|
||||
assert_eq!(bounded.font_scale, 250);
|
||||
assert_eq!(bounded.font_brightness, 180);
|
||||
assert_eq!(bounded.requester_color.as_deref(), Some("#A1B2C3"));
|
||||
assert_eq!(bounded.song_title_color, None);
|
||||
assert_eq!(bounded.scroll_speed_pixels_per_second, 5);
|
||||
assert_eq!(bounded.edge_pause_seconds, 15);
|
||||
assert_eq!(bounded.max_queue_size, 10_000);
|
||||
|
||||
Reference in New Issue
Block a user