Browse Source

feature: all implemented

keep-around/3819c53d3947c2b803e4eeb5df287c8649b045e0
Adler Neves 11 months ago
parent
commit
3819c53d39
8 changed files with 387 additions and 337 deletions
  1. +5
    -0
      .vscode/settings.json
  2. +3
    -3
      config.toml
  3. +1
    -1
      content/_index.md
  4. +118
    -90
      content/control.md
  5. +4
    -1
      content/display.md
  6. +14
    -14
      static/css/display.css
  7. +105
    -93
      static/js/control.js
  8. +137
    -135
      static/js/display.js

+ 5
- 0
.vscode/settings.json View File

@ -0,0 +1,5 @@
{
"files.associations": {
"istream": "cpp"
}
}

+ 3
- 3
config.toml View File

@ -1,5 +1,5 @@
title = "Text Scroller"
baseurl = "https://text-scroller.sfner.com/"
title = "Manual Notifications"
baseurl = "https://manual-notifier.sfner.com/"
languageCode = "en-us"
theme = "ananke"
@ -16,7 +16,7 @@ enableRobotsTXT = true
filename = "sitemap.xml"
[params]
description = "Your live streams deserve better scrolling texts"
description = "For those services with no automatic integrations with streaming software"
favicon = "/favicon.ico"
site_logo = ""
facebook = ""


+ 1
- 1
content/_index.md View File

@ -1,5 +1,5 @@
---
description: "Your live streams deserve better scrolling texts"
description: "No integration should not mean no on-stream notifications"
---
# Use for free


+ 118
- 90
content/control.md View File

@ -1,9 +1,9 @@
---
title: Control Panel
description: This is a page that controls which texts will scroll
description: This is a page that controls the manual notifications
---
This is a page that controls which texts will scroll and how they will look like.
This is a page that controls the manual notifications and how they will look like.
<div style="text-align: center;">
<div class="w-30 reveal-on-hover pixelated" style="display: inline-block;" id="qrcode"></div>
@ -62,120 +62,148 @@ setTimeout(keepQrCodeUpdated, 10);
</div>
</form>
## Background
<form class="form" class="black-80 sans-serif w-100" onsubmit="return false;">
<label for="backgroundColor" class="f6 b db mb1 mt3 sans-serif mid-gray">
Color:
</label>
<input id="backgroundColor" name="backgroundColor" type="text" class="w-90 f5 pv3 ph3 bg-light-gray bn" value="transparent" onkeyup="updateMediaPreviewAfterChange(); broadcastChanges()" onkeydown="updateMediaPreviewAfterChange(); broadcastChanges()" onchange="updateMediaPreviewAfterChange(); broadcastChanges()">
<label for="backgroundMedia" class="f6 b db mb1 mt3 sans-serif mid-gray">
Media:
</label>
<input id="backgroundMedia" type="file" class="w-90 f5 pv3 ph3 bg-light-gray bn" style="cursor: pointer;" onchange="return loadBackgroundMedia(this);">
<div class="requirements f6 gray glow i ph3 overflow-hidden">
Drag-and-drop is supported
</div>
<div class="requirements f6 gray glow i ph3 overflow-hidden">
Goes over the color
</div>
<input id="backgroundMediaHidden" type="hidden" class="w-90 f5 pv3 ph3 bg-light-gray bn" name="backgroundMediaHidden">
<button id="backgroundMediaRemover" class="w-100 mv2 white pa3 bn hover-shadow hover-bg-black bg-animate bg-black" style="cursor: pointer;" onclick="loadBackgroundMedia(false)">
Remove any existing media
</button>
<label for="mediaPreview" class="f6 b db mb1 mt3 sans-serif mid-gray">
Preview:
</label>
<div id="mediaPreview" style="width: 100%; padding: 2em; border: 1px solid black; border-radius: 0.25em;"></div>
</form>
## Styling
<form class="form" class="black-80 sans-serif w-100" onsubmit="return false;">
<label for="containerWidth" class="f6 b db mb1 mt3 sans-serif mid-gray">
Container width (in PXs)
</label>
<input id="containerWidth" name="containerWidth" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="350" step="1" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="containerHeight" class="f6 b db mb1 mt3 sans-serif mid-gray">
Container height (in PXs)
</label>
<input id="containerHeight" name="containerHeight" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="150" step="1" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="transitionDuration" class="f6 b db mb1 mt3 sans-serif mid-gray">
Transition durations (in milliseconds)
</label>
<input id="transitionDuration" name="transitionDuration" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="300" step="10" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="onScreenDuration" class="f6 b db mb1 mt3 sans-serif mid-gray">
On-screen duration (in seconds)
</label>
<input id="onScreenDuration" name="onScreenDuration" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="7" step="0.25" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="fontFamily" class="f6 b db mb1 mt3 sans-serif mid-gray">
Font-family
</label>
<input id="fontFamily" name="fontFamily" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="sans-serif" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="font-size" class="f6 b db mb1 mt3 sans-serif mid-gray">
<label for="horizontalPadding" class="f6 b db mb1 mt3 sans-serif mid-gray">
Horizontal padding (in EMs)
</label>
<input id="horizontalPadding" name="horizontalPadding" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="3" step="0.25" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
</form>
### Primary text
<form class="form" class="black-80 sans-serif w-100" onsubmit="return false;">
<label for="fontSizePrimary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Font-size (in EMs)
</label>
<input id="fontSize" name="fontSize" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="3" step="0.1" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="containerWidth" class="f6 b db mb1 mt3 sans-serif mid-gray">
Container width (in EMs)
<input id="fontSizePrimary" name="fontSizePrimary" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="3" step="0.1" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="fontWeightPrimary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Font-weight
</label>
<input id="containerWidth" name="containerWidth" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="15" step="0.25" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="container-height" class="f6 b db mb1 mt3 sans-serif mid-gray">
Container height (in EMs)
<input id="fontWeightPrimary" name="fontWeightPrimary" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="300" step="100" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="colorBasePrimary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Color base
</label>
<input id="containerHeight" name="containerHeight" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="6" step="0.1" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="containerBackgroundColor" class="f6 b db mb1 mt3 sans-serif mid-gray">
Container background color
<input id="colorBasePrimary" name="colorBasePrimary" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="#FFFFFF" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="colorTemplatedPrimary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Color templated
</label>
<input id="containerBackgroundColor" name="containerBackgroundColor" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="transparent" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="containerBorderColor" class="f6 b db mb1 mt3 sans-serif mid-gray">
Container border color
<input id="colorTemplatedPrimary" name="colorTemplatedPrimary" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="#8CD5FC" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="strokeColorPrimary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Stroke color
</label>
<input id="containerBorderColor" name="containerBorderColor" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="#FFFFFF" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="cardBackgroundColor" class="f6 b db mb1 mt3 sans-serif mid-gray">
Card background color
<input id="strokeColorPrimary" name="strokeColorPrimary" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="#000000" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="strokeWeightPrimary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Stroke weight (in EMs)
</label>
<input id="cardBackgroundColor" name="cardBackgroundColor" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="#0000007F" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="cardPadding" class="f6 b db mb1 mt3 sans-serif mid-gray">
Card padding (in EMs)
<input id="strokeWeightPrimary" name="strokeWeightPrimary" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="0.1" step="0.005" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="marginTopPrimary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Top margin (in EMs)
</label>
<input id="cardPadding" name="cardPadding" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="0.3" step="0.05" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="cardMargin" class="f6 b db mb1 mt3 sans-serif mid-gray">
Card margin (in EMs)
<input id="marginTopPrimary" name="marginTopPrimary" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="0" step="0.05" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="lineHeightPrimary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Spacing between lines
</label>
<input id="lineHeightPrimary" name="lineHeightPrimary" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="1" step="0.05" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
</form>
### Secondary text
<form class="form" class="black-80 sans-serif w-100" onsubmit="return false;">
<label for="fontSizeSecondary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Font-size (in EMs)
</label>
<input id="cardMargin" name="cardMargin" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="0.1" step="0.05" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="cardBorderRadius" class="f6 b db mb1 mt3 sans-serif mid-gray">
Card border radius (in EMs)
<input id="fontSizeSecondary" name="fontSizeSecondary" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="3" step="0.1" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="fontWeightSecondary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Font-weight
</label>
<input id="cardBorderRadius" name="cardBorderRadius" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="0.2" step="0.05" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="cardNextSize" class="f6 b db mb1 mt3 sans-serif mid-gray">
Next card size (in %)
<input id="fontWeightSecondary" name="fontWeightSecondary" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="300" step="100" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="colorBaseSecondary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Color base
</label>
<input id="cardNextSize" name="cardNextSize" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="50" step="1" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="cardNextOpacity" class="f6 b db mb1 mt3 sans-serif mid-gray">
Next card opacity (0 = transparent; 1 = opaque)
<input id="colorBaseSecondary" name="colorBaseSecondary" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="#FFFFFF" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="colorTemplatedSecondary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Color templated
</label>
<input id="cardNextOpacity" name="cardNextOpacity" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="0.7" step="0.05" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="textColor" class="f6 b db mb1 mt3 sans-serif mid-gray">
Text color
<input id="colorTemplatedSecondary" name="colorTemplatedSecondary" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="#8CD5FC" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="strokeColorSecondary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Stroke color
</label>
<input id="textColor" name="textColor" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="#000000" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="textPadding" class="f6 b db mb1 mt3 sans-serif mid-gray">
Text padding (in EMs)
<input id="strokeColorSecondary" name="strokeColorSecondary" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="#000000" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="strokeWeightSecondary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Stroke weight (in EMs)
</label>
<input id="textPadding" name="textPadding" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="0.1" step="0.05" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="subtextOpacity" class="f6 b db mb1 mt3 sans-serif mid-gray">
Subtext opacity (0 = transparent; 1 = opaque)
<input id="strokeWeightSecondary" name="strokeWeightSecondary" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="0.1" step="0.005" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="marginTopSecondary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Top margin (in EMs)
</label>
<input id="subtextOpacity" name="subtextOpacity" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="0.7" step="0.05" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="subtextSize" class="f6 b db mb1 mt3 sans-serif mid-gray">
Subtext size (in %, relative to title size)
<input id="marginTopSecondary" name="marginTopSecondary" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="0" step="0.05" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="lineHeightSecondary" class="f6 b db mb1 mt3 sans-serif mid-gray">
Spacing between lines
</label>
<input id="subtextSize" name="subtextSize" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="70" step="1" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<input id="lineHeightSecondary" name="lineHeightSecondary" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="1" step="0.05" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
</form>
## Content
<form class="form" class="black-80 sans-serif w-100" onsubmit="return false;">
<label for="cardTimeout" class="f6 b db mb1 mt3 sans-serif mid-gray">
How much time a text will be on screen (in seconds)
<form id="form_content" class="form" class="black-80 sans-serif w-100" onsubmit="return false;">
<label for="primaryText" class="f6 b db mb1 mt3 sans-serif mid-gray">
Primary text
</label>
<input id="cardTimeout" name="cardTimeout" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="5" step="0.1" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="transitionDuration" class="f6 b db mb1 mt3 sans-serif mid-gray">
Transition duration (in milliseconds)
</label>
<input id="transitionDuration" name="transitionDuration" type="number" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="500" step="10" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="cardDirection" class="f6 b db mb1 mt3 sans-serif mid-gray">
Scrolling direction
</label>
<div id="cardDirection">
<div>
<input type="radio" name="cardDirection" id="cardDirectionU" value="row" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()" checked>
<label for="cardDirectionU" class="f6 mb1 mt3 sans-serif mid-gray">
Top to bottom
</label>
</div>
<div>
<input type="radio" name="cardDirection" id="cardDirectionD" value="row-reverse" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="cardDirectionD" class="f6 mb1 mt3 sans-serif mid-gray">
Bottom to top
</label>
</div>
<div>
<input type="radio" name="cardDirection" id="cardDirectionL" value="column" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="cardDirectionL" class="f6 mb1 mt3 sans-serif mid-gray">
Left to right
</label>
</div>
<div>
<input type="radio" name="cardDirection" id="cardDirectionR" value="column-reverse" onkeyup="broadcastChanges()" onkeydown="broadcastChanges()" onchange="broadcastChanges()">
<label for="cardDirectionR" class="f6 mb1 mt3 sans-serif mid-gray">
Right to left
</label>
</div>
</div>
<label for="displayContent" class="f6 b db mb1 mt3 sans-serif mid-gray">
Texts to show as cards (leave an empty line between cards)
<input id="primaryText" name="primaryText" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="{name} has donated {amount} for charity!" onkeyup="updateSendFields(); broadcastChanges()" onkeydown="updateSendFields(); broadcastChanges()" onchange="updateSendFields(); broadcastChanges()">
<label for="secondaryText" class="f6 b db mb1 mt3 sans-serif mid-gray">
Secondary text
</label>
<textarea name="displayContent" class="w-100 f5 pv3 ph3 bg-light-gray bn" style="display: none;"></textarea>
<textarea name="displayContentVisible" class="w-100 f5 pv3 ph3 bg-light-gray bn" style="resize: none;" onkeyup="compareDisplayContentTextAreas()" onkeydown="compareDisplayContentTextAreas()" onchange="compareDisplayContentTextAreas()"></textarea>
<input id="btn-update-dc-internal" type="button" class="w-100 mv2 white pa3 bn hover-shadow hover-bg-black bg-animate bg-black" style="display: none;" value="Send changes to screen" style="cursor: pointer;" onclick="return updateInternalDisplayContent();">
<input id="secondaryText" name="secondaryText" type="text" class="w-100 f5 pv3 ph3 bg-light-gray bn" value="{message}" onkeyup="updateSendFields(); broadcastChanges()" onkeydown="updateSendFields(); broadcastChanges()" onchange="updateSendFields(); broadcastChanges()">
</form>
## Currently on screen
<form id="currentForm" class="black-80 sans-serif w-100" onsubmit="return false;"></form>
## Send
<form id="form_send" class="form" class="black-80 sans-serif w-100" onsubmit="return false;">
<div id="form_send_fields"></div>
<input type="button" class="w-100 mv2 white pa3 bn hover-shadow hover-bg-black bg-animate bg-black" value="Send to display" style="cursor: pointer;" onclick="return sendToDisplay();">
</form>

+ 4
- 1
content/display.md View File

@ -1,6 +1,6 @@
---
title: Display Page
description: This is a page that displays scrolling texts
description: This is a page that displays the manual notification
---
<script>
@ -17,6 +17,8 @@ function addCSS(name) {
node.setAttribute('href', name);
document.head.appendChild(node);
}
var searchArgsGetter = () => Object.fromEntries(window.location.search.substr(1).split('&').map(x=>[...x.split('='), ''].slice(0, 2).map(y=>decodeURIComponent(y))));
var searchArgs = searchArgsGetter();
var stylesheet = Array.from(document.getElementsByTagName('link')).filter(x=>x.getAttribute('rel')=='stylesheet')[0];
stylesheet.parentElement.removeChild(stylesheet);
function eraseCanvas() {
@ -29,6 +31,7 @@ function eraseCanvas() {
var nextStyle = document.createElement('style');
nextStyle.setAttribute('id', 'nextStyle');
document.head.appendChild(nextStyle);
test_display = !!searchArgs.test;
setTimeout(()=>{
eraseCanvas();
addCSS('/css/display.css');


+ 14
- 14
static/css/display.css View File

@ -1,22 +1,22 @@
body {
* {
transition-property: all;
transition-timing-function: ease-in-out;
}
html > body {
margin: 0px;
}
main {
overflow: hidden;
display: flex;
flex-wrap: wrap;
html > body > main > section {
position: fixed;
top: 0;
left: 0;
}
section {
width: 100%;
overflow: hidden;
display: flex;
justify-content: flex-start;
flex-flow: column nowrap;
html > body > main > section > article {
text-align: center;
}
article {
width: 100%;
overflow: hidden;
.transparent {
opacity: 0;
}

+ 105
- 93
static/js/control.js View File

@ -1,6 +1,6 @@
var loadingNewData = false;
var indexFromWebSocket = false;
var acceptingRemoteSettings = false;
var acceptingRemoteSettings = true;
var currentForm = document.getElementById('currentForm');
var currentDisplayIndex = 0;
var nbsp = ' ';
@ -28,11 +28,7 @@ function handleBroadcastedMessage(message) {
type: 'Settings',
settings: getSettings()
}));
else if (message.type == 'SetCurrentlyOnScreen' || message.type == 'CurrentlyOnScreen') {
indexFromWebSocket = true;
updateCurrentlyOnScreen(message.index);
indexFromWebSocket = false;
} else if ((message.type == 'NewSettings') || (message.type == 'Settings' && acceptingRemoteSettings)) {
else if ((message.type == 'NewSettings') || (message.type == 'Settings' && acceptingRemoteSettings)) {
acceptingRemoteSettings = true;
applyLoadedSettings(message.settings);
acceptingRemoteSettings = false;
@ -55,13 +51,14 @@ function getSettings() {
for (var form of Array.from(document.querySelectorAll('form.form')))
for (var keyPair of Array.from((new FormData(form)).entries()))
settings[keyPair[0]] = keyPair[1];
settings.templateParams = getTemplateParameters();
return settings;
}
function saveSettings() {
var token = window.location.hash.substr(1);
downloadTextFile(
`text-scroller_settings_${token}.json`,
`manual-notifier_settings_${token}.json`,
JSON.stringify(getSettings())
);
}
@ -79,6 +76,44 @@ function loadSettings(input) {
}
}
function loadBackgroundMedia(input) {
if (!input) {
var backgroundMediaHidden = document.getElementById('backgroundMediaHidden');
backgroundMediaHidden.value = '';
updateMediaPreviewAfterChange();
broadcastChanges();
} else {
var file = input.files[0];
input.value = null;
var fr = new FileReader();
fr.readAsDataURL(file);
fr.onloadend = () => {
var backgroundMediaHidden = document.getElementById('backgroundMediaHidden');
backgroundMediaHidden.value = fr.result;
updateMediaPreviewAfterChange();
broadcastChanges();
};
}
}
function updateMediaPreviewAfterChange() {
var mediaPreview = document.getElementById('mediaPreview');
var backgroundMediaHidden = document.getElementById('backgroundMediaHidden');
var backgroundMediaString = backgroundMediaHidden.value;
mediaPreview.style.backgroundColor = document.getElementById('backgroundColor').value;
if (backgroundMediaString == '') {
mediaPreview.innerHTML = 'No media.';
} else if (backgroundMediaString.startsWith('data:image/')) {
mediaPreview.innerHTML = `<img src="${backgroundMediaString}" style="width: 100%;">`;
} else if (backgroundMediaString.startsWith('data:video/')) {
mediaPreview.innerHTML = `<video src="${backgroundMediaString}" controls playsinline autoplay muted loop style="width: 100%;">`;
} else {
mediaPreview.innerHTML = 'Incompatible type.';
}
}
setTimeout(updateMediaPreviewAfterChange, 100);
function applyLoadedSettings(settings) {
loadingNewData = true;
for (var setting of Object.entries(settings)) {
@ -94,26 +129,12 @@ function applyLoadedSettings(settings) {
}
}
}
updateMediaPreviewAfterChange();
loadingNewData = false;
if (!acceptingRemoteSettings)
broadcastChanges(); // only once
}
function textAreaAdjust(elem) {
elem.style.height = Math.max(1, elem.scrollHeight - 20) + "px";
elem.style.height = (18 + elem.scrollHeight) + "px";
}
function keepDisplayContentUpdated() {
setTimeout(keepDisplayContentUpdated, 100);
try {
textAreaAdjust(document.querySelector('[name="displayContentVisible"]'));
} catch (e) {
console.error(e);
}
}
setTimeout(keepDisplayContentUpdated, 10);
function broadcastChanges() {
if (!loadingNewData)
sendThroughWebSocket(JSON.stringify({
@ -122,41 +143,6 @@ function broadcastChanges() {
}));
}
function getDisplayContent() {
var dcInput = document.querySelector('[name="displayContent"]');
var dcText = dcInput.value.trim().replace(/\r/g, '');
var dcCards = dcText.trim().split('\n\n').map(
x => x.trim().split('\n').map(
y => y.trim()
).filter(
y => y.length
)
).filter(x => x.length);
var maxLength = Math.max(...dcCards.map(x => x.length));
for (var dcCard of dcCards)
while (dcCard.length < maxLength)
dcCard.push(nbsp);
return dcCards;
}
function compareDisplayContentTextAreas() {
var dcStore = document.querySelector('[name="displayContent"]');
var dcInput = document.querySelector('[name="displayContentVisible"]');
var btn = document.getElementById('btn-update-dc-internal');
if (dcStore.value != dcInput.value)
btn.style.display = 'block';
else
btn.style.display = 'none';
}
function updateInternalDisplayContent() {
var dcStore = document.querySelector('[name="displayContent"]');
var dcInput = document.querySelector('[name="displayContentVisible"]');
dcStore.value = dcInput.value;
compareDisplayContentTextAreas();
broadcastChanges();
}
var escapeHtml = (unsafe) => (unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
@ -164,45 +150,71 @@ var escapeHtml = (unsafe) => (unsafe
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;"));
function updateCurrentForm() {
var dc = getDisplayContent();
var s = '';
s += '<label class="f6 b db mb1 mt3 sans-serif mid-gray">';
s += 'Click to change';
s += '</label>';
s += '<button class="w-100 mv2 white pa3 bn hover-shadow hover-bg-black bg-animate ';
s += `${(null === currentDisplayIndex) ? 'bg-black' : 'bg-gray'}`;
s += '" ';
s += `onclick="setCurrentlyOnScreen(null)" style="cursor: pointer;">`;
s += '<em>Hide everything</em>';
s += '</button>';
for (var i = 0; i < dc.length; i++) {
s += '<button class="w-100 mv2 white pa3 bn hover-shadow hover-bg-black bg-animate ';
s += `${(i == currentDisplayIndex) ? 'bg-black' : 'bg-gray'}`;
s += '" ';
s += `onclick="setCurrentlyOnScreen(${i})" style="cursor: pointer;">`;
s += dc[i].map(x => escapeHtml(x)).join('<br>');
s += '</button>';
}
s += '';
currentForm.innerHTML = s;
}
var form_send = document.getElementById('form_send');
var form_send_fields = document.getElementById('form_send_fields');
function keepCurrentFormUpdated() {
setTimeout(keepCurrentFormUpdated, 250);
updateCurrentForm();
function getTemplateParametersFrom(input) {
return input.value.match(/\{.+?\}/g);
}
setTimeout(keepCurrentFormUpdated, 250);
function setCurrentlyOnScreen(index) {
if (!indexFromWebSocket)
sendThroughWebSocket(JSON.stringify({
type: 'SetCurrentlyOnScreen',
index: index
}));
updateCurrentlyOnScreen(index);
function getTemplateParameters() {
var params = ['primaryText', 'secondaryText'].map(x => document.getElementById(x)).flatMap(x => getTemplateParametersFrom(x));
return Array.from(new Set(params)).sort();
}
function updateCurrentlyOnScreen(index) {
currentDisplayIndex = index;
Element.prototype.insertChildAtIndex = function (child, index) {
if (!index) index = 0;
if (index >= this.children.length || index < 0)
this.appendChild(child);
else
this.insertBefore(child, this.children[index]);
}
function updateSendFields() {
var currentTemplateParameters = getTemplateParameters();
var onScreenTemplateParametersSet = new Set(Array.from(document.getElementsByClassName('variable-field')).map(
it => it.id).map(x => x.split('_', 2)[1]).map(x => `{${x}}`));
var currentTemplateParametersSet = new Set(currentTemplateParameters);
var fieldsToRemove = new Set([...onScreenTemplateParametersSet].filter(x => !currentTemplateParametersSet.has(x)));
var fieldsToAdd = new Set([...currentTemplateParametersSet].filter(x => !onScreenTemplateParametersSet.has(x)));
for (let fieldNameLonger of Array.from(fieldsToRemove)) {
let fieldName = fieldNameLonger.substr(1, fieldNameLonger.length - 2);
let fieldId = `variableField_${fieldName}`;
let field = document.getElementById(fieldId);
let parent = field.parentNode;
parent.removeChild(field);
}
for (let fieldNameLonger of Array.from(fieldsToAdd).sort()) {
let fieldName = fieldNameLonger.substr(1, fieldNameLonger.length - 2);
let containerId = `variableField_${fieldName}`;
let fieldId = `${containerId}_field`;
let labelId = `${containerId}_label`;
let container = document.createElement('div');
container.setAttribute('id', containerId);
container.classList.add('variable-field');
form_send_fields.insertChildAtIndex(container, currentTemplateParameters.indexOf(fieldName));
let label = document.createElement('label');
label.setAttribute('id', labelId);
label.setAttribute('for', fieldId);
label.setAttribute('class', 'f6 b db mb1 mt3 sans-serif mid-gray');
label.innerText = fieldName;
let field = document.createElement('input');
field.setAttribute('type', 'text');
field.setAttribute('id', fieldId);
field.setAttribute('name', containerId);
field.setAttribute('class', 'w-100 f5 pv3 ph3 bg-light-gray bn');
field.setAttribute('onkeyup', 'broadcastChanges()');
field.setAttribute('onkeydown', 'broadcastChanges()');
field.setAttribute('onchange', 'broadcastChanges()');
container.appendChild(label);
container.appendChild(field);
}
}
updateSendFields();
function sendToDisplay() {
sendThroughWebSocket(JSON.stringify({
type: 'SendToDisplay',
settings: getSettings()
}));
}

+ 137
- 135
static/js/display.js View File

@ -3,6 +3,15 @@ var currentDisplayIndex = 0;
var main = document.createElement('main');
document.body.appendChild(main);
if (!test_display)
main.classList.add('transparent');
var section_bg = document.createElement('section');
section_bg.style.zIndex = 1;
main.appendChild(section_bg);
var section_fg = document.createElement('section');
section_fg.style.zIndex = 2;
main.appendChild(section_fg);
var nextStyle = document.getElementById('nextStyle');
@ -53,156 +62,149 @@ function handleBroadcastedMessage(message) {
type: 'CurrentlyOnScreen',
index: currentDisplayIndex
}));
} else if (message.type == 'SetCurrentlyOnScreen') {
currentDisplayIndex = (message.index === null) ? NaN : message.index;
} else if (message.type == 'SendToDisplay') {
enqueueForDisplay(message.settings);
}
console.log(message);
}
function getDisplayContent() {
var dcInput = knownSettings.displayContent;
var dcText = dcInput.trim().replace(/\r/g, '');
var dcCards = dcText.trim().split('\n\n').map(
x => x.trim().split('\n').map(
y => y.trim()
).filter(
y => y.length
)
).filter(x => x.length);
var maxLength = Math.max(...dcCards.map(x => x.length));
for (var dcCard of dcCards)
while (dcCard.length < maxLength)
dcCard.push(nbsp);
return dcCards;
function configureForSettings() {
if (!!knownSettings && test_display) {
configureDisplay(knownSettings);
configureTextContent(knownSettings);
}
}
var escapeHtml = (unsafe) => (unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;"));
function configureForSettings() {
if (!!knownSettings) {
nextStyle.innerHTML = `
section.current {
margin: ${knownSettings.cardMargin}em;
transform: scale(1, 1);
width: ${(knownSettings.containerWidth) - (knownSettings.cardMargin * 2)}em;
height: ${((knownSettings.containerHeight) - (knownSettings.cardMargin * 2)) / 3}em;
}
section.next {
margin-left: 0em;
transform: scale(${knownSettings.cardNextSize / 100}, ${knownSettings.cardNextSize / 100});
width: ${(knownSettings.containerWidth) - (knownSettings.cardMargin * 2)}em;
height: ${((knownSettings.containerHeight) - (knownSettings.cardMargin * 2)) / 3}em;
opacity: ${knownSettings.cardNextOpacity};
}
section:not(.current):not(.next) {
margin: 0px !important;
padding: 0px !important;
transform: scale(0, 0);
width: 0px !important;
height: 0px !important;
opacity: 0;
}
`.trim();
let displayContents = getDisplayContent();
main.style.fontFamily = knownSettings.fontFamily;
main.style.fontSize = `${knownSettings.fontSize}em`;
main.style.width = `${knownSettings.containerWidth}em`;
main.style.height = `${knownSettings.containerHeight}em`;
main.style.backgroundColor = knownSettings.containerBackgroundColor;
main.style.flexDirection = knownSettings.cardDirection;
main.style.transitionProperty = 'all';
main.style.transitionTimingFunction = 'ease-in-out';
main.style.transitionDuration = `${knownSettings.transitionDuration}ms`;
for (let dc_index = 0; dc_index < displayContents.length; dc_index++) {
let displayContent = displayContents[dc_index];
let dc_id = `dc_${dc_index}`;
let dc_element = document.getElementById(dc_id);
if (!dc_element) {
dc_element = document.createElement('section');
dc_element.setAttribute('id', dc_id);
main.appendChild(dc_element);
}
for (let dl_index = 0; dl_index < displayContent.length; dl_index++) {
let displayLine = displayContent[dl_index];
let dl_id = `dc_${dc_index}_dl_${dl_index}`;
let dl_element = document.getElementById(dl_id);
if (!dl_element) {
dl_element = document.createElement('article');
dl_element.setAttribute('id', dl_id);
dc_element.appendChild(dl_element);
}
dl_element.innerText = displayLine;
dl_element.style.color = knownSettings.textColor;
dl_element.style.fontSize = `${Math.pow(parseFloat(knownSettings.subtextSize) / 100, dl_index) * 100}%`;
dl_element.style.opacity = `${Math.pow(parseFloat(knownSettings.subtextOpacity) / 100, dl_index) * 100}`;
dl_element.style.transitionProperty = 'all';
dl_element.style.transitionTimingFunction = 'ease-in-out';
dl_element.style.transitionDuration = `${knownSettings.transitionDuration}ms`;
}
dc_element.style.padding = `${knownSettings.cardPadding}em`;
dc_element.style.borderRadius = `${knownSettings.cardBorderRadius}em`;
dc_element.style.backgroundColor = knownSettings.cardBackgroundColor;
dc_element.style.transitionProperty = 'all';
dc_element.style.transitionTimingFunction = 'ease-in-out';
dc_element.style.transitionDuration = `${knownSettings.transitionDuration}ms`;
for (let dl_element of Array.from(dc_element.getElementsByTagName('article'))) {
let i = parseInt(dl_element.getAttribute('id').split('_')[3]);
if (i < 0 && i >= displayContent.length)
dc_element.removeChild(dl_element);
}
function configureDisplay(settings) {
while (section_bg.firstChild) section_bg.removeChild(section_bg.firstChild);
nextStyle.innerHTML = `
* { transition-duration: ${settings.transitionDuration}ms; }
html > body > main > section > article {
padding-left: ${settings.horizontalPadding}em;
padding-right: ${settings.horizontalPadding}em;
font-family: ${settings.fontFamily};
}
html > body > main > section > article:nth-child(1) {
margin-top: ${settings.marginTopPrimary}em;
font-size: ${settings.fontSizePrimary}em;
font-weight: ${settings.fontWeightPrimary};
line-height: ${settings.lineHeightPrimary};
color: ${settings.colorBasePrimary};
-webkit-text-fill-color: ${settings.colorBasePrimary};
-webkit-text-stroke-width: ${settings.strokeWeightPrimary}em;
-webkit-text-stroke-color: ${settings.strokeColorPrimary};
}
html > body > main > section > article:nth-child(1) > span.template-substitution {
color: ${settings.colorTemplatedPrimary};
-webkit-text-fill-color: ${settings.colorTemplatedPrimary};
}
html > body > main > section > article:nth-child(2) {
margin-top: ${settings.marginTopSecondary}em;
font-size: ${settings.fontSizeSecondary}em;
font-weight: ${settings.fontWeightSecondary};
line-height: ${settings.lineHeightSecondary};
color: ${settings.colorBaseSecondary};
-webkit-text-fill-color: ${settings.colorBaseSecondary};
-webkit-text-stroke-width: ${settings.strokeWeightSecondary}em;
-webkit-text-stroke-color: ${settings.strokeColorSecondary};
}
for (let dc_element of Array.from(main.getElementsByTagName('section'))) {
let i = parseInt(dc_element.getAttribute('id').split('_')[3]);
if (i < 0 && i >= displayContents.length)
main.removeChild(dc_element);
html > body > main > section > article:nth-child(2) > span.template-substitution {
color: ${settings.colorTemplatedSecondary};
-webkit-text-fill-color: ${settings.colorTemplatedSecondary};
}
`.trim();
section_fg.style.width = `${settings.containerWidth}px`;
section_fg.style.height = `${settings.containerHeight}px`;
section_bg.style.backgroundColor = `${settings.backgroundColor}`;
section_bg.style.width = `${settings.containerWidth}px`;
section_bg.style.height = `${settings.containerHeight}px`;
let media = null;
let backgroundMediaString = settings.backgroundMediaHidden || '';
if (backgroundMediaString.startsWith('data:image/')) {
media = document.createElement('img');
} else if (backgroundMediaString.startsWith('data:video/')) {
media = document.createElement('video');
media.setAttribute('autoplay', '');
media.setAttribute('playsinline', '');
media.setAttribute('muted', '');
}
if (!!media) {
media.setAttribute('src', backgroundMediaString);
media.style.width = '100%';
media.style.height = '100%';
media.style.objectFit = 'contain';
media.style.objectPosition = 'center';
section_bg.appendChild(media);
}
}
function configureTextContent(settings) {
while (section_fg.firstChild) section_fg.removeChild(section_fg.firstChild);
let primarySubstituted = doSubstitution(settings, 'primaryText');
let secondarySubstituted = doSubstitution(settings, 'secondaryText');
section_fg.appendChild(renderSubstituted(primarySubstituted));
section_fg.appendChild(renderSubstituted(secondarySubstituted));
}
function keepCardsUpdatedByTime() {
if (!knownSettings)
setTimeout(keepCardsUpdatedByTime, 100);
else {
setTimeout(keepCardsUpdatedByTime, parseFloat(knownSettings.cardTimeout) * 1000);
let displayContents = getDisplayContent();
if (!isNaN(currentDisplayIndex))
currentDisplayIndex = (++currentDisplayIndex) % displayContents.length;
function doSubstitution(settings, templateName) {
let template = [[false, settings[templateName]]];
for (let templateParam of settings.templateParams) {
let templateParamName = templateParam.substr(1, templateParam.length - 2);
let renderAsValue = settings[`variableField_${templateParamName}`];
template = template.flatMap(x => x[0] ? [x] : [x[1]].flatMap(y => {
let a = y.split(templateParam).reduce((arr, z) => [...arr, [false, z], [true, renderAsValue]], []);
return a.splice(0, a.length - 1);
}));
}
return template.filter(x => x[1].length);
}
keepCardsUpdatedByTime();
function keepCardsUpdated() {
if (!knownSettings)
setTimeout(keepCardsUpdated, 100);
else {
setTimeout(keepCardsUpdated, 100);
let displayContents = getDisplayContent();
let nextDisplayIndex = (currentDisplayIndex + 1) % displayContents.length;
if (isNaN(currentDisplayIndex))
nextDisplayIndex = NaN;
if (parseFloat(knownSettings.cardNextOpacity) == 0.0 || parseFloat(knownSettings.cardNextSize) == 0.0) {
nextDisplayIndex = NaN;
}
for (let dc_index = 0; dc_index < displayContents.length; dc_index++) {
let dc_id = `dc_${dc_index}`;
let dc_element = document.getElementById(dc_id);
if (dc_index === currentDisplayIndex) {
dc_element.setAttribute("class", "current");
} else if (dc_index === nextDisplayIndex) {
dc_element.setAttribute("class", "next");
} else {
dc_element.setAttribute("class", "");
function renderSubstituted(listOfLists) {
let container = document.createElement('article');
for (let tuple of listOfLists) {
if (!tuple[0]) {
container.appendChild(document.createTextNode(tuple[1]));
} else {
let subcontainer = document.createElement('span');
subcontainer.classList.add('template-substitution');
let text = tuple[1];
for (let i = 0; i < text.length; i++) {
let char = text[i];
let charcontainer = document.createElement('span');
charcontainer.setAttribute('style', `--i: ${i};`);
charcontainer.appendChild(document.createTextNode(char));
subcontainer.appendChild(charcontainer);
}
container.appendChild(subcontainer);
}
sendThroughWebSocket(JSON.stringify({
type: 'CurrentlyOnScreen',
index: currentDisplayIndex
}));
}
return container;
}
keepCardsUpdated();
var displayQueue = [];
function enqueueForDisplay(settings) {
if (!test_display) {
let emptyDisplayQueue = displayQueue.length == 0;
displayQueue.push(settings);
if (emptyDisplayQueue)
processDisplayQueue();
}
}
function processDisplayQueue() {
if (displayQueue.length > 0) {
let settings = displayQueue[0];
configureDisplay(settings);
configureTextContent(settings);
main.classList.remove('transparent');
setTimeout(() => {
setTimeout(() => {
main.classList.add('transparent');
setTimeout(() => {
displayQueue.shift();
processDisplayQueue();
}, settings.transitionDuration);
}, settings.onScreenDuration * 1000);
}, settings.transitionDuration);
}
}

Loading…
Cancel
Save