diff --git a/nodecg-io-debug/dashboard/debug-helper.css b/nodecg-io-debug/dashboard/debug-helper.css new file mode 100644 index 000000000..896410f3e --- /dev/null +++ b/nodecg-io-debug/dashboard/debug-helper.css @@ -0,0 +1,174 @@ +.container { + display: inline-block; + border: 1px dashed rgb(64, 64, 64); + background-color: rgb(44, 44, 44); + padding: 0; + margin: 10px; + min-width: 200px; + min-height: 100px; + box-shadow: 6px 5px 15px 3px rgba(0, 0, 0, 0.52); +} + +.containerHead { + display: block; + color: #92d6d6; + padding-top: 1px; + font-size: large; + border-bottom: 1px dashed rgb(64, 64, 64); + background-color: rgb(55, 55, 55); +} + +.containerTitle { + font-weight: bold; +} + +.containerEvent { + font-style: italic; + float: right; + margin-left: 30px; +} + +.containerBody { + padding-top: 10px; + padding-bottom: 10px; + font-size: large; +} + +.containerHead, +.containerBody { + padding-left: 10px; + padding-right: 10px; +} + +body { + float: left; + display: flex; + flex-wrap: wrap; +} + +button { + border: 1px solid gray; + color: white; + background-color: rgb(70, 70, 70); + font-size: large; +} + +button:active { + background-color: rgb(90, 90, 90); +} + +#event_click button, +#event_bool button { + height: 80px; + width: 80px; +} + +#event_number button { + height: 30px; + padding-left: 15px; + padding-right: 15px; +} + +.button1 { + background-color: #45586e; +} +.button2 { + background-color: #4c6c78; +} +.button3 { + background-color: #426161; +} +.button4 { + background-color: #4c786b; +} +.button5 { + background-color: #456e57; +} + +.button1:active { + background-color: #506780; +} +.button2:active { + background-color: #577c8a; +} +.button3:active { + background-color: #4e7373; +} +.button4:active { + background-color: #578a7b; +} +.button5:active { + background-color: #508065; +} + +.smallInfo { + font-size: smaller; +} + +input[type="number"] { + font-size: larger; + color: white; + font-family: "Consolas", sans-serif; + background-color: rgb(70, 70, 70); + border: 1px solid rgb(20, 20, 20); + width: 80px; +} + +.inlineContainer { + margin-top: 10px; +} + +#number_input1_send, +#text_oneline_send, +#text_multiline_send, +#list_list_send { + padding: 5px !important; + margin-left: 5px; +} + +input[type="range"] { + width: 75%; + margin-right: 20px; +} + +input[type="color"] { + width: 80px; + height: 80px; +} + +input[type="date"], +input[type="datetime-local"] { + font-size: larger; + color: white; + background-color: rgb(70, 70, 70); + border: 1px solid rgb(20, 20, 20); +} + +input[type="text"], +textarea { + font-size: larger; + color: white; + background-color: rgb(70, 70, 70); + border: 1px solid rgb(20, 20, 20); + width: 200px; +} + +.area { + resize: none; + height: 100px; +} + +.lists { + resize: none; + height: 120px; +} + +.flex-fill { + flex: 1; +} + +#instanceMonaco { + height: 300px; + width: 500px; + font-size: larger; +} diff --git a/nodecg-io-debug/dashboard/debug-helper.html b/nodecg-io-debug/dashboard/debug-helper.html new file mode 100644 index 000000000..fd196e717 --- /dev/null +++ b/nodecg-io-debug/dashboard/debug-helper.html @@ -0,0 +1,174 @@ + + + + + + + +
+
+ Clicks + onClick, onclick[1-5] +
+
+ + + + + +
+
+ +
+
+ Numbers + onNumber +
+
+ + + + + +
+ + +
+
+ + (number sent automatically) +
+
+
+ +
+
+ Ranges + onRange(0to100|0to1|M1to1) +
+
+ 50 +
+ 0.5 +
+
+ 0 +
+
+ +
+ +
+
+ Colors + onColor +
+
+ +
+
+ +
+
+ Date + onDate +
+
+ +
+ +
+
+ +
+ +
+
+ Booleans + onBool +
+
+ + +
+
+ +
+
+ Strings + onText +
+
+
+ + +
+
+ + +
+
+
+ +
+
+ Lists + onList +
+
+
+ + +
+ (List entries are comma separated) +
+
+ +
+
+ JSON + onJSON +
+
+
+ + + + +
+
+ + + + diff --git a/nodecg-io-debug/dashboard/debug-helper.js b/nodecg-io-debug/dashboard/debug-helper.js new file mode 100644 index 000000000..be09559a5 --- /dev/null +++ b/nodecg-io-debug/dashboard/debug-helper.js @@ -0,0 +1,79 @@ +/* eslint-disable no-undef */ + +// Buttons +for (let i = 1; i <= 5; i++) { + document.querySelector(`#click_button${i}`).onclick = () => { + nodecg.sendMessage("onClick", i); + }; +} + +// Numbers +for (let i = 0; i < 5; i++) { + const num = 10 ** i; + document.querySelector(`#number_button${num}`).onclick = () => { + nodecg.sendMessage("onNumber", num); + }; +} +document.querySelector(`#number_input1_send`).onclick = () => { + const num = document.querySelector('#number_input1').value; + nodecg.sendMessage("onNumber", num); +}; +document.querySelector(`#number_input2`).onchange = () => { + const num = document.querySelector('#number_input2').value; + nodecg.sendMessage("onNumber", num); +}; + +// Ranges +for (const range of ["0to100", "0to1", "M1to1"]) { + document.querySelector("#range_" + range).addEventListener("change", (e) => { + const num = e.target.value; + nodecg.sendMessage(`onRange${range}`, num); + }); +} + +// Color +document.querySelector("#color_color").addEventListener("input", (e) => { + const color = e.target.value; + nodecg.sendMessage(`onColor`, color); +}); + +// Dates +for (const element of ["#date_date", "#date_datetime"]) { + document.querySelector(element).addEventListener("change", (e) => { + const date = e.target.value; + nodecg.sendMessage(`onDate`, date); + }); +} + +// Booleans +document.querySelector("#bool_false").onclick = () => { + nodecg.sendMessage("onBool", false); +}; +document.querySelector("#bool_true").onclick = () => { + nodecg.sendMessage("onBool", true); +}; + +// Text +for (const element of ["oneline", "multiline"]) { + document.querySelector(`#text_${element}_send`).onclick = () => { + const value = document.querySelector(`#text_${element}`).value; + nodecg.sendMessage("onText", value); + }; +} + +// Lists +document.querySelector("#list_list_send").onclick = () => { + const value = document.querySelector("#list_list").value; + nodecg.sendMessage("onList", value); +}; + +// JSON +document.querySelector("#json_send").onclick = () => { + const jsonString = window.debugMonacoEditor.getValue(); + try { + const json = JSON.parse(jsonString); + nodecg.sendMessage("onJSON", json); + } catch (e) { + nodecg.log.error(`Cannot send invalid json: ${e}`) + } +}; \ No newline at end of file diff --git a/nodecg-io-debug/extension/debugHelper.ts b/nodecg-io-debug/extension/debugHelper.ts new file mode 100644 index 000000000..34390acbe --- /dev/null +++ b/nodecg-io-debug/extension/debugHelper.ts @@ -0,0 +1,130 @@ +import { EventEmitter } from "events"; +import { NodeCG } from "nodecg/types/server"; + +export interface Color { + red: number; + green: number; + blue: number; +} + +export class DebugHelper extends EventEmitter { + constructor(nodecg: NodeCG) { + super(); + nodecg.log.info("DebugHelper is ready to help debugging."); + + // Registering all listeners and defining redirection + nodecg.listenFor("onClick", (value) => { + this.emit("onClick"); + this.emit(`onClick${value}`); + }); + + nodecg.listenFor("onNumber", (value) => { + this.emit("onNumber", parseInt(value)); + }); + + for (const range of ["0to100", "0to1", "M1to1"]) { + nodecg.listenFor(`onRange${range}`, (value) => { + this.emit(`onRange${range}`, parseFloat(value)); + }); + } + + nodecg.listenFor("onColor", (value) => { + this.emit("onColor", DebugHelper.hexToRGB(value)); + }); + + nodecg.listenFor("onDate", (value) => { + this.emit("onDate", new Date(value)); + }); + + nodecg.listenFor("onBool", (value) => { + this.emit("onBool", value); + }); + + nodecg.listenFor("onText", (value) => { + this.emit("onText", value); + }); + + nodecg.listenFor("onList", (value) => { + const list = (value as string).split(","); + this.emit("onList", list); + }); + + nodecg.listenFor("onJSON", (value) => { + this.emit("onJSON", value); + }); + } + + private static hexToRGB(hex: string): Color { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result + ? { + red: parseInt(result[1], 16), + green: parseInt(result[2], 16), + blue: parseInt(result[3], 16), + } + : { red: 0, green: 0, blue: 0 }; + } + + static createClient(nodecg: NodeCG): DebugHelper { + return new DebugHelper(nodecg); + } + + // Custom register handler functions + + onClick(listener: () => void): void { + this.on("onClick", listener); + } + onClick1(listener: () => void): void { + this.on("onClick1", listener); + } + onClick2(listener: () => void): void { + this.on("onClick2", listener); + } + onClick3(listener: () => void): void { + this.on("onClick3", listener); + } + onClick4(listener: () => void): void { + this.on("onClick4", listener); + } + onClick5(listener: () => void): void { + this.on("onClick5", listener); + } + + onNumber(listener: (value: number) => void): void { + this.on("onNumber", listener); + } + + onRange0to100(listener: (value: number) => void): void { + this.on("onRange0to100", listener); + } + onRange0to1(listener: (value: number) => void): void { + this.on("onRange0to1", listener); + } + onRangeM1to1(listener: (value: number) => void): void { + this.on("onRangeM1to1", listener); + } + + onColor(listener: (value: Color) => void): void { + this.on("onColor", listener); + } + + onDate(listener: (value: Date) => void): void { + this.on("onDate", listener); + } + + onBool(listener: (value: boolean) => void): void { + this.on("onBool", listener); + } + + onText(listener: (value: string) => void): void { + this.on("onText", listener); + } + + onList(listener: (value: Array) => void): void { + this.on("onList", listener); + } + + onJSON(listener: (value: unknown) => void): void { + this.on("onJSON", listener); + } +} diff --git a/nodecg-io-debug/extension/index.ts b/nodecg-io-debug/extension/index.ts new file mode 100644 index 000000000..dd5bc1202 --- /dev/null +++ b/nodecg-io-debug/extension/index.ts @@ -0,0 +1,35 @@ +import { NodeCG } from "nodecg/types/server"; +import { Result, emptySuccess, success, ServiceBundle } from "nodecg-io-core"; +import { DebugHelper } from "./debugHelper"; + +export type DebugConfig = { + // Nothing to configure +}; + +export { DebugHelper } from "./debugHelper"; + +module.exports = (nodecg: NodeCG) => { + new DebugService(nodecg, "debug", __dirname, "../schema.json").register(); +}; + +class DebugService extends ServiceBundle { + async validateConfig(_: DebugConfig): Promise> { + return emptySuccess(); + } + + async createClient(_: DebugConfig): Promise> { + const client = DebugHelper.createClient(this.nodecg); + this.nodecg.log.info("Successfully created debug helper."); + return success(client); + } + + stopClient(_: DebugHelper): void { + this.nodecg.log.info("Successfully stopped debug client."); + } + + removeHandlers(client: DebugHelper): void { + client.removeAllListeners(); + } + + requiresNoConfig = true; +} diff --git a/nodecg-io-debug/package-lock.json b/nodecg-io-debug/package-lock.json new file mode 100644 index 000000000..f79fd2286 --- /dev/null +++ b/nodecg-io-debug/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "nodecg-io-debug", + "version": "0.2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "monaco-editor": "^0.23.0" + } + }, + "node_modules/monaco-editor": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.23.0.tgz", + "integrity": "sha512-q+CP5zMR/aFiMTE9QlIavGyGicKnG2v/H8qVvybLzeFsARM8f6G9fL0sMST2tyVYCwDKkGamZUI6647A0jR/Lg==" + } + }, + "dependencies": { + "monaco-editor": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.23.0.tgz", + "integrity": "sha512-q+CP5zMR/aFiMTE9QlIavGyGicKnG2v/H8qVvybLzeFsARM8f6G9fL0sMST2tyVYCwDKkGamZUI6647A0jR/Lg==" + } + } +} diff --git a/nodecg-io-debug/package.json b/nodecg-io-debug/package.json new file mode 100644 index 000000000..53ee2e3af --- /dev/null +++ b/nodecg-io-debug/package.json @@ -0,0 +1,56 @@ +{ + "name": "nodecg-io-debug", + "version": "0.2.0", + "description": "Debug helper service that helps to easily trigger your code for debugging purposes.", + "homepage": "https://nodecg.io/samples/debug", + "author": { + "name": "CodeOverflow team", + "url": "https://github.com/codeoverflow-org" + }, + "repository": { + "type": "git", + "url": "https://github.com/codeoverflow-org/nodecg-io.git", + "directory": "nodecg-io-debug" + }, + "main": "extension", + "scripts": { + "build": "tsc -b", + "watch": "tsc -b -w", + "clean": "tsc -b --clean" + }, + "keywords": [ + "nodecg-io", + "nodecg-bundle" + ], + "nodecg": { + "compatibleRange": "^1.1.1", + "bundleDependencies": { + "nodecg-io-core": "^0.2.0" + }, + "dashboardPanels": [ + { + "name": "nodecg-io-debug-helper", + "title": "Debug Helper", + "file": "debug-helper.html", + "fullbleed": true, + "headerColor": "#527878" + } + ], + "mount": [ + { + "directory": "node_modules", + "endpoint": "monaco" + } + ] + }, + "license": "MIT", + "devDependencies": { + "@types/node": "^14.14.33", + "nodecg": "^1.8.1", + "typescript": "^4.2.3" + }, + "dependencies": { + "nodecg-io-core": "^0.2.0", + "monaco-editor": "^0.23.0" + } +} diff --git a/nodecg-io-debug/schema.json b/nodecg-io-debug/schema.json new file mode 100644 index 000000000..21dca84a4 --- /dev/null +++ b/nodecg-io-debug/schema.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": {}, + "required": [] +} diff --git a/nodecg-io-debug/tsconfig.json b/nodecg-io-debug/tsconfig.json new file mode 100644 index 000000000..1c8405620 --- /dev/null +++ b/nodecg-io-debug/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.common.json" +} diff --git a/samples/debug/extension/index.ts b/samples/debug/extension/index.ts new file mode 100644 index 000000000..046e95114 --- /dev/null +++ b/samples/debug/extension/index.ts @@ -0,0 +1,53 @@ +import { NodeCG } from "nodecg/types/server"; +import { DebugHelper } from "nodecg-io-debug"; +import { requireService } from "nodecg-io-core"; + +module.exports = function (nodecg: NodeCG) { + nodecg.log.info("Sample bundle for the debug service started."); + + const debug = requireService(nodecg, "debug"); + + debug?.onAvailable((debug) => { + nodecg.log.info("Debug service available."); + + debug.onClick(() => { + nodecg.log.info(`Received in 'onClick'`); + }); + + debug.onNumber((value) => { + nodecg.log.info(`Received in 'onNumber' with number: ${value}`); + }); + + debug.onRange0to100((value) => { + nodecg.log.info(`Received in 'onRange0to100' with value: ${value}`); + }); + + debug.onColor((value) => { + nodecg.log.info(`Received in 'onColor' with [red,green,blue]: [${value.red},${value.green},${value.blue}]`); + }); + + debug.onDate((value) => { + nodecg.log.info(`Received in 'onDate' with date: ${value}`); + }); + + debug.onBool((value) => { + nodecg.log.info(`Received in 'onBool' with boolean: ${value}`); + }); + + debug.onText((value) => { + nodecg.log.info(`Received in 'onText' with string: ${value}`); + }); + + debug.onList((value) => { + nodecg.log.info(`Received in 'onList' with entries: [${value.join(",")}]`); + }); + + debug.onJSON((value) => { + nodecg.log.info(`Received in 'onJSON' with JSON: ${JSON.stringify(value)}`); + }); + }); + + debug?.onUnavailable(() => { + nodecg.log.info("Debug service unavailable."); + }); +}; diff --git a/samples/debug/package.json b/samples/debug/package.json new file mode 100644 index 000000000..ef1d599a7 --- /dev/null +++ b/samples/debug/package.json @@ -0,0 +1,24 @@ +{ + "name": "debug", + "version": "0.2.0", + "private": true, + "nodecg": { + "compatibleRange": "^1.1.1", + "bundleDependencies": { + "nodecg-io-debug": "^0.2.0" + } + }, + "scripts": { + "build": "tsc -b", + "watch": "tsc -b -w", + "clean": "tsc -b --clean" + }, + "license": "MIT", + "dependencies": { + "@types/node": "^14.14.33", + "nodecg": "^1.8.1", + "nodecg-io-core": "^0.2.0", + "nodecg-io-debug": "^0.2.0", + "typescript": "^4.2.3" + } +} diff --git a/samples/debug/tsconfig.json b/samples/debug/tsconfig.json new file mode 100644 index 000000000..c8bb01bee --- /dev/null +++ b/samples/debug/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.common.json" +}