diff --git a/httpstatic/chan_monitors.js b/httpstatic/chan_monitors.js index 9c4b06db..d5d3d5b3 100644 --- a/httpstatic/chan_monitors.js +++ b/httpstatic/chan_monitors.js @@ -1,5 +1,5 @@ import choc, {set_content, DOM, on} from "https://rosuav.github.io/choc/factory.js"; -const {A, BR, BUTTON, CODE, DIV, FIELDSET, INPUT, LABEL, LEGEND, OPTGROUP, OPTION, SELECT, SPAN, TABLE, TD, TEXTAREA, TH, TR} = choc; //autoimport +const {A, BR, BUTTON, CODE, DIV, FIELDSET, INPUT, LABEL, LEGEND, OPTGROUP, OPTION, P, SELECT, SPAN, TABLE, TD, TEXTAREA, TH, TR} = choc; //autoimport import {update_display, formatters} from "$$static||monitor.js$$"; import {simpleconfirm, TEXTFORMATTING} from "$$static||utils.js$$"; @@ -99,6 +99,18 @@ set_content("#editcountdown form div", [ {name: "textcompleted", label: "Completed", desc: " If blank, same as Active"}, {name: "textinactive", label: "Inactive", desc: " If blank, same as Active"}, ]}), + TABLE({border: 1}, [ + TR(TH({colspan: 2}, "Automate timer based on...")), + TR([TH("Scene"), TD([ + LABEL([INPUT({name: "startonscene", type: "checkbox"}), + " Start the countdown when this scene is selected"]), + P(["If this countdown is in an OBS scene and it becomes visible, the timer", BR(), + "will be started or reset. Good for break/BRB scenes."]), + LABEL(["Initial time ", INPUT({name: "startonscene_time", type: "number"}), + " Will count down from this time (eg 600 = ten minutes)"]), + ])]), + TR([TH("Schedule"), TD("TODO: Link this timer to your Twitch schedule (maybe w/ offset)")]), + ]), ]); set_content("#editgoalbar form div", TABLE({border: 1}, [ diff --git a/httpstatic/monitor.js b/httpstatic/monitor.js index ac9333f1..ba720465 100644 --- a/httpstatic/monitor.js +++ b/httpstatic/monitor.js @@ -50,6 +50,15 @@ function countdown_ticker(elem, id) { set_content(elem, parts.join(":")); } +let lastvis = "hidden"; +function vischange() { + if (lastvis === "hidden" && document.visibilityState === "visible") { + //We've just become visible. Signal the server to start the timer. + ws_sync.send({cmd: "sceneactive"}); + } + lastvis = document.visibilityState; +} + export function render(data) {update_display(DOM("#display"), data.data);} export function update_display(elem, data) { //Used for the preview as well as the live display //Update styles. The server provides a single "text_css" attribute covering most of the easy @@ -104,6 +113,9 @@ export function update_display(elem, data) { //Used for the preview as well as t elem._stillebot_countdown_format = m[2]; elem._stillebot_countdown_interval = setInterval(countdown_ticker, 1000, elem, data.id); countdown_ticker(elem, data.id); + if (data.startonscene && ws_group[0] !== '#') //Don't do this on the control/preview connection + (document.onvisibilitychange = vischange)(); + else document.onvisibilitychange = null; //Note: Using this instead of on() for idempotency } else set_content(elem, data.display); } diff --git a/modules/http/chan_monitors.pike b/modules/http/chan_monitors.pike index e2a8b7cd..9d1b9956 100644 --- a/modules/http/chan_monitors.pike +++ b/modules/http/chan_monitors.pike @@ -3,8 +3,6 @@ inherit hook; inherit builtin_command; /* Subset of functionality from mustard-mine.herokuapp.com: -* It can be autostarted on page activation - good for a break timer. - - On WS connection, if timer not active, start with a known countdown * It can be linked to your Twitch schedule (see get_stream_schedule()) to define the target. - Note that this will likely mean that schedule updates become crucial. - Obviously will require a call to get_stream_schedule inside get_chan_state @@ -44,7 +42,7 @@ constant vars_provided = ([ //Some of these attributes make sense only with certain types (eg needlesize is only for goal bars). constant saveable_attributes = "previewbg barcolor fillcolor needlesize thresholds progressive lvlupcmd format width height " "active bit sub_t1 sub_t2 sub_t3 exclude_gifts tip follow kofi_dono kofi_member kofi_renew kofi_shop " - "fw_dono fw_member fw_shop fw_gift textcompleted textinactive" / " " + TEXTFORMATTING_ATTRS; + "fw_dono fw_member fw_shop fw_gift textcompleted textinactive startonscene startonscene_time" / " " + TEXTFORMATTING_ATTRS; constant valid_types = (<"text", "goalbar", "countdown">); __async__ mapping(string:mixed) http_request(Protocols.HTTP.Server.Request req) { @@ -128,6 +126,7 @@ mapping _get_monitor(object channel, mapping monitors, string id) { "text_css": textformatting_css(text), ]); } + bool need_mod(string grp) {return grp == "";} //Require mod status for the master socket mapping get_chan_state(object channel, string grp, string|void id) { mapping monitors = G->G->DB->load_cached_config(channel->userid, "monitors"); @@ -172,6 +171,15 @@ void websocket_cmd_setvar(mapping(string:mixed) conn, mapping(string:mixed) msg) channel->set_variable(msg->varname, (string)(int)msg->val, "set"); } +//NOTE: This is a very rare message - a mutator that does not require mod powers or even a login. +//The *only* thing you can do with it is (re)start a countdown configured to start on scene. +void wscmd_sceneactive(object channel, mapping(string:mixed) conn, mapping(string:mixed) msg) { + mapping mon = G->G->DB->load_cached_config(channel->userid, "monitors")[conn->subgroup]; + if (!mon->?startonscene) return; + sscanf(mon->text, "$%s$:", string varname); + channel->set_variable(varname, (string)(time() + (int)mon->startonscene_time)); +} + @hook_allmsgs: int message(object channel, mapping person, string msg) {