Complete overhaul of Printer Bot dock

First stage of widget-dock-core
This commit is contained in:
nuttylmao
2025-09-06 01:08:14 +10:00
parent befefc0eb3
commit 7e20a30c67
21 changed files with 1740 additions and 969 deletions
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 2000 1556" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(30.772,0,0,30.772,323.017,878.74)">
<path d="M32.927,8.977L22,22L11.073,8.977C14.134,6.408 18.003,5 22,5C25.997,5 29.866,6.408 32.927,8.977ZM-5.271,-10.5L-10.497,-16.729C-1.392,-24.369 10.114,-28.557 22,-28.557C33.886,-28.557 45.392,-24.369 54.497,-16.729L49.271,-10.5C41.63,-16.912 31.974,-20.426 22,-20.426C12.026,-20.426 2.37,-16.912 -5.271,-10.5ZM4.373,0.993L0.145,-4.046C6.269,-9.184 14.007,-12 22,-12C29.993,-12 37.731,-9.184 43.855,-4.046L39.627,0.993C34.688,-3.151 28.447,-5.422 22,-5.422C15.553,-5.422 9.312,-3.151 4.373,0.993Z" style="fill:rgb(0,230,118);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1865 1556" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(86.4291,0,0,86.4291,-901.44,-432.146)">
<path d="M32,9L32,15L30,15L30,9L32,9ZM32,17L32,19L30,19L30,17L32,17ZM28.048,9.158C26.155,8.331 24.097,7.895 22,7.895C18.449,7.895 15.011,9.146 12.291,11.429L10.43,9.211C13.672,6.491 17.768,5 22,5C24.923,5 27.782,5.712 30.339,7.048L30,7.048C28.922,7.048 28.048,7.922 28.048,9L28.048,9.158ZM28.082,15.362C26.355,13.988 24.212,13.237 22,13.237C19.705,13.237 17.483,14.045 15.724,15.521L14.219,13.727C16.399,11.897 19.154,10.895 22,10.895C24.135,10.895 26.22,11.459 28.048,12.514L28.048,15C28.048,15.124 28.06,15.245 28.082,15.362ZM25.891,18.363L22,23L18.109,18.363C19.2,17.449 20.577,16.947 22,16.947C23.423,16.947 24.8,17.449 25.891,18.363Z" style="fill:rgb(221,44,0);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+86
View File
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" href="#" />
<link rel="stylesheet" href="../../styles/global.css" />
<link rel="stylesheet" href="./style.css" />
<script type="text/javascript" src="https://unpkg.com/@streamerbot/client/dist/streamerbot-client.js"></script>
<script src="https://cdn.jsdelivr.net/npm/luxon@3/build/global/luxon.min.js"></script>
</head>
<body>
<!-- Header -->
<div id="header">
<img src="../../resources/logo.png" style="height: 40px;" />
<label id="title" class="title"></label>
<div id="header-end">
<button id="sb-actions-button" onclick="OpenRequiredActionsDialog()">
</button>
<button id="sb-status-button" onclick="OpenConnectDialog()">
<img id="sb-status-icon" style="height: 1em;" />
</button>
</div>
</div>
<!-- Page Contents -->
<iframe id="content">
</iframe>
<!-- Blur Layer -->
<div id="blur-layer"></div>
<!-- Streamer.bot Required Actions Dialog -->
<div id="sb-required-actions-dialog" class="dialog">
<button class="dialog-nav-button" onclick="CloseRequiredActionsDialog()">×</button>
<label id="sb-required-actions-success">✅ All actions found</label>
<label id="sb-required-actions-failure">⚠️ You are missing Streamer.bot actions</label>
<label id="sb-required-actions-failure-subtext" class="setting-description">The following actions were not found in Streamer.bot:</label>
<div id="sb-required-actions-list" class="callout">
</div>
<label class="setting-description">Please import the following </label>
<div id="sb-import-wrapper">
<label id="sb-import-code" class="callout"></label>
<button id="sb-import-copy-button" onclick="CopyImportCode()">Copy</button>
</div>
<button onclick="CheckRequiredActions()">Check Again</button>
</div>
<!-- Streamer.bot Connect Dialog -->
<div id="sb-connect-dialog" class="dialog">
<hr class="divider">
<div style="display: flex; flex-direction: row; gap: 1em">
<div class="field">
<label for="sb-address">Address *</label>
<input type="text" id="sb-address" name="sb-address" placeholder="127.0.0.1" value="127.0.0.1">
</div>
<div class="field">
<label for="sb-port">Port *</label>
<input type="text" id="sb-port" name="sb-port" placeholder="8080" value="8080">
</div>
</div>
<div class="field">
<label for="sb-password">Password</label>
<label class="setting-description" for="sb-password">(Optional)</label>
<input type="password" id="sb-password" name="sb-password">
</div>
<hr class="divider">
<label id="sb-error-label"></label>
<button id="sb-connect-button" onclick="Connect()">Connect</button>
</div>
</body>
<script src="./script.js"></script>
+271
View File
@@ -0,0 +1,271 @@
////////////////
// PARAMETERS //
////////////////
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const configJson = urlParams.get("config") || "";
///////////////////
// PAGE ELEMENTS //
///////////////////
const sbConnectDialog = document.getElementById('sb-connect-dialog');
const sbAddressInput = document.getElementById('sb-address');
const sbPortInput = document.getElementById('sb-port');
const sbPasswordInput = document.getElementById('sb-password');
const sbErrorLabel = document.getElementById('sb-error-label');
const sbRequiredActionsDialog = document.getElementById('sb-required-actions-dialog');
const sbRequiredActionsSuccessLabel = document.getElementById('sb-required-actions-success');
const sbRequiredActionsFailureLabel = document.getElementById('sb-required-actions-failure');
const sbRequiredActionsFailureSubtext = document.getElementById('sb-required-actions-failure-subtext');
const sbRequiredActionsList = document.getElementById('sb-required-actions-list');
const sbImportCodeLabel = document.getElementById('sb-import-code');
const sbImportCopyButton = document.getElementById('sb-import-copy-button');
const sbActionsButton = document.getElementById('sb-actions-button');
const sbStatusButton = document.getElementById('sb-status-button');
const sbStatusIcon = document.getElementById('sb-status-icon');
const blurLayer = document.getElementById('blur-layer');
/////////////////////////
// STREAMER.BOT CLIENT //
/////////////////////////
// Check local storage
if (localStorage.getItem('sbServerAddress') === null)
localStorage.setItem('sbServerAddress', '127.0.0.1');
if (localStorage.getItem('sbServerPort') === null)
localStorage.setItem('sbServerPort', '8080');
sbAddressInput.value = localStorage.getItem('sbServerAddress');
sbPortInput.value = localStorage.getItem('sbServerPort');
sbPasswordInput.value = localStorage.getItem('sbServerPassword');
const sbServerAddress = sbAddressInput.value;
const sbServerPort = sbPortInput.value;
const sbServerPassword = sbPasswordInput.value;
window.sbClient = new StreamerbotClient({
host: sbServerAddress,
port: sbServerPort,
password: sbServerPassword,
immediate: true,
onConnect: (data) => {
console.log(`Streamer.bot successfully connected to ${sbServerAddress}:${sbServerPort}`)
console.debug(data);
SetConnectionState(true);
},
onDisconnect: () => {
console.error(`Streamer.bot disconnected from ${sbServerAddress}:${sbServerPort}`)
SetConnectionState(false);
},
onError: (err) => {
console.error(`Streamer.bot disconnected from ${sbServerAddress}:${sbServerPort}`)
SetErrorMessage(err);
}
});
function SetConnectionState(isConnected) {
if (isConnected) {
localStorage.setItem('sbServerAddress', sbAddressInput.value);
localStorage.setItem('sbServerPort', sbPortInput.value);
localStorage.setItem('sbServerPassword', sbPasswordInput.value);
sbConnectDialog.style.display = "none";
blurLayer.style.display = "none";
sbErrorLabel.style.display = 'none';
sbStatusIcon.src = 'icons/connected.svg';
sbStatusButton.title = `Connected to ${window.sbClient.info.name} (${window.sbClient.info.version})`;
// Check required actions
CheckRequiredActions();
}
else {
sbConnectDialog.style.display = "flex";
blurLayer.style.display = "block";
sbStatusIcon.src = 'icons/disconnected.svg';
SetErrorMessage('Disconnected from Streamer.bot');
}
}
function SetErrorMessage(error) {
sbErrorLabel.textContent = error;
sbErrorLabel.style.display = 'block';
}
////////////
// CONFIG //
////////////
if (configJson) {
// Set the header/title of the page
fetch(configJson)
.then(res => res.json())
.then(config => {
const root = document.documentElement;
const domain = getComputedStyle(root).getPropertyValue('--domain').trim();
// Set the page title
parent.document.title = `${domain}${config.title}`;
// Set the title label
title.textContent = config.title;
})
.catch(err => console.error('Failed to load config:', err));
}
async function CheckRequiredActions() {
if (configJson) {
console.debug('Checking required actions...')
// Set the header/title of the page
fetch(configJson)
.then(res => res.json())
.then(async config => {
// Clear the required actions list
const sbRequiredActionsList = document.getElementById('sb-required-actions-list');
sbRequiredActionsList.innerHTML = '';
// Set the import code
sbImportCodeLabel.textContent = config.sbImportCode;
// Assume all actions are found
SetRequiredActionState(true);
// Check each required action
if (config.requiredSbActions) {
// Get a full list of actions currently installed in Streamer.bot
const response = await sbClient.getActions();
// Iterate over required SB actions and check if they're present
config.requiredSbActions.forEach(req => {
const exists = response.actions.some(act => act.id === req.id);
console.debug(`${req.name}: ${exists ? 'Found' : 'Missing'}`)
// As soon as one is found that doesn't exist, throw up warning
if (!exists)
SetRequiredActionState(false);
// container div
const item = document.createElement('div');
item.className = 'sb-required-action';
// name label
const nameLabel = document.createElement('label');
nameLabel.textContent = req.name;
// status label
const statusLabel = document.createElement('label');
statusLabel.className = 'sb-required-action-found';
//statusLabel.textContent = exists ? '✅' : '❌';
statusLabel.textContent = exists ? 'Found' : 'Missing';
statusLabel.style.color = exists ? '#00d26a' : '#f92f60';
statusLabel.style.fontWeight = 500;
// append labels to div
item.appendChild(nameLabel);
item.appendChild(statusLabel);
// append div to list
sbRequiredActionsList.appendChild(item);
});
}
else
{
// There are no required actions, so hide the button
sbActionsButton.style.display = 'none';
}
})
.catch(err => console.error('Failed to load config:', err));
}
}
function SetRequiredActionState(isSuccess) {
if (isSuccess)
{
sbRequiredActionsSuccessLabel.style.display = 'block';
sbRequiredActionsFailureLabel.style.display = 'none';
sbRequiredActionsFailureSubtext.style.display = 'none';
sbActionsButton.title = `All actions found`;
sbActionsButton.textContent = '✅';
}
else
{
sbRequiredActionsSuccessLabel.style.display = 'none';
sbRequiredActionsFailureLabel.style.display = 'block';
sbRequiredActionsFailureSubtext.style.display = 'block';
sbActionsButton.title = `You are missing Streamer.bot actions`;
sbActionsButton.textContent = '⚠️';
OpenRequiredActionsDialog();
}
}
///////////////////////
// PAGE INTERACTIONS //
///////////////////////
function Connect() {
sbClient.options.host = sbAddressInput.value;
sbClient.options.port = sbPortInput.value;
sbClient.options.password = sbPasswordInput.value;
sbClient.connect();
}
function CopyImportCode() {
const textToCopy = sbImportCodeLabel.textContent;
navigator.clipboard.writeText(textToCopy)
.then(() => {
console.debug('Copied to clipboard!');
// Click feedback
const root = document.documentElement;
const successColor = getComputedStyle(root).getPropertyValue('--success-color').trim();
const buttonColor = getComputedStyle(root).getPropertyValue('--button-color').trim();
sbImportCopyButton.textContent = 'Copied!';
sbImportCopyButton.style.background = successColor;
setTimeout(() => {
sbImportCopyButton.textContent = 'Copy';
sbImportCopyButton.style.background = buttonColor;
}, 1500);
})
.catch(err => console.error('Failed to copy text: ', err));
}
function OpenConnectDialog() {
sbConnectDialog.style.display = "flex";
blurLayer.style.display = "block";
}
function OpenRequiredActionsDialog() {
sbRequiredActionsDialog.style.display = "flex";
blurLayer.style.display = "block";
}
function CloseRequiredActionsDialog() {
sbRequiredActionsDialog.style.display = "none";
blurLayer.style.display = "none";
}
+118
View File
@@ -0,0 +1,118 @@
html,
body {
height: 100%;
display: flex;
flex-direction: column;
}
/**************/
/*** HEADER ***/
/**************/
#header {
display: flex;
flex-direction: row;
align-items: center;
padding: 1em;
gap: 1em;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 1);
}
#header-end {
margin-left: auto;
align-items: center;
display: flex;
flex-direction: row;
gap: 1em;
}
#header-end button {
padding: 0.2em;
}
/*********************/
/*** PAGE CONTENTS ***/
/*********************/
#content {
flex: 1;
overflow: auto;
margin: 1em;
}
/******************/
/*** BLUR LAYER ***/
/******************/
#blur-layer {
position: fixed;
inset: 0;
/* top:0; left:0; bottom:0; right:0 */
backdrop-filter: blur(5px);
/* blur the content behind */
-webkit-backdrop-filter: blur(5px);
/* Safari support */
z-index: 1;
/* behind the modal */
}
/********************************/
/*** STREAMER.BOT CONNECT BOX ***/
/********************************/
#sb-required-actions-dialog {
max-width: 30em;
display: none;
}
#sb-connect-button:enabled:hover {
background-color: #3be477;
}
#sb-error-label {
background: #a82d2e33;
border-radius: 0.5em;
padding: 1em;
color: #c65e5e;
display: none;
}
/******************************************/
/*** STREAMER.BOT REQUIRED ACTIONS LIST ***/
/******************************************/
.sb-required-action {
display: flex;
flex-direction: row;
align-items: center;
}
.sb-required-action-found {
margin-left: auto;
}
#sb-import-code {
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
max-width: 100%;
padding: 0.5em;
font-family: monospace;
font-size: 1em;
}
#sb-import-wrapper {
display: flex;
align-items: center;
gap: 0.5em;
}