Initial commit

This commit is contained in:
2023-08-29 19:55:48 +02:00
commit 7c2eec4446
473 changed files with 40947 additions and 0 deletions

101
Frontend/gulpfile.js Normal file
View File

@@ -0,0 +1,101 @@
const gulp = require('gulp');
const path = require('path');
const fs = require('fs-extra');
const CC = require('@meesvdw/coloredconsole');
const ts = require('gulp-typescript');
const concat = require('gulp-concat');
const cleanCSS = require('gulp-clean-css');
const uglify = require('gulp-uglify');
const rename = require('gulp-rename');
const { argv } = require('process');
var sass = require('gulp-sass')(require('sass'));
var tap = require('gulp-tap');
var browserify = require('browserify');
var buffer = require('gulp-buffer');
exports.default = () => {
var pageIndex = argv.includes('-page') ? argv.indexOf('-page') : null;
var pageName = pageIndex != null && pageIndex + 1 < argv.length ? argv[pageIndex + 1] : null;
var layoutIndex = argv.includes('-layout') ? argv.indexOf('-layout') : null;
var layoutName = layoutIndex != null && layoutIndex + 1 < argv.length ? argv[layoutIndex + 1] : null;
if (pageName == null && layoutName == null)
return console.log(
CC.red +
CC.bright +
`\n\nIncorrect syntax. Please use '-page [pagename]' or '-layout [layoutname]'\n\n` +
CC.reset
);
var directoryType = pageName ? 'Page' : 'Layout';
var directoryName = pageName ? pageName : layoutName;
var directoryQuery = pageName ? pageName : `layouts/${layoutName}`;
var basePath = path.join(__filename, '..', 'pages', directoryQuery);
fs.pathExists(basePath, (err, exists) => {
if (exists == false)
return console.log(
CC.red + CC.bright + `\n\n${directoryType} ${directoryName} does not exist\n\n` + CC.reset
);
else {
buildTypescript(basePath, () =>
buildSass(basePath, () => {
console.log(CC.green + CC.bright + `Watcher running` + CC.reset);
gulp.watch(
[
`pages/${directoryQuery}/ts/**/*.ts`
],
(finish) => {
console.log(CC.blue + CC.bright + `Typescript change detected` + CC.reset);
buildTypescript(basePath, finish);
}
);
gulp.watch(
[
`pages/${directoryQuery}/sass/**/*.scss`
],
(finish) => {
console.log(CC.magenta + CC.bright + `Sass change detected` + CC.reset);
buildSass(basePath, finish);
}
);
})
);
}
});
};
function buildTypescript(basePath, cb) {
var tsProject = ts.createProject(path.join(basePath, 'tsconfig.json'));
gulp
.src(path.join(basePath, 'ts', '**', '*.ts'))
// .pipe(
// tap(function(file) {
// file.contents = browserify(file.path, { debug: true }).bundle();
// })
// )
// .pipe(buffer())
.pipe(tsProject())
.js.pipe(concat(`script.js`))
.pipe(rename(`script.js`))
.pipe(uglify())
.pipe(gulp.dest(basePath))
.on('end', () => {
cb();
});
}
function buildSass(basePath, cb) {
gulp
.src(path.join(basePath, 'sass', '**', '*.scss'))
.pipe(sass().on('error', sass.logError))
.pipe(cleanCSS({ compatibility: 'ie8' }))
.pipe(concat(`style.css`))
.pipe(rename(`style.css`))
.pipe(gulp.dest(basePath))
.on('end', () => {
cb();
});
}

10597
Frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
Frontend/package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "home",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "browserify ./ts/*.ts -p [ tsify --noImplicitAny ] > ./dist/bundle.js"
},
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@meesvdw/coloredconsole": "^1.0.3",
"browserify": "^17.0.0",
"fs-extra": "^10.0.1",
"gulp": "^4.0.2",
"gulp-buffer": "^0.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-concat": "^2.6.1",
"gulp-rename": "^2.0.0",
"gulp-sass": "^5.1.0",
"gulp-tap": "^2.0.0",
"gulp-typescript": "^6.0.0-alpha.1",
"gulp-uglify": "^3.0.2",
"sass": "^1.49.11",
"tsify": "^5.0.4",
"watchify": "^4.0.0"
},
"devDependencies": {
"@types/node": "^17.0.23"
}
}

View File

@@ -0,0 +1,416 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Undecked</title>
<link rel="stylesheet" href="/pd/home/style">
<script src="/stc/libs/socket.io.min.js"></script>
<script src="/pd/home/script" defer></script>
</head>
<body>
<div class="tabcontainer">
<div class="tabmenu">
<div class="item" tab="pages">Pages</div>
<div class="item" tab="connections">Connections</div>
<div class="item" tab="decks">Decks</div>
</div>
<div class="tabpages">
<div class="item" tab="pages">
<div class="pageselector">
<div class="pageinfo">
<input type="text" disabled class="pagename">
<div class="controls">
<div class="box left">
<img src="/stc/icon/left.png">
</div>
<div class="centered">
<div class="box add">
<img src="/stc/icon/add.png">
</div>
</div>
<div class="box right">
<img src="/stc/icon/right.png">
</div>
</div>
</div>
</div>
<div class="overview">
<div class="deck">
<div class="group small fs">
<div class="key" x="0" y="0"><canvas class="ready"></canvas></div>
<div class="key" x="1" y="0"><canvas class="ready"></canvas></div>
<div class="key" x="2" y="0"><canvas class="ready"></canvas></div>
<div class="key" x="3" y="0"><canvas class="ready"></canvas></div>
<div class="key" x="4" y="0"><canvas class="ready"></canvas></div>
</div>
<div class="group large">
<div class="key" x="5" y="0"><canvas class="ready"></canvas></div>
<div class="key" x="6" y="0"><canvas class="ready"></canvas></div>
<div class="key" x="7" y="0"><canvas class="ready"></canvas></div>
</div>
<div class="group small">
<div class="key" x="0" y="1"><canvas class="ready"></canvas></div>
<div class="key" x="1" y="1"><canvas class="ready"></canvas></div>
<div class="key" x="2" y="1"><canvas class="ready"></canvas></div>
<div class="key" x="3" y="1"><canvas class="ready"></canvas></div>
<div class="key" x="4" y="1"><canvas class="ready"></canvas></div>
</div>
<div class="group large">
<div class="key" x="5" y="1"><canvas class="ready"></canvas></div>
<div class="key" x="6" y="1"><canvas class="ready"></canvas></div>
<div class="key" x="7" y="1"><canvas class="ready"></canvas></div>
</div>
<div class="group small ls">
<div class="key" x="0" y="2"><canvas class="ready"></canvas></div>
<div class="key" x="1" y="2"><canvas class="ready"></canvas></div>
<div class="key" x="2" y="2"><canvas class="ready"></canvas></div>
<div class="key" x="3" y="2"><canvas class="ready"></canvas></div>
<div class="key" x="4" y="2"><canvas class="ready"></canvas></div>
</div>
<div class="group large">
<div class="key" x="5" y="2"><canvas class="ready"></canvas></div>
<div class="key" x="6" y="2"><canvas class="ready"></canvas></div>
<div class="key" x="7" y="2"><canvas class="ready"></canvas></div>
</div>
<div class="group large">
<div class="key" x="0" y="3"><canvas class="ready"></canvas></div>
<div class="key" x="1" y="3"><canvas class="ready"></canvas></div>
<div class="key" x="2" y="3"><canvas class="ready"></canvas></div>
<div class="key" x="3" y="3"><canvas class="ready"></canvas></div>
<div class="key" x="4" y="3"><canvas class="ready"></canvas></div>
<div class="key" x="5" y="3"><canvas class="ready"></canvas></div>
<div class="key" x="6" y="3"><canvas class="ready"></canvas></div>
<div class="key" x="7" y="3"><canvas class="ready"></canvas></div>
</div>
</div>
</div>
<div class="editcontainer">
<div class="edit disabled">
<div class="split">
<div class="buttontype">
<div class="types">
<div class="buttonitem selected" type="empty">
<div class="text">Empty</div>
</div>
<div class="buttonitem" type="custom">
<div class="text">Custom</div>
</div>
<div class="buttonitem" type="pageup">
<div class="text">Page Up</div>
</div>
<div class="buttonitem" type="pagedown">
<div class="text">Page Down</div>
</div>
<div class="buttonitem" type="currentpage">
<div class="text">Current Page</div>
</div>
</div>
</div>
<div class="editinner">
<div class="containertitle blockheader">
Appearence
</div>
<div class="container appearence">
<div class="vis">
<div class="row75">
<div class="row text">
<div class="label">Text</div>
<div class="inputs">
<input type="text" class="value ap_text_value">
<select class="size ap_text_size">
<option value="15">15px</option>
<option value="16">16px</option>
<option value="17">17px</option>
<option value="18">18px</option>
<option value="19">19px</option>
<option value="20">20px</option>
<option value="21">21px</option>
<option value="22">22px</option>
<option value="23">23px</option>
<option value="24">24px</option>
<option value="25">25px</option>
<option value="26">26px</option>
<option value="27">27px</option>
<option value="28">28px</option>
<option value="29">29px</option>
<option value="30">30px</option>
<option value="33">33px</option>
<option value="36">36px</option>
<option value="40">40px</option>
<option value="45">45px</option>
<option value="50">50px</option>
</select>
<input type="color" class="color ap_text_color">
</div>
<div class="advanced">
<div class="subrow ap_text_offsetx">
<div class="label">Offset X</div>
<input type="range" value="0" min="-50" max="50" step="0.1">
<input type="number" value="0" min="-50" max="50" step="0.1">
</div>
<div class="subrow ap_text_offsety">
<div class="label">Offset Y</div>
<input type="range" value="0" min="-50" max="50" step="0.1">
<input type="number" value="0" min="-50" max="50" step="0.1">
</div>
</div>
</div>
<div class="row background">
<div class="label">Background</div>
<input type="color" class="color ap_background_color">
</div>
</div>
<div class="row image">
<div class="label">Image</div>
<div class="imagemenu">
<div class="imagetype">
<div class="selectoritem selected" panel="none">
<div class="text">None</div>
</div>
<div class="selectoritem" panel="icon">
<div class="text">Icon</div>
</div>
<div class="selectoritem" panel="upload">
<div class="text">Upload</div>
</div>
</div>
<div class="info">
<div class="infopanel" panel="none">
</div>
<div class="infopanel" panel="icon">
Icons
</div>
<div class="infopanel" panel="upload">
Upload
</div>
</div>
</div>
<div class="panels">
<div class="panel" panel="none">
</div>
<div class="panel icons" panel="icon">
<div class="list">
{{#each icons}}
<div class="icon" notloaded iconID="{{this.id}}">
<img class="white">
<img class="black">
<div class="name">{{this.name}}</div>
</div>
{{/each}}
</div>
</div>
<div class="panel" panel="upload">
Upload
</div>
</div>
<div class="advanced">
<div class="subrow ap_image_size">
<div class="label">Size</div>
<input type="range" value="100" min="0" max="100" step="0.1">
<input type="number" value="100" min="0" max="100" step="0.1">
</div>
<div class="subrow ap_image_offsetx">
<div class="label">Offset X</div>
<input type="range" value="0" min="-50" max="50" step="0.1">
<input type="number" value="0" min="-50" max="50" step="0.1">
</div>
<div class="subrow ap_image_offsety">
<div class="label">Offset Y</div>
<input type="range" value="0" min="-50" max="50" step="0.1">
<input type="number" value="0" min="-50" max="50" step="0.1">
</div>
<div class="subrow ap_image_rotation">
<div class="label">Rotation</div>
<input type="range" value="0" min="0" max="360" step="1">
<input type="number" value="0" min="0" max="360" step="1">
</div>
</div>
</div>
</div>
</div>
<div class="containertitle checktitle blockheader">
<div class="text">
Actions
</div>
<div class="checks">
<div class="check toggle">
<input type="checkbox">
<div class="checklabel">Toggle</div>
</div>
<div class="check confirm">
<input type="checkbox">
<div class="checklabel">Confirm</div>
</div>
</div>
</div>
<div class="container actions">
<div class="actioninner">
<div class="row event up">
<div class="label">Key Up</div>
<div class="actions"></div>
<div class="newaction">
<input type="text" placeholder="Search for action"
class="actionselector">
</div>
</div>
<div class="row event toggle">
<div class="tlatch">
<div class="label">Key Latch</div>
<div class="actions latch"></div>
<div class="newaction">
<input type="text" placeholder="Search for action"
class="actionselector">
</div>
</div>
<div class="tunlatch">
<div class="label">Key Unlatch</div>
<div class="actions unlatch"></div>
<div class="newaction">
<input type="text" placeholder="Search for action"
class="actionselector">
</div>
</div>
</div>
<div class="row event down">
<div class="label">Key Down</div>
<div class="actions down"></div>
<div class="newaction">
<input type="text" placeholder="Search for action"
class="actionselector">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="item" tab="connections">
<div class="connections">
<div class="connected">
<div class="blockheader">Connections</div>
<table class="connectedtable" cellspacing="0">
<tr>
<th class="status">Status</th>
<th class="name">Name</th>
<th class="integration">Integration</th>
<th class="type">Device Type</th>
</tr>
{{#each connected}}
<tr connectionID="{{this.connectionID}}">
<td class="status">
<div class="statuscontainer">
<div class="value online"></div>
</div>
</td>
<td class="name">{{this.name}}</td>
<td class="integration">{{this.integrationName}}</td>
<td class="type">{{this.connectionType}}</td>
</tr>
{{/each}}
</table>
</div>
<div class="connectionbrowser">
{{#each connections}}
<div class="available" connectionType="{{this.connectionType}}"
integrationID="{{this.integrationID}}">
<div class="integration">{{this.integrationName}}</div>
<div class="connectionName">{{this.connectionName}}</div>
<div class="button">Add</div>
</div>
{{/each}}
</div>
</div>
</div>
<div class="item" tab="decks">Decks</div>
</div>
</div>
<div class="connectiondialog">
<div class="dialog">
<div class="title">New Connection</div>
<div class="message"></div>
<a class="link" target="_blank" href=""></a>
<div class="fields"></div>
<div class="buttons">
<div class="button secondary cn">Cancel</div>
<div class="button co">Connect</div>
</div>
</div>
</div>
<div class="contextmenu">
<div class="item copy">
<div class="content">
<img src="/stc/icon/copy.png">
<div class="title">Copy</div>
</div>
<div class="shortcut"><span class="shortcutKey"></span>+C</div>
</div>
<div class="item paste">
<div class="content">
<img src="/stc/icon/paste.png">
<div class="title">Paste</div>
</div>
<div class="shortcut"><span class="shortcutKey"></span>+V</div>
</div>
<div class="item cut">
<div class="content">
<img src="/stc/icon/cut.png">
<div class="title">Cut</div>
</div>
<div class="shortcut"><span class="shortcutKey"></span>+X</div>
</div>
<div class="seperator"></div>
<div class="item ghost">
<div class="content">
<img src="/stc/icon/ghost.png">
<div class="title">Create ghost</div>
</div>
<div class="shortcut"><span class="shortcutKey"></span>+G</div>
</div>
<div class="seperator"></div>
<div class="item osc disabled">
<div class="content">
<img src="">
<div class="title">Get OSC address</div>
</div>
</div>
<div class="item http disabled">
<div class="content">
<img src="">
<div class="title">Get HTTP trigger address</div>
</div>
</div>
</div>
<div class="actiondialog">
{{#each actions}}
<div class="item" actionID="{{this.actionID}}" integrationID="{{this.integrationID}}">
<div class="integration">{{this.integrationName}}</div>
<div class="action">{{this.actionName}}</div>
</div>
{{/each}}
</div>
</body>
</html>

265
Frontend/pages/home/package-lock.json generated Normal file
View File

@@ -0,0 +1,265 @@
{
"name": "home",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "home",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"socket.io-client": "^4.4.1"
}
},
"node_modules/@socket.io/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz",
"integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q=="
},
"node_modules/backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io-client": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz",
"integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==",
"dependencies": {
"@socket.io/component-emitter": "~3.0.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.0.0",
"has-cors": "1.1.0",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"ws": "~8.2.3",
"xmlhttprequest-ssl": "~2.0.0",
"yeast": "0.1.2"
}
},
"node_modules/engine.io-parser": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz",
"integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==",
"dependencies": {
"@socket.io/base64-arraybuffer": "~1.0.2"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/parseqs": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
},
"node_modules/parseuri": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
},
"node_modules/socket.io-client": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz",
"integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==",
"dependencies": {
"@socket.io/component-emitter": "~3.0.0",
"backo2": "~1.0.2",
"debug": "~4.3.2",
"engine.io-client": "~6.1.1",
"parseuri": "0.0.6",
"socket.io-parser": "~4.1.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz",
"integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==",
"dependencies": {
"@socket.io/component-emitter": "~3.0.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/ws": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
}
},
"dependencies": {
"@socket.io/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ=="
},
"@socket.io/component-emitter": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz",
"integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q=="
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"engine.io-client": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz",
"integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==",
"requires": {
"@socket.io/component-emitter": "~3.0.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.0.0",
"has-cors": "1.1.0",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"ws": "~8.2.3",
"xmlhttprequest-ssl": "~2.0.0",
"yeast": "0.1.2"
}
},
"engine.io-parser": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz",
"integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==",
"requires": {
"@socket.io/base64-arraybuffer": "~1.0.2"
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"parseqs": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
},
"parseuri": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
},
"socket.io-client": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz",
"integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==",
"requires": {
"@socket.io/component-emitter": "~3.0.0",
"backo2": "~1.0.2",
"debug": "~4.3.2",
"engine.io-client": "~6.1.1",
"parseuri": "0.0.6",
"socket.io-parser": "~4.1.1"
}
},
"socket.io-parser": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz",
"integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==",
"requires": {
"@socket.io/component-emitter": "~3.0.0",
"debug": "~4.3.1"
}
},
"ws": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
"requires": {}
},
"xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="
},
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
}
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "home",
"version": "1.0.0",
"description": "",
"main": "script.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"socket.io-client": "^4.4.1"
}
}

View File

@@ -0,0 +1,51 @@
.actionselector {
width: calc(100% - 24px);
margin: 5px 0px;
&:focus,
&:active {
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
}
}
.actiondialog {
position: absolute;
border: solid var(--main-color);
border-width: 0px 2px 2px 2px;
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
overflow: auto;
display: none;
flex-direction: column;
.item {
width: calc(100% - 20px);
display: flex;
justify-content: flex-start;
align-items: center;
padding: 5px 10px;
background: var(--main-secondary-color);
border-bottom: 1px solid var(--main-color);
transition-duration: .2s;
cursor: pointer;
font-size: 12px;
&:last-child {
border-bottom: 0px;
}
&:hover {
background: var(--main-hover-color);
}
.integration {
margin-right: 10px;
font-weight: 600;
}
}
.item.hidden {
display: none;
}
}

View File

@@ -0,0 +1,152 @@
.actioncontainer {
width: calc(100% - 20px);
margin: 10px 10px 0px 10px;
border-bottom: 1px solid #424242;
padding-bottom: 10px;
&:last-child {
border-bottom: 0px;
}
.header {
display: flex;
font-size: 12px;
.integration {
font-weight: 500;
margin-right: 5px;
}
.action {
color: #d9d9d9;
width: 100%;
}
.buttons {
display: flex;
justify-content: center;
align-items: center;
.btn {
padding: 2px 8px;
border-radius: var(--border-radius);
transition-duration: .2s;
cursor: pointer;
}
.btn.logs {
background: #707070;
&:hover {
background: #4b4b4b;
}
}
.btn.remove {
background: var(--color-red);
margin-left: 5px;
&:hover {
background: var(--color-red-hover);
}
}
}
}
.fields {
width: 100%;
.field {
width: calc(100% - 5px);
display: flex;
justify-content: flex-start;
align-items: center;
margin-left: 5px;
margin-top: 5px;
.fieldlabel {
font-size: 12px;
white-space: nowrap;
margin-right: 10px;
color: #bfbfbf;
}
.multiselect {
position: relative;
width: calc(100% - 14px);
margin: 0;
padding: 2px 5px;
select {
width: calc(100% + 4px);
margin: 0;
padding: 2px 5px;
&.open {
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
border-color: var(--main-color);
}
}
.dropdown {
position: absolute;
top: 25px;
left: 5px;
right: 1px;
height: 300px;
display: none;
.inner {
width: 100%;
max-height: 300px;
overflow-y: auto;
border: 2px solid var(--main-color);
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
// display: flex;
// justify-content: center;
// align-items: center;
// flex-direction: column;
.option {
width: calc(100% - 20px);
height: 18px;
padding: 4px 10px;
display: flex;
justify-content: flex-start;
align-items: center;
border-bottom: 1px solid var(--main-color);
background: #323232;
user-select: none;
font-size: 12px;
&:last-child {
border-bottom: 0px;
}
input {
width: 18px;
height: 18px;
pointer-events: none;
}
.text {
margin-left: 10px;
}
}
}
}
}
input,
select {
width: 100%;
margin: 0px;
padding: 2px 5px;
}
}
}
}

View File

@@ -0,0 +1,78 @@
.connectiondialog {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
z-index: 3;
display: none;
justify-content: center;
align-items: center;
background: #0000005e;
.dialog {
width: 300px;
padding: 20px;
border-radius: var(--border-radius);
border: 2px solid var(--main-color);
background: var(--panel-color);
.title {
width: 100%;
font-size: 16px;
}
.message {
width: 100%;
font-size: 12px;
margin: 5px 0px;
}
.link {
font-size: 14px;
margin-bottom: 10px;
}
.fields {
width: 100%;
min-height: 100px;
.field {
width: calc(100% - 5px);
display: flex;
justify-content: flex-start;
align-items: center;
margin-left: 5px;
margin-top: 5px;
.fieldlabel {
font-size: 12px;
white-space: nowrap;
margin-right: 10px;
color: #bfbfbf;
}
input,
select {
width: 100%;
margin: 0px;
padding: 2px 5px;
}
}
}
.buttons {
width: 100%;
display: flex;
justify-content: flex-end;
margin-top: 10px;
font-size: 12px;
.secondary {
margin-right: 10px;
}
}
}
}

View File

@@ -0,0 +1,102 @@
.connections {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
display: flex;
justify-content: center;
align-items: center;
.connected {
width: calc(80% - 20px);
height: calc(100% - 20px);
padding: 10px;
border-right: 2px solid var(--main-color);
table {
width: 100%;
tr {
height: 40px;
}
th,
td {
text-align: left;
border-bottom: 1px solid #323232;
}
td {
font-size: 12px;
}
.status {
width: 100px;
.statuscontainer {
width: 100%;
height: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
.value {
width: 15px;
height: 15px;
border: 2px solid #00000044;
border-radius: 100%;
&.online {
background: green;
}
&.offline {
background: var(--color-red);
}
}
}
}
.name {}
.integration {}
.type {}
}
}
.connectionbrowser {
width: calc(20% - 20px);
height: calc(100% - 20px);
min-width: 200px;
padding: 10px;
background: var(--subpanel-color);
.available {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
transition-duration: .2s;
cursor: pointer;
font-size: 12px;
height: 30px;
border-bottom: 1px solid #323232;
.integration {
margin-right: 10px;
font-weight: 600;
white-space: nowrap;
}
.connectionName {
width: 100%;
}
}
}
}

View File

@@ -0,0 +1,106 @@
.contextmenu {
position: absolute;
z-index: 10;
min-width: 150px;
display: none;
// flex-direction: column;
border: 3px solid var(--main-color);
border-radius: var(--border-radius);
background: var(--panel-color);
box-shadow: 0px 0px 5px #727272;
overflow: hidden;
&:first-child {
border-top: 0px;
}
.item {
width: 100%;
height: 24px;
border-top: 1px solid var(--main-hover-color);
cursor: pointer;
transition-duration: .2s;
position: relative;
&:hover {
background: var(--main-hover-color);
img {
filter: grayscale(1);
}
}
&.disabled {
opacity: .7;
pointer-events: none;
img {
filter: grayscale(0);
}
cursor: not-allowed;
}
.content {
position: absolute;
inset: 0px;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0px 5px;
box-sizing: border-box;
pointer-events: none;
img {
width: 14px;
height: 14px;
transition-duration: .2s;
filter: grayscale(0);
}
.title {
margin-left: 5px;
font-size: 9px;
white-space: nowrap;
}
}
.shortcut {
position: absolute;
inset: 0;
pointer-events: none;
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 8px;
font-weight: 400;
box-sizing: border-box;
padding-right: 5px;
color: #adadad;
}
}
.seperator {
width: 100%;
height: 1px;
background: var(--main-color);
}
}

View File

@@ -0,0 +1,75 @@
.overview {
position: absolute;
top: 0px;
bottom: 0px;
left: 202px;
right: 502px;
display: flex;
justify-content: center;
align-items: center;
.deck {
width: 960px;
height: 480px;
display: flex;
flex-wrap: wrap;
.group {
display: flex;
.key {
width: 100px;
height: 100px;
border: 2px solid #ffffff8c;
margin: 8px;
border-radius: var(--border-radius);
overflow: hidden;
transition-duration: .1s;
cursor: pointer;
canvas {
width: 100%;
height: 100%;
pointer-events: none;
}
&:hover {
border-color: var(--main-hover-color);
}
}
.key.selected,
.key.selected:hover {
border-color: var(--main-color);
border-width: 4px;
margin: 6px;
}
.key.context,
.key.context:hover {
border-color: var(--main-color);
border-width: 2px;
margin: 8px;
}
}
.group.small {
background: #2a2a2a;
}
.group.fs {
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
}
.group.ls {
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
}
}

View File

@@ -0,0 +1,418 @@
.editcontainer {
position: absolute;
top: 0px;
bottom: 0px;
right: 0px;
width: 500px;
border-left: 2px solid var(--main-color);
background: var(--subpanel-color);
.edit {
.split {
position: absolute;
top: 5px;
bottom: 5px;
left: 5px;
right: 5px;
opacity: 1;
transition-duration: .2s;
.row {
width: calc(100% - 10px);
margin-top: 5px;
padding: 5px;
background: #363636;
.label {
color: #fff;
margin: 0 0 5px 0;
font-size: 13px;
border-bottom: 1px solid #525252;
padding-bottom: 4px;
padding-left: 5px;
}
.inputs {
display: flex;
justify-content: flex-start;
align-items: center;
}
}
.row75 {
width: 100%;
display: flex;
align-items: flex-start;
.row {
width: calc(25% - 15px);
margin: 0px 0px 0px 5px;
&:first-child {
width: calc(75% - 10px);
margin: 0px;
}
}
}
.buttontype {
width: calc(100% + 10px);
display: flex;
justify-content: center;
align-items: center;
border-bottom: 2px solid var(--main-color);
padding-bottom: 5px;
margin-left: -5px;
.types {
display: flex;
justify-content: center;
.buttonitem {
border: solid var(--main-hover-color);
border-width: 2px 2px 2px 0px;
transition-duration: .2s;
padding: 5px 10px;
background: var(--background);
color: #fff;
font-size: 12px;
transition-duration: .2s;
cursor: pointer;
&:hover {
background: var(--main-hover-color);
}
&:first-child {
border-left-width: 2px;
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
}
&:last-child {
border-top-right-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
}
.buttonitem.selected,
.buttonitem.selected:hover {
background: var(--main-color);
border-color: var(--main-color);
}
}
}
.editinner {
width: 100%;
height: calc(100% - 36px);
overflow-y: auto;
}
.container {
// position: absolute;
// top: 0px;
// bottom: 0px;
width: 100%;
}
.containertitle {
width: calc(100% - 10px);
padding-left: 10px;
margin: 10px 0 5px 0;
&.checktitle {
display: flex;
.text {
width: 100%;
}
.checks {
display: flex;
align-items: center;
.check {
display: flex;
align-items: center;
margin-left: 10px;
user-select: none;
input {
width: 18px;
height: 18px;
pointer-events: none;
margin-right: 5px;
}
.checklabel {
font-size: 12px;
}
}
}
}
}
.container.appearence {
// left: 0px;
// right: 50%;
// padding-right: 10px;
.advanced {
width: calc(100% + 10px);
display: flex;
align-items: center;
justify-content: space-between;
margin-left: -5px;
margin-top: 5px;
.subrow {
width: 100%;
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 0px 5px;
.label {
width: 100%;
color: #a5a5a5;
margin: 0px 0px -3px 5px;
font-size: 10px;
border-bottom: 0px;
padding: 0px;
}
input[type="range"] {
width: calc(100% - 42px);
margin: 0px;
padding: 0px;
}
input[type="number"] {
padding: 2px 0px;
width: 30px;
font-size: 10px;
text-align: center;
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
margin: 0;
}
}
}
}
.row.text {
.value {
height: 17px;
width: calc(100% - 104px);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
.size {
width: 80px;
border-radius: 0px;
border-width: 2px 0px;
}
.color {
height: 31px;
width: 60px;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
}
.row.background {
.color {
height: 31px;
width: 100%
}
}
.row.image {
// width: 100%;
// height: calc(100% - 72px);
// display: flex;
// justify-content: center;
// align-items: flex-start;
// flex-wrap: wrap;
.imagemenu {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
.imagetype {
display: flex;
justify-content: center;
.selectoritem {
border: solid var(--main-hover-color);
border-width: 2px 2px 2px 0px;
transition-duration: .2s;
padding: 5px 10px;
background: var(--background);
color: #fff;
font-size: 12px;
transition-duration: .2s;
cursor: pointer;
&:hover {
background: var(--main-hover-color);
}
&:first-child {
border-left-width: 2px;
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
}
&:last-child {
border-top-right-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
}
.selectoritem.selected,
.selectoritem.selected:hover {
background: var(--main-color);
border-color: var(--main-color);
}
}
.info {
width: 100%;
height: 28px;
margin-left: 10px;
.infopanel {
width: 100%;
height: 100%;
display: none;
}
.infopanel.selected {
display: block;
}
}
}
.panels {
width: 100%;
.panel {
width: 100%;
height: calc(100% - 10px);
padding: 5px 0px;
display: none;
}
.panel.selected {
display: block;
}
.panel.icons {
max-height: 150px;
overflow-y: auto;
overflow-x: hidden;
.list {
width: calc(100% + 4px);
height: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
flex-wrap: wrap;
margin-left: -2px;
cursor: pointer;
overflow-y: auto;
.icon {
position: relative;
width: 55px;
height: 55px;
overflow: hidden;
background: #343434;
margin: 1px;
transition-duration: .2s;
border: 1px solid transparent;
&:hover {
border-color: var(--main-hover-color);
.name {
max-height: 100%;
}
}
img {
position: absolute;
top: 10px;
bottom: 10px;
left: 10px;
right: 10px;
z-index: 1;
width: calc(100% - 20px);
height: calc(100% - 20px);
opacity: 0;
transition-duration: .2s;
}
.name {
position: absolute;
max-height: 20%;
bottom: 0px;
left: 0px;
right: 0px;
z-index: 2;
text-align: center;
font-size: 9px;
padding: 2px;
background: #00000096;
transition-duration: .3s;
}
}
.icon.selected,
.icon.selected:hover {
border-color: var(--main-color);
background: var(--main-secondary-color);
}
}
}
}
}
}
.container.actions {}
}
}
.edit.disabled {
pointer-events: none;
.split {
opacity: 0;
}
}
}

View File

@@ -0,0 +1,6 @@
.blockheader {
font-size: 18px;
letter-spacing: 0px;
font-weight: 500;
color: #dfdfdf;
}

View File

@@ -0,0 +1,182 @@
.item[tab="pages"] {
.pageselector {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
width: 200px;
border-right: 2px solid var(--main-color);
background: var(--subpanel-color);
.pageinfo {
position: absolute;
top: 0;
bottom: calc(100% - 90px);
left: 0;
right: 0;
border-bottom: 2px solid var(--main-color);
padding: 10px;
display: flex;
justify-content: center;
align-items: flex-start;
flex-wrap: wrap;
.pagename {
width: calc(100% - 24px);
}
.controls {
width: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
margin-top: 10px;
.box {
width: 30px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
&:hover {
img {
filter: grayscale(1);
}
}
&.left,
&.right {
img {
width: 30px;
height: 30px;
}
}
img {
filter: grayscale(0);
transition-duration: .2s;
}
}
.centered {
width: calc(100% - 60px);
height: 30px;
display: flex;
justify-content: center;
align-items: center;
.box img {
width: 20px;
height: 20px;
}
}
}
}
.list {
position: absolute;
top: 90px;
bottom: 0px;
left: 0px;
right: 0px;
.pageitem {
width: 100%;
height: 40px;
display: flex;
align-items: center;
background: #363636;
border-bottom: 1px solid #2c2c2c;
transition-duration: .2s;
cursor: pointer;
&:hover {
background: #3e3d3d;
}
.name {
width: calc(100% - 35px);
margin-left: 10px;
font-size: 12px;
}
.move {
display: flex;
flex-direction: column;
margin-left: 6px;
.moveitem {
width: 14px;
height: 14px;
padding: 2px;
border-radius: 2px;
img {
width: 14px;
height: 14px;
filter: grayscale(0);
transition-duration: .2s;
}
&:hover {
background: var(--subpanel-color);
img {
filter: grayscale(1);
}
}
.selected {
display: none;
}
}
}
&.selected {
background: var(--main-color);
.moveitem .normal {
display: none;
}
.moveitem .selected {
display: block;
}
.moveitem:hover {
background: var(--main-hover-color);
}
}
}
}
}
.buttoneditor {
position: absolute;
top: 0px;
bottom: 0px;
left: 202px;
right: 0px;
.edit {
position: absolute;
bottom: 0px;
left: 0px;
right: 0px;
height: 400px;
border-top: 2px solid var(--main-color);
background: var(--subpanel-color);
}
}
}

View File

@@ -0,0 +1,20 @@
/* width */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
/* Track */
::-webkit-scrollbar-track {
background: #ffffff0f;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #b8b8b8;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #d6d6d6;
}

View File

@@ -0,0 +1,78 @@
.tabcontainer {
border: 2px solid var(--main-color);
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: #222222;
border-radius: var(--border-radius);
overflow: hidden;
.tabmenu {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 30px;
display: flex;
justify-content: flex-start;
align-items: center;
background: var(--panel-color);
border-bottom: 2px solid var(--main-color);
.item {
height: 100%;
padding: 0px 15px;
display: flex;
align-items: center;
background: var(--main-secondary-color);
border-right: 2px solid var(--main-color);
user-select: none;
transition-duration: .2s;
cursor: pointer;
&:hover {
background: var(--main-hover-color);
}
}
.item.active,
.item.active :hover {
background: var(--main-color);
cursor: inherit;
}
}
.tabpages {
position: absolute;
top: 32px;
bottom: 0px;
left: 0px;
right: 0px;
.item {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
transition-duration: .2s;
display: none;
}
.item.padding {
padding: 10px;
}
.item.active {
display: inherit;
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,583 @@
var ActionEditor: ActionEditor = {
elements: {
up: {
container: document.querySelector('.event.up'),
selector: document.querySelector('.event.up').querySelector('.actionselector'),
actions: document.querySelector('.event.up').querySelector('.actions')
},
down: {
container: document.querySelector('.event.down'),
selector: document.querySelector('.event.down').querySelector('.actionselector'),
actions: document.querySelector('.event.down').querySelector('.actions')
},
latch: {
container: document.querySelector('.event.toggle'),
selector: document.querySelector('.event.toggle').querySelector('.tlatch').querySelector('.actionselector'),
actions: document.querySelector('.event.toggle').querySelector('.tlatch').querySelector('.actions')
},
unlatch: {
container: document.querySelector('.event.toggle'),
selector: document
.querySelector('.event.toggle')
.querySelector('.tunlatch')
.querySelector('.actionselector'),
actions: document.querySelector('.event.toggle').querySelector('.tunlatch').querySelector('.actions')
}
},
openEditors: { up: {}, down: {}, latch: {}, unlatch: {} },
listeners() {
if (ActionSelector) {
ActionSelector.register(ActionEditor.elements.up.selector, (integrationID: string, actionID: string) =>
handleNewAction(integrationID, actionID, 'up')
);
ActionSelector.register(ActionEditor.elements.down.selector, (integrationID: string, actionID: string) =>
handleNewAction(integrationID, actionID, 'down')
);
ActionSelector.register(ActionEditor.elements.latch.selector, (integrationID: string, actionID: string) =>
handleNewAction(integrationID, actionID, 'latch')
);
ActionSelector.register(ActionEditor.elements.unlatch.selector, (integrationID: string, actionID: string) =>
handleNewAction(integrationID, actionID, 'unlatch')
);
function handleNewAction(
integrationID: string,
actionID: string,
type: 'up' | 'down' | 'latch' | 'unlatch'
) {
socket.emit(
'actioneditor',
'create',
PageHandler.currentPageID,
Editor.currentX,
Editor.currentY,
type,
integrationID,
actionID,
(actionInstance: Page_Key_Action) => {
ActionEditor.openAction(actionInstance, type);
}
);
}
} else setTimeout(ActionEditor.listeners, 100);
},
open(actions: Page_Key_ActionTypes, x: string, y: string, key: Page_Key, pageID: string) {
for (var type in actions) {
ActionEditor.elements[type].actions.innerHTML = '';
for (var actionInstanceID in actions[type]) {
ActionEditor.openAction(actions[type][actionInstanceID], type);
}
}
},
openAction(actionInstance: Page_Key_Action, type: string) {
var actionInstanceID = actionInstance.actionInstanceID;
var container = <HTMLDivElement>ce('div', 'actioncontainer', { actionInstanceID });
var header = ce('div', 'header');
header.appendChild(
ce('div', 'integration', null, ActionSelector.maps.integrationNames[actionInstance.integrationID])
);
header.appendChild(ce('div', 'action', null, ActionSelector.maps.actionNames[actionInstance.actionID]));
var buttons = ce('div', 'buttons');
var actionLogs = ce(
'div',
[
'btn',
'logs'
],
null,
'Logs'
);
actionLogs.onclick = () => UndeckedNotification('Not implented yet', 'error');
var actionRemove = ce(
'div',
[
'btn',
'remove'
],
null,
'Remove'
);
actionRemove.onclick = () => {
if (container.parentElement) container.parentElement.removeChild(container);
if (
ActionEditor.openEditors[type] != undefined &&
ActionEditor.openEditors[type][actionInstanceID] != undefined
) {
ActionEditor.openEditors[type][actionInstanceID].destroy();
}
socket.emit(
'actioneditor',
'remove',
PageHandler.currentPageID,
Editor.currentX,
Editor.currentY,
actionInstanceID,
type
);
Editor.registerChange();
};
buttons.appendChild(actionLogs);
buttons.appendChild(actionRemove);
header.appendChild(buttons);
container.appendChild(header);
var fields = <HTMLDivElement>ce('div', 'fields');
container.appendChild(fields);
ActionEditor.elements[type].actions.appendChild(container);
ActionEditor.openEditors[type][actionInstanceID] = new Action(
{
integrationID: actionInstance.integrationID,
actionID: actionInstance.actionID,
actionType: <any>type,
keyX: Editor.currentX,
keyY: Editor.currentY,
actionInstanceID,
pageID: PageHandler.currentPageID
},
fields
);
},
close() {
for (var type in ActionEditor.openEditors) {
for (var actionInstanceID in ActionEditor.openEditors[type]) {
ActionEditor.openEditors[type][actionInstanceID].destroy();
}
}
ActionEditor.elements.up.actions.innerHTML = '';
ActionEditor.elements.down.actions.innerHTML = '';
}
};
ActionEditor.listeners();
var Action = class Action {
settings: Action_Settings;
container: HTMLDivElement;
actionEditorID: string;
lastFields: EditorAPI_Field[];
constructor(settings: Action_Settings, container: HTMLDivElement) {
this.settings = settings;
this.container = container;
socket.emit(
'actioneditor',
'start',
this.settings,
(error: string, actionEditorID: string, ready: () => void) => {
if (error) return UndeckedNotification(error, 'error', 5000);
this.actionEditorID = actionEditorID;
this.listeners();
this.emit('ready');
}
);
}
emit(query: string, ...args: any[]) {
socket.emit('actioneditor', 'instance', this.actionEditorID, query, ...args);
}
listeners() {
socket.on(`AE_${this.actionEditorID}`, (query: string, ...args: any[]) => {
switch (query) {
case 'fields':
var fields: EditorAPI_Field[] = args[0];
this.render(fields);
break;
}
});
}
destroy() {
if (this.container && this.container.parentElement) this.container.parentElement.removeChild(this.container);
this.emit('close');
}
render(fields: EditorAPI_Field[]) {
var valueNameMap = {};
var updateMulti = (selected: string[], input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement) => {
var text: string =
selected != undefined && selected.length > 0
? selected
.map((value) => {
return valueNameMap[value];
})
.join(', ')
: 'None';
input.innerHTML = '';
input.appendChild(ce('option', 'mutli', { value: selected.join(',') }, text));
};
var getMultiValues = (dropdowninner: HTMLDivElement): string[] => {
var values: string[] = [];
dropdowninner.querySelectorAll('.option').forEach((option: HTMLDivElement) => {
var checkbox: HTMLInputElement = option.querySelector('input');
var optionID = option.getAttribute('optionID');
if (checkbox.checked) values.push(optionID);
});
return values;
};
fields.forEach((field) => {
var fieldcontainer: HTMLDivElement = this.container.querySelector(`.field_${field.id}`);
if (fieldcontainer == null) {
fieldcontainer = <HTMLDivElement>ce('div', [
'field',
`field_${field.id}`
]);
this.container.appendChild(fieldcontainer);
}
var label: HTMLDivElement = fieldcontainer.querySelector('.fieldlabel');
if (label == null) {
label = <HTMLDivElement>ce('div', 'fieldlabel', null, `${field.name}${field.required ? ' *' : ''}`);
fieldcontainer.appendChild(label);
} else label.innerText = `${field.name}${field.required ? ' *' : ''}`;
var input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement = fieldcontainer.querySelector(
'.input'
);
if (input == null) {
switch (field.type) {
case 'number':
case 'text':
case 'color':
input = <HTMLInputElement>ce('input', 'input', { type: field.type, value: field.value });
fieldcontainer.appendChild(input);
break;
case 'select':
case 'connection':
if (field.multi != undefined && field.multi == true) {
var multiSelect = ce('div', [
'multiselect'
]);
input = <HTMLInputElement>ce('select', [
'input',
'multiinput'
]);
input.onmouseup = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
dropdown.style.display = 'block';
input.classList.add('open');
var handleWindowClick = (e: MouseEvent) => {
if (e.target) {
var target = <HTMLElement>e.target;
if (
!target.classList.contains('msdp') &&
!target.classList.contains('multiinput')
) {
window.removeEventListener('click', handleWindowClick);
dropdown.style.display = 'none';
input.classList.remove('open');
}
}
};
window.addEventListener('click', handleWindowClick);
};
input.onmousedown = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
};
multiSelect.appendChild(input);
var dropdown = ce('div', [
'dropdown',
'msdp'
]);
var dropdowninner = <HTMLDivElement>ce('div', [
'inner',
'msdp'
]);
dropdown.appendChild(dropdowninner);
multiSelect.appendChild(dropdown);
if (field.values != undefined && field.values.length > 0)
for (let i = 0; i < field.values.length; i++) {
((value) => {
valueNameMap[value.id] = field.values[i].text;
var item = ce(
'div',
[
'option',
'msdp'
],
{ optionID: value.id }
);
var checkbox = <HTMLInputElement>ce('input', 'msdp', {
type: 'checkbox'
});
checkbox.checked = field.value != undefined && field.value.includes(value.id);
item.onclick = () => {
checkbox.checked = !checkbox.checked;
updateMulti(getMultiValues(dropdowninner), input);
this.emit('fields', this.export());
};
item.appendChild(checkbox);
item.appendChild(
ce(
'div',
[
'text',
'msdp'
],
null,
value.text
)
);
dropdowninner.appendChild(item);
})(field.values[i]);
}
updateMulti(field.value, input);
fieldcontainer.appendChild(multiSelect);
} else {
input = <HTMLInputElement>ce('select', 'input');
if (field.values != undefined && field.values.length > 0)
for (let i = 0; i < field.values.length; i++) {
var option = ce(
'option',
null,
{ value: field.values[i].id },
field.values[i].text
);
if (field.value == field.values[i].id) option.setAttribute('selected', '');
input.appendChild(option);
}
fieldcontainer.appendChild(input);
}
break;
}
if (input) input.oninput = () => this.emit('fields', this.export());
} else {
input.value = field.value;
if (field.type == 'select' || field.type == 'connection') {
var parsedOptionValues = [];
if (field.multi != undefined && field.multi == true) {
var dropdown_inner = <HTMLDivElement>input.parentElement.querySelector('.inner');
if (field.values != undefined && field.values.length > 0)
for (let i = 0; i < field.values.length; i++) {
var selectValue = field.values[i];
var existingOption: HTMLDivElement = dropdown_inner.querySelector(
`.option[optionid="${selectValue.id}"]`
);
parsedOptionValues.push(selectValue.id);
if (existingOption) {
var text: HTMLDivElement = existingOption.querySelector('.text');
text.innerText = selectValue.text;
} else {
((value) => {
valueNameMap[value.id] = field.values[i].text;
var item = ce(
'div',
[
'option',
'msdp'
],
{ optionID: value.id }
);
var checkbox = <HTMLInputElement>ce('input', 'msdp', {
type: 'checkbox'
});
checkbox.checked = field.value != undefined && field.value.includes(value.id);
item.onclick = () => {
checkbox.checked = !checkbox.checked;
updateMulti(getMultiValues(dropdown_inner), input);
this.emit('fields', this.export());
};
item.appendChild(checkbox);
item.appendChild(
ce(
'div',
[
'text',
'msdp'
],
null,
value.text
)
);
dropdown_inner.appendChild(item);
})(selectValue);
}
}
else dropdown_inner.innerHTML = '';
dropdown_inner.querySelectorAll('.option').forEach((option: HTMLOptionElement) => {
var optionValue = option.getAttribute('optionID');
var checkbox: HTMLInputElement = option.querySelector('input');
if (!parsedOptionValues.includes(optionValue)) option.parentElement.removeChild(option);
else checkbox.checked = field.value.includes(optionValue);
});
} else {
if (field.values != undefined && field.values.length > 0)
for (let i = 0; i < field.values.length; i++) {
var selectValue = field.values[i];
var existing: HTMLOptionElement = input.querySelector(
`option[value="${selectValue.id}"]`
);
if (existing) existing.innerText = selectValue.text;
else {
existing = <HTMLOptionElement>ce(
'option',
null,
{ value: selectValue.id },
selectValue.text
);
input.appendChild(existing);
}
parsedOptionValues.push(selectValue.id);
}
else input.innerHTML = '';
input.querySelectorAll('option').forEach((option: HTMLOptionElement) => {
var optionValue = option.getAttribute('value');
option.removeAttribute('selected');
if (!parsedOptionValues.includes(optionValue)) option.parentElement.removeChild(option);
else if (field.value == option) option.setAttribute('selected', '');
});
}
// if (field.value != undefined && input.value.length > 0) input.value = field.value;
}
}
});
this.lastFields = fields;
}
export(): EditorAPI_Field[] {
var copyOfLast = JSON.parse(JSON.stringify(this.lastFields));
for (let i = 0; i < copyOfLast.length; i++) {
var field = copyOfLast[i];
var fieldcontainer: HTMLDivElement = this.container.querySelector(`.field_${field.id}`);
if (fieldcontainer) {
var input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement = fieldcontainer.querySelector(
'.input'
);
if (input) {
if (!input.classList.contains('multiinput')) copyOfLast[i].value = input.value;
else copyOfLast[i].value = input.value.split(',');
}
}
}
return copyOfLast;
}
};
interface Action_Settings {
integrationID: string;
actionID: string;
actionInstanceID: string;
pageID: string;
actionType: 'up' | 'down';
keyX: number | string;
keyY: number | string;
}
interface ActionEditor {
elements: {
up: {
container: HTMLDivElement;
selector: HTMLInputElement;
actions: HTMLDivElement;
};
down: {
container: HTMLDivElement;
selector: HTMLInputElement;
actions: HTMLDivElement;
};
latch: {
container: HTMLDivElement;
selector: HTMLInputElement;
actions: HTMLDivElement;
};
unlatch: {
container: HTMLDivElement;
selector: HTMLInputElement;
actions: HTMLDivElement;
};
};
openEditors: {
up: { [actionInstanceID: string]: typeof Action };
down: { [actionInstanceID: string]: typeof Action };
latch: { [actionInstanceID: string]: typeof Action };
unlatch: { [actionInstanceID: string]: typeof Action };
};
listeners: () => void;
open: (actions: Page_Key_ActionTypes, x: string, y: string, key: Page_Key, pageID: string) => void;
openAction: (actionInstance: Page_Key_Action, type: string) => void;
close: () => void;
}
interface Page_Key_ActionTypes {
up: Page_Key_Actions;
down: Page_Key_Actions;
}
interface Page_Key_Actions {
[actionInstanceID: string]: Page_Key_Action;
}
interface Page_Key_Action {
integrationID: string;
actionID: string;
actionInstanceID: string;
properties: { [property: string]: any };
logs: { timestamp: number; type: 'error' | 'info' | 'warning'; text: string }[];
}
interface EditorAPI_Field {
id: string;
name: string;
type: 'text' | 'number' | 'select' | 'connection' | 'color';
value: any;
values?: { id: string; text: string }[];
required?: boolean;
multi?: boolean;
}

View File

@@ -0,0 +1,110 @@
var ActionSelector: ActionSelector = {
elements: {
actiondialog: document.querySelector('.actiondialog')
},
fadeTimeout: null,
maps: {
integrationNames: {},
actionNames: {}
},
init() {
ActionSelector.elements.actiondialog.querySelectorAll('.item').forEach((item: HTMLDivElement) => {
var integrationID = item.getAttribute('integrationID');
var actionID = item.getAttribute('actionID');
var integrationName = (<HTMLDivElement>item.querySelector('.integration')).innerText.trim();
var actionName = (<HTMLDivElement>item.querySelector('.action')).innerText.trim();
if (ActionSelector.maps.integrationNames[integrationID] == undefined)
ActionSelector.maps.integrationNames[integrationID] = integrationName;
if (ActionSelector.maps.actionNames[actionID] == undefined)
ActionSelector.maps.actionNames[actionID] = actionName;
});
},
register(input: HTMLInputElement, callback: (integrationID: string, actionID: string) => void) {
input.onfocus = () => ActionSelector.show(input, callback);
input.onblur = () => setTimeout(ActionSelector.hide, 200);
input.oninput = () => ActionSelector.search(input.value);
},
show(input: HTMLInputElement, callback: (integrationID: string, actionID: string) => void) {
clearTimeout(ActionSelector.fadeTimeout);
ActionSelector.search('');
var boundingbox = input.getBoundingClientRect();
ActionSelector.elements.actiondialog.style.top = `${boundingbox.top - 31}px`;
ActionSelector.elements.actiondialog.style.left = `${boundingbox.left - 10}px`;
ActionSelector.elements.actiondialog.style.width = `${boundingbox.width - 4}px`;
ActionSelector.elements.actiondialog.style.maxHeight = `${Math.min(
window.innerHeight - boundingbox.top,
200
)}px`;
ActionSelector.elements.actiondialog.style.display = 'flex';
ActionSelector.elements.actiondialog.style.transitionDuration = '.3s';
ActionSelector.elements.actiondialog.style.opacity = '1';
ActionSelector.elements.actiondialog.style.pointerEvents = 'auto';
ActionSelector.elements.actiondialog.querySelectorAll('.item').forEach((item: HTMLDivElement) => {
item.onclick = () => {
var integrationID = item.getAttribute('integrationID');
var actionID = item.getAttribute('actionID');
callback(integrationID, actionID);
input.value = '';
};
});
},
hide() {
ActionSelector.elements.actiondialog.style.transitionDuration = '.3s';
ActionSelector.elements.actiondialog.style.opacity = '0';
ActionSelector.elements.actiondialog.style.pointerEvents = 'none';
clearTimeout(ActionSelector.fadeTimeout);
ActionSelector.fadeTimeout = setTimeout(() => {
ActionSelector.elements.actiondialog.style.display = 'none';
}, 300);
},
search(query: string) {
ActionSelector.elements.actiondialog.querySelectorAll('.item').forEach((item: HTMLDivElement) => {
var integration: HTMLDivElement = item.querySelector('.integration');
var action: HTMLDivElement = item.querySelector('.action');
var interactionQuery = integration.innerText.toLowerCase();
var actionQuery = action.innerText.toLowerCase();
query = query.toLowerCase();
if (interactionQuery.includes(query) || actionQuery.includes(query) || query.length == 0)
item.classList.remove('hidden');
else item.classList.add('hidden');
});
}
};
ActionSelector.init();
interface ActionSelector {
elements: {
actiondialog: HTMLDivElement;
};
fadeTimeout: any;
maps: {
integrationNames: { [integrationID: string]: string };
actionNames: { [actionID: string]: string };
};
init: () => void;
register: (input: HTMLInputElement, callback: (integrationID: string, actionID: string) => void) => void;
show: (input: HTMLInputElement, callback: (integrationID: string, actionID: string) => void) => void;
hide: () => void;
search: (query: string) => void;
}

View File

@@ -0,0 +1,72 @@
var UndeckedClipboard = new class UndeckedClipboard {
constructor() {}
hasKeyInClipboard() {
return localStorage.getItem('clipboard') != undefined && localStorage.getItem('clipboard').length > 0;
}
copyKey(originKeyX: number, originKeyY: number) {
localStorage.setItem('clipboard', `key_copy_${PageHandler.currentPageID}.${originKeyX}.${originKeyY}`);
UndeckedNotification('Key has been copied to clipboard');
}
copyGhostKey(originKeyX: number, originKeyY: number) {
localStorage.setItem('clipboard', `key_ghost_${PageHandler.currentPageID}.${originKeyX}.${originKeyY}`);
UndeckedNotification('Key has been copied to clipboard as a ghost');
}
cutKey(originKeyX: number, originKeyY: number) {
//TODO: Implement something in the front end to show that the item is being cut right now
localStorage.setItem('clipboard', `key_cut_${PageHandler.currentPageID}.${originKeyX}.${originKeyY}`);
UndeckedNotification('Key has been cut to clipboard');
}
pasteKey(destinationKeyX: number, destinationKeyY: number) {
if (this.hasKeyInClipboard()) {
var clipboard = this.decodeClipboard();
if (clipboard.elementType == 'key') {
socket.emit(
'page',
'operation',
clipboard.operationType,
clipboard.id,
clipboard.x,
clipboard.y,
PageHandler.currentPageID,
destinationKeyX,
destinationKeyY
);
if (clipboard.operationType == 'cut') localStorage.setItem('clipboard', '');
}
}
}
decodeClipboard(): {
elementType: 'key';
operationType: 'cut' | 'copy' | 'ghost';
id: string;
x?: number;
y?: number;
} {
var clipboard = {
elementType: null,
operationType: null,
id: null,
x: null,
y: null
};
if (this.hasKeyInClipboard()) {
var raw = localStorage.getItem('clipboard').split('_');
clipboard.elementType = raw[0];
clipboard.operationType = raw[1];
var args = raw[2].split('.');
clipboard.id = args[0];
clipboard.x = args[1];
clipboard.y = args[2];
return clipboard;
} else return clipboard;
}
}();

View File

@@ -0,0 +1,75 @@
declare var ce: (
type: string,
classList?: string | string[],
attributes?: { [key: string]: string },
innerText?: string,
innerHTML?: string
) => HTMLElement;
declare var UndeckedNotification: (message: string, type?: 'info' | 'error', time?: number) => void;
declare var io: any;
var responseToken = Math.random().toString(16).substr(2, 8);
var socket = io('/');
socket.on('connect', () => {
console.log('Connected to server');
socket.emit('init', 'home');
});
var fontSizeRatio: number = null;
var renderQuality: number = null;
socket.on('quality', (quality: number) => {
document.querySelectorAll('canvas.ready').forEach((canvas: HTMLCanvasElement) => {
canvas.width = quality;
canvas.height = quality;
var context = canvas.getContext('2d');
context.textBaseline = 'middle';
context.textAlign = 'center';
});
renderQuality = quality;
fontSizeRatio = quality / 100;
});
socket.on('pagelist', (pagelist: PageListItem[]) => {
(function render() {
if (fontSizeRatio != null) PageList.render(pagelist);
else setTimeout(render, 100);
})();
});
socket.on('connectedlist', (connected: ConnectedList[]) => Connections.renderConnected(connected));
socket.on('page', (query: string, ...args: any[]) => {
switch (query) {
case 'updatename':
var pageID: string = args[0];
var newName: string = args[1];
PageList.updateName(pageID, newName);
break;
case 'updatekey':
var pageID: string = args[0];
var x: string = args[1];
var y: string = args[2];
var key: Page_Key = args[3];
var returnResponseToken: string = args[4];
if (PageHandler.currentPageID == pageID) {
if (PageHandler.currentPage.keys[y] != undefined && PageHandler.currentPage.keys[y][x] != undefined)
PageHandler.currentPage.keys[y][x] = key;
if (responseToken != returnResponseToken) {
KeyHandler.render(x, y, key);
if (Editor.currentKey != undefined && Editor.currentKey.id == key.id) Editor.open(x, y, key);
}
}
break;
}
});

View File

@@ -0,0 +1,206 @@
var Connections: Connections = {
elements: {
connectionbrowser: document.querySelector('.connectionbrowser'),
table: document.querySelector('.connectedtable'),
dialog: {
container: document.querySelector('.connectiondialog'),
dialog: document.querySelector('.connectiondialog').querySelector('.dialog'),
fields: document.querySelector('.connectiondialog').querySelector('.fields'),
message: document.querySelector('.connectiondialog').querySelector('.message'),
link: document.querySelector('.connectiondialog').querySelector('.link'),
cancel: document.querySelector('.connectiondialog').querySelector('.cn'),
connect: document.querySelector('.connectiondialog').querySelector('.co')
}
},
init() {
Connections.elements.connectionbrowser.querySelectorAll('.available').forEach((item: HTMLDivElement) => {
var integrationID = item.getAttribute('integrationID');
var connectionType = item.getAttribute('connectionType');
var button: HTMLDivElement = item.querySelector('.button');
button.onclick = () => Connections.requestNewDevice(integrationID, connectionType);
});
},
requestNewDevice(integrationID: string, connectionType: string) {
socket.emit(
'connections',
'request',
integrationID,
connectionType,
(connectionRequestData: ConnectionRequestData) => {
if (connectionRequestData.fields && connectionRequestData.fields.length > 0)
Connections.openDialog(integrationID, connectionType, connectionRequestData);
else UndeckedNotification('Unable to add a new device of this type.', 'error', 5000);
}
);
},
openDialog(integrationID, connectionType, connectionRequestData: ConnectionRequestData) {
Connections.elements.dialog.fields.innerHTML = '';
var nameField: Connection_Field = {
id: '_internal_name',
name: 'Connection Name',
type: 'text'
};
connectionRequestData.fields = [
nameField,
...connectionRequestData.fields
];
if (connectionRequestData.message != undefined) {
Connections.elements.dialog.message.style.display = 'block';
Connections.elements.dialog.message.innerText = connectionRequestData.message;
} else Connections.elements.dialog.message.style.display = 'none';
if (connectionRequestData.link != undefined) {
Connections.elements.dialog.link.style.display = 'inline-block';
Connections.elements.dialog.link.innerText = connectionRequestData.link.title;
Connections.elements.dialog.link.href = connectionRequestData.link.address;
} else Connections.elements.dialog.link.style.display = 'none';
connectionRequestData.fields.forEach((field) => {
var fieldcontainer = ce('div', [
'field',
`field_${field.id}`
]);
Connections.elements.dialog.fields.appendChild(fieldcontainer);
var label = ce('div', 'fieldlabel', null, `${field.name}`);
fieldcontainer.appendChild(label);
var input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement = null;
switch (field.type) {
case 'number':
case 'text':
input = <HTMLInputElement>ce('input', 'input', { type: field.type, fieldID: field.id });
if (field.value != undefined) input.value = field.value;
fieldcontainer.appendChild(input);
break;
case 'select':
input = <HTMLInputElement>ce('select', 'input', { fieldID: field.id });
if (field.values != undefined && field.values.length > 0)
for (let i = 0; i < field.values.length; i++) {
var option = ce('option', null, { value: field.values[i].id }, field.values[i].text);
if (field.value != undefined && field.values[i].id == field.value)
option.setAttribute('selected', '');
input.appendChild(option);
}
fieldcontainer.appendChild(input);
break;
}
});
Connections.elements.dialog.cancel.onclick = Connections.closeDialog;
Connections.elements.dialog.connect.onclick = () => {
var properties = {};
var inputs: NodeListOf<HTMLInputElement> = Connections.elements.dialog.fields.querySelectorAll('.input');
for (let i = 0; i < inputs.length; i++)
if (inputs[i].hasAttribute('fieldID')) properties[inputs[i].getAttribute('fieldID')] = inputs[i].value;
Connections.elements.dialog.dialog.style.display = 'none';
socket.emit(
'connections',
'create',
integrationID,
connectionType,
properties,
(succeed: boolean, errormessage?: string) => {
if (succeed == true) {
Connections.closeDialog();
} else {
Connections.elements.dialog.dialog.style.display = 'block';
UndeckedNotification(
errormessage != undefined ? errormessage : 'Unable to validate connection',
'error',
5000
);
}
}
);
};
Connections.elements.dialog.dialog.style.display = 'block';
Connections.elements.dialog.container.style.display = 'flex';
},
closeDialog() {
Connections.elements.dialog.container.style.display = 'none';
},
renderConnected(connectList: ConnectedList[]) {
var table = Connections.elements.table;
for (let i = 0; i < connectList.length; i++) {
var connected = connectList[i];
if (table.querySelector(`tr[connectionid="${connected.connectionID}"]`) == null) {
var row = ce('tr', null, { connectionID: connected.connectionID });
var status = ce('td', 'status');
var statuscontainer = ce('div', 'statuscontainer');
statuscontainer.appendChild(
ce('div', [
'value',
'online'
])
);
status.appendChild(statuscontainer);
row.appendChild(status);
row.appendChild(ce('td', 'name', null, connected.name));
row.appendChild(ce('td', 'integration', null, connected.integrationName));
row.appendChild(ce('td', 'type', null, connected.connectionType));
table.appendChild(row);
}
}
}
};
Connections.init();
interface Connections {
elements: {
connectionbrowser: HTMLDivElement;
table: HTMLTableElement;
dialog: {
container: HTMLDivElement;
dialog: HTMLDivElement;
message: HTMLDivElement;
link: HTMLAnchorElement;
fields: HTMLDivElement;
cancel: HTMLDivElement;
connect: HTMLDivElement;
};
};
init: () => void;
requestNewDevice: (integrationID: string, connectionType: string) => void;
openDialog: (integrationID, connectionID, connectionRequestData: ConnectionRequestData) => void;
closeDialog: () => void;
renderConnected: (connectList: ConnectedList[]) => void;
}
interface Connection_Field {
id: string;
name: string;
type: 'text' | 'number' | 'select';
values?: { id: string; text: string }[];
value?: string;
}
interface ConnectedList {
connectionID: string;
integrationName: string;
connectionType: string;
name: string;
online: boolean;
}
interface ConnectionRequestData {
fields: Connection_Field[];
message?: string;
link?: { address: string; title: string };
}

View File

@@ -0,0 +1,137 @@
var ContextMenu = new class ContextMenu {
elements: {
menu: HTMLDivElement;
items: {
copy: HTMLDivElement;
paste: HTMLDivElement;
cut: HTMLDivElement;
ghost: HTMLDivElement;
osc: HTMLDivElement;
http: HTMLDivElement;
};
};
open: boolean;
contextHolder: HTMLElement;
constructor() {
this.elements = {
menu: document.querySelector('.contextmenu'),
items: {
copy: document.querySelector('.contextmenu').querySelector('.copy'),
paste: document.querySelector('.contextmenu').querySelector('.paste'),
cut: document.querySelector('.contextmenu').querySelector('.cut'),
ghost: document.querySelector('.contextmenu').querySelector('.ghost'),
osc: document.querySelector('.contextmenu').querySelector('.osc'),
http: document.querySelector('.contextmenu').querySelector('.http')
}
};
this.open = false;
this.contextHolder = null;
window.addEventListener('contextmenu', (ev: MouseEvent) => this.handle(this.elements.menu, ev));
window.addEventListener('mousedown', (ev: MouseEvent) => {
if (this.open == true)
if (ev.target) {
var target = ev.target as HTMLElement;
if (
target.classList.contains('contextmenu') ||
(target.parentElement != undefined && target.parentElement.classList.contains('contextmenu'))
) {
} else {
this.close();
}
} else this.close();
});
}
setItems(items: DropdownItemTypes[]) {
for (var type in this.elements.items) {
if (items.includes(<DropdownItemTypes>type)) this.elements.items[type].style.display = 'block';
else this.elements.items[type].style.display = 'none';
}
}
close() {
this.elements.menu.style.display = 'none';
if (this.contextHolder != null) {
this.contextHolder.classList.remove('context');
this.contextHolder = null;
}
this.open = false;
}
handle(menu: HTMLDivElement, ev: MouseEvent) {
if (ev.target != undefined) {
var target = ev.target as HTMLElement;
var types = {
key: (element: HTMLElement) => {
if (element.hasAttribute('x') && element.hasAttribute('y')) {
var keyX = element.getAttribute('x');
var keyY = element.getAttribute('y');
var items: DropdownItemTypes[] = [
'osc',
'http'
];
if (PageHandler.currentPage.keys[keyY][keyX].state.type != 'empty') items.push('copy', 'cut');
if (PageHandler.currentPage.keys[keyY][keyX].state.type == 'custom') items.push('ghost');
if (UndeckedClipboard.hasKeyInClipboard()) items.push('paste');
this.setItems(items);
this.elements.items.copy.onclick = () => {
UndeckedClipboard.copyKey(parseInt(keyX), parseInt(keyY));
this.close();
};
this.elements.items.cut.onclick = () => {
UndeckedClipboard.cutKey(parseInt(keyX), parseInt(keyY));
this.close();
};
this.elements.items.paste.onclick = () => {
UndeckedClipboard.pasteKey(parseInt(keyX), parseInt(keyY));
this.close();
};
this.elements.items.ghost.onclick = () => {
UndeckedClipboard.copyGhostKey(parseInt(keyX), parseInt(keyY));
this.close();
};
return items.length > 0;
}
}
};
for (var type in types)
if (target.classList.contains(type)) {
ev.preventDefault();
var valid = types[type](target);
if (valid) {
this.contextHolder = target;
target.classList.add('context');
setTimeout(() => {
this.open = true;
}, 100);
menu.style.left = `${ev.pageX}px`;
menu.style.top = `${ev.pageY - 50}px`;
menu.style.display = 'block';
}
return;
}
}
}
}();
type DropdownItemTypes = 'copy' | 'paste' | 'cut' | 'ghost' | 'osc' | 'http';

View File

@@ -0,0 +1,468 @@
var Editor: Editor = {
elements: {
container: document.querySelector('.edit'),
appearence: {
text: {
content: document.querySelector('.ap_text_value'),
size: document.querySelector('.ap_text_size'),
color: document.querySelector('.ap_text_color'),
offsetX: document.querySelector('.ap_text_offsetx').querySelector('input[type="range"]'),
offsetY: document.querySelector('.ap_text_offsety').querySelector('input[type="range"]')
},
background: {
color: document.querySelector('.ap_background_color')
},
image: {
size: document.querySelector('.ap_image_size').querySelector('input[type="range"]'),
offsetX: document.querySelector('.ap_image_offsetx').querySelector('input[type="range"]'),
offsetY: document.querySelector('.ap_image_offsety').querySelector('input[type="range"]'),
rotation: document.querySelector('.ap_image_rotation').querySelector('input[type="range"]')
}
},
imageui: {
imagetypes: document.querySelector('.imagetype'),
imageinfopanels: document.querySelector('.imagemenu').querySelector('.info'),
imagepanels: document.querySelector('.row.image').querySelector('.panels'),
advanced: document.querySelector('.row.image').querySelector('.advanced')
},
buttonui: {
buttontypes: document.querySelector('.buttontype'),
visual: document.querySelector('.vis'),
actioninner: document.querySelector('.actioninner'),
containerTitles: document.querySelector('.editcontainer').querySelectorAll('.containertitle')
},
checks: {
toggle: {
container: document.querySelector('.checks').querySelector('.check.toggle'),
checkbox: document.querySelector('.checks').querySelector('.check.toggle').querySelector('input')
},
confirm: {
container: document.querySelector('.checks').querySelector('.check.confirm'),
checkbox: document.querySelector('.checks').querySelector('.check.confirm').querySelector('input')
}
}
},
currentX: null,
currentY: null,
currentKey: null,
currentImageType: null,
currentButtonType: null,
isToggle: null,
isConfirm: null,
isOpen: false,
registerChange() {
if (Editor.currentKey != null) {
var editorExport = Editor.export();
KeyHandler.render(Editor.currentX, Editor.currentY, editorExport);
socket.emit(
'page',
'setkey',
PageHandler.currentPageID,
Editor.currentX,
Editor.currentY,
editorExport,
responseToken
);
}
},
listeners() {
for (var inputCategory in Editor.elements.appearence) {
for (var inputName in Editor.elements.appearence[inputCategory]) {
var inputElement: HTMLInputElement = Editor.elements.appearence[inputCategory][inputName];
inputElement.oninput = Editor.registerChange;
if (inputElement.type == 'range')
((range: HTMLInputElement, number: HTMLInputElement) => {
range.addEventListener('input', () => {
number.value = range.value;
});
number.addEventListener('input', () => {
range.value = number.value;
Editor.registerChange();
});
})(inputElement, inputElement.parentElement.querySelector('input[type="number"]'));
}
}
Editor.elements.imageui.imagetypes.querySelectorAll('.selectoritem').forEach((item: HTMLDivElement) => {
item.onclick = () => {
var panelType: 'none' | 'icon' | 'upload' = <any>item.getAttribute('panel');
Editor.selectImageTab(panelType);
};
});
Editor.elements.buttonui.buttontypes.querySelectorAll('.buttonitem').forEach((item: HTMLDivElement) => {
item.onclick = () => {
var buttonType: 'empty' | 'custom' | 'pageup' | 'pagedown' = <any>item.getAttribute('type');
Editor.selectButtonType(buttonType);
};
});
Editor.elements.checks.toggle.container.onclick = () =>
Editor.setActionOptions(!Editor.elements.checks.toggle.checkbox.checked, Editor.isConfirm);
Editor.elements.checks.confirm.container.onclick = () =>
Editor.setActionOptions(Editor.isToggle, !Editor.elements.checks.confirm.checkbox.checked);
},
export(): Page_Key {
if (Editor.isOpen) {
return {
id: Editor.currentKey.id,
actions: Editor.currentKey.actions,
state: {
type: Editor.currentButtonType,
confirm: Editor.isConfirm,
toggle: Editor.isToggle
},
appearence: {
text: Editor.getElementCategoryValues('text'),
background: Editor.getElementCategoryValues('background'),
image: Editor.getElementCategoryValues('image')
}
};
}
return null;
},
open(x: string, y: string, key: Page_Key) {
if (Editor.isOpen) Editor.close();
Editor.currentKey = key;
Editor.currentX = x;
Editor.currentY = y;
Editor.isOpen = true;
document.querySelectorAll('.actionselector').forEach((selector: HTMLInputElement) => {
selector.value = '';
});
// --------- TEXT ---------
{
var textElems = Editor.elements.appearence.text;
var textData: Page_Key_Text = Editor.getAppearenceCategory('text', key);
textElems.content.value = textData.value != undefined ? textData.value : '';
textElems.size.value = textData.size != undefined ? String(textData.size) : '10';
textElems.color.value = textData.color != undefined ? textData.color : '#000000';
textElems.offsetX.value = textData.offsetX != undefined ? String(textData.offsetX) : '0';
var offsetXNumber: HTMLInputElement = textElems.offsetX.parentElement.querySelector('input[type="number"]');
offsetXNumber.value = textData.offsetX != undefined ? String(textData.offsetX) : '0';
textElems.offsetY.value = textData.offsetY != undefined ? String(textData.offsetY) : '0';
var offsetYNumber: HTMLInputElement = textElems.offsetY.parentElement.querySelector('input[type="number"]');
offsetYNumber.value = textData.offsetY != undefined ? String(textData.offsetY) : '0';
}
// --------- BACKGROUND ---------
{
var backgroundElems = Editor.elements.appearence.background;
var backgroundData: Page_Key_Background = Editor.getAppearenceCategory('background', key);
backgroundElems.color.value = backgroundData.color != undefined ? backgroundData.color : '#000000';
}
// --------- IMAGE ---------
{
var imageElems = Editor.elements.appearence.image;
var imageData: Page_Key_Image = Editor.getAppearenceCategory('image', key);
imageElems.size.value = imageData.size != undefined ? String(imageData.size) : '100';
var sizeNumber: HTMLInputElement = imageElems.size.parentElement.querySelector('input[type="number"]');
sizeNumber.value = imageData.size != undefined ? String(imageData.size) : '100';
imageElems.rotation.value = imageData.rotation != undefined ? String(imageData.rotation) : '0';
var rotationNumber: HTMLInputElement = imageElems.rotation.parentElement.querySelector(
'input[type="number"]'
);
rotationNumber.value = imageData.rotation != undefined ? String(imageData.rotation) : '0';
imageElems.offsetX.value = imageData.offsetX != undefined ? String(imageData.offsetX) : '0';
var offsetXNumber: HTMLInputElement = imageElems.offsetX.parentElement.querySelector(
'input[type="number"]'
);
offsetXNumber.value = imageData.offsetX != undefined ? String(imageData.offsetX) : '0';
imageElems.offsetY.value = imageData.offsetY != undefined ? String(imageData.offsetY) : '0';
var offsetYNumber: HTMLInputElement = imageElems.offsetY.parentElement.querySelector(
'input[type="number"]'
);
offsetYNumber.value = imageData.offsetY != undefined ? String(imageData.offsetY) : '0';
}
if (imageData.address != undefined) {
Editor.selectImageTab('upload', true);
} else if (imageData.iconid != undefined) {
Editor.selectImageTab('icon', true);
Icons.select(imageData.iconid);
}
Editor.selectButtonType(key.state.type, true);
Editor.setActionOptions(key.state.toggle, key.state.confirm, true);
var actions = key.actions != undefined ? key.actions : { up: {}, down: {} };
ActionEditor.open(actions, x, y, key, PageHandler.currentPageID);
Editor.elements.container.classList.remove('disabled');
},
close() {
Editor.isOpen = false;
Editor.currentKey = null;
Editor.currentX = null;
Editor.currentY = null;
document.querySelectorAll('.actionselector').forEach((selector: HTMLInputElement) => {
selector.value = '';
});
var text = Editor.elements.appearence.text;
text.content.value = 'Button';
text.size.value = '20';
text.color.value = '#ffffff';
var background = Editor.elements.appearence.background;
background.color.value = '#000000';
Editor.elements.container.classList.add('disabled');
Editor.selectImageTab('none', true);
ActionEditor.close();
Icons.deselect();
},
selectImageTab(panelType: 'none' | 'icon' | 'upload', ignoreUpdate = false) {
Editor.elements.imageui.imagetypes.querySelectorAll('.selectoritem').forEach((selectoritem: HTMLDivElement) => {
if (panelType == selectoritem.getAttribute('panel')) selectoritem.classList.add('selected');
else selectoritem.classList.remove('selected');
});
Editor.elements.imageui.imagepanels.querySelectorAll('.panel').forEach((panel: HTMLDivElement) => {
if (panelType == panel.getAttribute('panel')) panel.classList.add('selected');
else panel.classList.remove('selected');
});
Editor.elements.imageui.imageinfopanels.querySelectorAll('.infopanel').forEach((infopanel: HTMLDivElement) => {
if (panelType == infopanel.getAttribute('panel')) infopanel.classList.add('selected');
else infopanel.classList.remove('selected');
});
Editor.currentImageType = panelType != 'none' ? panelType : null;
if (panelType == 'none') Editor.elements.imageui.advanced.style.display = 'none';
else Editor.elements.imageui.advanced.style.display = 'flex';
if (panelType == 'icon') Icons.loadOnScreen();
if (ignoreUpdate == false) Editor.registerChange();
},
selectButtonType(buttonType: KeyTypes, ignoreUpdate = false) {
if (buttonType == 'ghost') buttonType = 'custom';
Editor.elements.buttonui.buttontypes.querySelectorAll('.buttonitem').forEach((selectoritem: HTMLDivElement) => {
if (buttonType == selectoritem.getAttribute('type')) selectoritem.classList.add('selected');
else selectoritem.classList.remove('selected');
});
Editor.currentButtonType = buttonType;
switch (buttonType) {
case 'empty':
case 'pageup':
case 'pagedown':
case 'currentpage':
Editor.elements.buttonui.visual.style.display = 'none';
Editor.elements.buttonui.actioninner.style.display = 'none';
Editor.elements.buttonui.containerTitles.forEach((title) => (title.style.display = 'none'));
break;
case 'custom':
Editor.elements.buttonui.visual.style.display = 'block';
Editor.elements.buttonui.actioninner.style.display = 'block';
Editor.elements.buttonui.containerTitles.forEach((title) => (title.style.display = 'flex'));
break;
}
if (ignoreUpdate == false) Editor.registerChange();
},
setActionOptions(toggle: boolean, confirm: boolean, ignoreUpdate = false) {
if (toggle) {
ActionEditor.elements.up.container.style.display = 'none';
ActionEditor.elements.latch.container.style.display = 'block';
} else {
ActionEditor.elements.up.container.style.display = 'block';
ActionEditor.elements.latch.container.style.display = 'none';
}
if (confirm) {
ActionEditor.elements.down.container.style.display = 'none';
} else {
ActionEditor.elements.down.container.style.display = 'block';
}
Editor.isToggle = toggle;
Editor.isConfirm = confirm;
Editor.elements.checks.toggle.checkbox.checked = toggle;
Editor.elements.checks.confirm.checkbox.checked = confirm;
if (ignoreUpdate == false) Editor.registerChange();
},
getAppearenceCategory(category: Editor_Categories, key: Page_Key = Editor.currentKey) {
switch (category) {
case 'text':
var textData: Page_Key_Text =
key.appearence != undefined && key.appearence.text != undefined
? key.appearence.text
: { value: '', size: 10, color: '#000000', offsetX: 0, offsetY: 0 };
return textData;
case 'background':
var backgroundData: Page_Key_Background =
key.appearence != undefined && key.appearence.background != undefined
? key.appearence.background
: { color: '#000000' };
return backgroundData;
case 'image':
var imageData: Page_Key_Image =
key.appearence != undefined && key.appearence.image != undefined
? key.appearence.image
: { offsetX: 0, offsetY: 0, size: 100, rotation: 0 };
return imageData;
}
},
getElementCategoryValues(category: Editor_Categories) {
switch (category) {
case 'text':
var textData: Page_Key_Text = {
value: Editor.currentButtonType != 'empty' ? Editor.elements.appearence.text.content.value : '',
color:
Editor.currentButtonType != 'empty' ? Editor.elements.appearence.text.color.value : '#ffffff',
size:
Editor.currentButtonType != 'empty' ? parseInt(Editor.elements.appearence.text.size.value) : 20,
offsetX:
Editor.currentButtonType != 'empty'
? parseFloat(Editor.elements.appearence.text.offsetX.value)
: 0,
offsetY:
Editor.currentButtonType != 'empty'
? parseFloat(Editor.elements.appearence.text.offsetY.value)
: 0
};
return textData;
case 'background':
var backgroundData: Page_Key_Background = {
color:
Editor.currentButtonType != 'empty'
? Editor.elements.appearence.background.color.value
: '#000000'
};
return backgroundData;
case 'image':
var imageData: Page_Key_Image = {
size:
Editor.currentButtonType != 'empty'
? parseFloat(Editor.elements.appearence.image.size.value)
: 100,
rotation:
Editor.currentButtonType != 'empty'
? parseFloat(Editor.elements.appearence.image.rotation.value)
: 0,
offsetX:
Editor.currentButtonType != 'empty'
? parseFloat(Editor.elements.appearence.image.offsetX.value)
: 0,
offsetY:
Editor.currentButtonType != 'empty'
? parseFloat(Editor.elements.appearence.image.offsetY.value)
: 0
};
if (Editor.currentImageType != null && Editor.currentButtonType != 'empty') {
switch (Editor.currentImageType) {
case 'icon':
imageData.iconid = Icons.currentSelected;
imageData.iconstyle = 'white';
break;
}
}
return imageData;
}
}
};
Editor.listeners();
interface Editor {
elements: {
container: HTMLDivElement;
appearence: {
text: {
content: HTMLInputElement;
size: HTMLSelectElement;
color: HTMLInputElement;
offsetX: HTMLInputElement;
offsetY: HTMLInputElement;
};
background: {
color: HTMLInputElement;
};
image: {
size: HTMLInputElement;
offsetX: HTMLInputElement;
offsetY: HTMLInputElement;
rotation: HTMLInputElement;
};
};
imageui: {
imagetypes: HTMLDivElement;
imageinfopanels: HTMLDivElement;
imagepanels: HTMLDivElement;
advanced: HTMLDivElement;
};
buttonui: {
buttontypes: HTMLDivElement;
visual: HTMLDivElement;
actioninner: HTMLDivElement;
containerTitles: NodeListOf<HTMLDivElement>;
};
checks: {
toggle: {
container: HTMLDivElement;
checkbox: HTMLInputElement;
};
confirm: {
container: HTMLDivElement;
checkbox: HTMLInputElement;
};
};
};
isOpen: boolean;
currentKey: Page_Key;
currentX: string;
currentY: string;
currentImageType: 'icon' | 'upload';
currentButtonType: KeyTypes;
isToggle: boolean;
isConfirm: boolean;
listeners: () => void;
export: () => Page_Key;
open: (x: string, y: string, key: Page_Key) => void;
close: () => void;
registerChange: () => void;
selectImageTab: (panelType: string, ignoreUpdate?: boolean) => void;
selectButtonType: (buttonType: KeyTypes, ignoreUpdate?: boolean) => void;
setActionOptions: (toggle: boolean, confirm: boolean, ignoreUpdate?: boolean) => void;
getAppearenceCategory: (category: Editor_Categories, key?: Page_Key) => any;
getElementCategoryValues: (category: Editor_Categories) => any;
}
type Editor_Categories = 'text' | 'background' | 'image';

View File

@@ -0,0 +1,85 @@
var Icons: Icons = {
elements: {
container: document.querySelector('.panel.icons').querySelector('.list')
},
resizeTimeout: null,
currentSelected: null,
loadOnScreen() {
Icons.elements.container.querySelectorAll('.icon').forEach((icon: HTMLDivElement) => {
var position = icon.getBoundingClientRect();
// checking for partial visibility
if (position.top < window.innerHeight && position.bottom >= 0) {
if (icon.hasAttribute('notloaded')) {
icon.removeAttribute('notloaded');
var iconID = icon.getAttribute('iconID');
var whiteImg: HTMLImageElement = icon.querySelector('img.white');
var blackImg: HTMLImageElement = icon.querySelector('img.black');
whiteImg.src = `/stc/materialicons/white/${iconID}.png`;
blackImg.src = `/stc/materialicons/black/${iconID}.png`;
whiteImg.style.opacity = '1';
blackImg.style.opacity = '0';
}
}
icon.onclick = () => Icons.select(icon.getAttribute('iconID'));
});
},
listeners() {
var counter = 0;
Icons.elements.container.onscroll = () => {
counter++;
if (counter > 5) {
Icons.loadOnScreen();
counter = 0;
}
clearTimeout(Icons.resizeTimeout);
Icons.resizeTimeout = setTimeout(() => {
Icons.loadOnScreen();
}, 100);
};
// Icons.loadOnScreen();
},
select(iconID: string, ignoreUpdate: boolean = false) {
var icons = Icons.elements.container.querySelectorAll('.icon');
for (let i = 0; i < icons.length; i++) {
var testIconID = icons[i].getAttribute('iconID');
if (iconID != testIconID) icons[i].classList.remove('selected');
else icons[i].classList.add('selected');
}
Icons.currentSelected = iconID;
if (ignoreUpdate == false) Editor.registerChange();
},
deselect() {
var icons = Icons.elements.container.querySelectorAll('.icon');
for (let i = 0; i < icons.length; i++) icons[i].classList.remove('selected');
Icons.currentSelected = null;
}
};
Icons.listeners();
interface Icons {
elements: {
container: HTMLDivElement;
};
resizeTimeout: any;
currentSelected: string;
loadOnScreen: () => void;
listeners: () => void;
select: (iconID: string, ignoreUpdate?: boolean) => void;
deselect: () => void;
}

View File

@@ -0,0 +1,57 @@
window.addEventListener('keydown', (ev: KeyboardEvent) => {
if (document.activeElement && document.activeElement.nodeName != 'INPUT') {
if ((isWin() && ev.ctrlKey) || (isMac() && ev.metaKey)) {
switch (ev.key) {
case 'c':
ev.preventDefault();
if (PageHandler.currentPageID && KeyHandler.selected.length == 1) {
UndeckedClipboard.copyKey(
parseInt(KeyHandler.selected[0].split(',')[0]),
parseInt(KeyHandler.selected[0].split(',')[1])
);
}
break;
case 'x':
ev.preventDefault();
if (PageHandler.currentPageID && KeyHandler.selected.length == 1) {
UndeckedClipboard.cutKey(
parseInt(KeyHandler.selected[0].split(',')[0]),
parseInt(KeyHandler.selected[0].split(',')[1])
);
}
break;
case 'v':
ev.preventDefault();
if (PageHandler.currentPageID && KeyHandler.selected.length == 1) {
UndeckedClipboard.pasteKey(
parseInt(KeyHandler.selected[0].split(',')[0]),
parseInt(KeyHandler.selected[0].split(',')[1])
);
}
break;
case 'g':
ev.preventDefault();
if (PageHandler.currentPageID && KeyHandler.selected.length == 1) {
UndeckedClipboard.copyGhostKey(
parseInt(KeyHandler.selected[0].split(',')[0]),
parseInt(KeyHandler.selected[0].split(',')[1])
);
}
break;
}
} else {
switch (ev.key) {
case 'Delete':
var x = parseInt(KeyHandler.selected[0].split(',')[0]);
var y = parseInt(KeyHandler.selected[0].split(',')[1]);
socket.emit('page', 'operation', 'delete', PageHandler.currentPageID, x, y);
Editor.close();
KeyHandler.select(String(x), String(y));
break;
}
}
}
});

View File

@@ -0,0 +1,372 @@
var KeyHandler: KeyHandler = {
elements: {
keys: {}
},
selected: [],
imagecache: {},
ghostImage: null,
init() {
KeyHandler.ghostImage = new Image();
KeyHandler.ghostImage.src = '/stc/icon/ghost.png';
KeyHandler.ghostImage.onload = () => {
for (var y = 0; y < 4; y++) {
if (KeyHandler.elements.keys[y] == undefined) KeyHandler.elements.keys[y] = {};
for (var x = 0; x < 8; x++) {
var keyCheck: HTMLDivElement = document.querySelector(`.key[y="${y}"][x="${x}"]`);
if (keyCheck) {
((keyX: number, keyY: number, key: HTMLDivElement) => {
var canvas = key.querySelector('canvas');
var context = canvas.getContext('2d');
KeyHandler.elements.keys[keyY][keyX] = { key, canvas, context };
context.textBaseline = 'middle';
context.textAlign = 'center';
key.onclick = () => {
if (altDown == false) KeyHandler.select(String(keyX), String(keyY));
else {
socket.emit('page', 'executekey', PageHandler.currentPageID, keyX, keyY);
}
};
})(x, y, keyCheck);
}
}
}
};
},
clear() {
for (var y in KeyHandler.elements.keys) {
for (var x in KeyHandler.elements.keys[y]) {
var key = KeyHandler.elements.keys[y][x];
key.context.clearRect(0, 0, 100, 100);
key.key.classList.remove('selected');
}
}
KeyHandler.selected = [];
Editor.close();
},
render(x: string, y: string, data: Page_Key) {
if (KeyHandler.elements.keys[String(y)]) {
if (KeyHandler.elements.keys[String(y)][String(x)]) {
var context = KeyHandler.elements.keys[String(y)][String(x)].context;
context.clearRect(0, 0, renderQuality, renderQuality);
if (data.state.type == 'custom') {
var appearence = data.appearence;
render(appearence);
} else if (data.state.type == 'ghost') {
var appearence = data.appearence;
if (appearence == undefined) appearence = {};
appearence.system = { ghost: true };
render(appearence);
} else if (data.state.type == 'pageup') {
render({
text: { value: 'Up', color: '#ffffff', size: 18, offsetX: 0, offsetY: 25 },
background: { color: '#4676b7' },
image: {
size: 100,
rotation: 0,
offsetX: 0,
offsetY: -15,
iconid: 'keyboard_arrow_up',
iconstyle: 'white'
},
system: {
border: {
color: '#253e5e',
thickness: 8
}
}
});
} else if (data.state.type == 'pagedown') {
render({
text: { value: 'Down', color: '#ffffff', size: 18, offsetX: 0, offsetY: -25 },
background: { color: '#4676b7' },
image: {
size: 100,
rotation: 0,
offsetX: 0,
offsetY: 15,
iconid: 'keyboard_arrow_down',
iconstyle: 'white'
},
system: {
border: {
color: '#253e5e',
thickness: 8
}
}
});
} else if (data.state.type == 'currentpage') {
render({
text: {
value: `Page\\n\\n${PageHandler.currentIndex + 1}`,
color: '#ffffff',
size: 22,
offsetX: 0,
offsetY: 0
},
background: { color: '#4676b7' },
system: {
border: {
color: '#253e5e',
thickness: 8
}
}
});
}
} else console.error(`Invalid x '${x}'`);
} else console.error(`Invalid y '${y}'`);
function render(appearence: Page_Key_Appearence) {
if (appearence.background != undefined) {
context.fillStyle = appearence.background.color;
context.fillRect(0, 0, renderQuality, renderQuality);
context.fill();
}
if (appearence.text != undefined) {
context.fillStyle = appearence.text.color;
context.font = `800 ${appearence.text.size * fontSizeRatio}px "Montserrat"`;
var text = appearence.text.value;
var lineHeight = appearence.text.size * fontSizeRatio;
var centerX = renderQuality / 2 + appearence.text.offsetX / 100 * (renderQuality * 2);
var centerY = renderQuality / 2 + appearence.text.offsetY / 100 * renderQuality;
var canvasYCounter = centerY;
var words = text.replace(/\\n/g, ' \\n ').split(' ');
var line = '';
var totalLineHeight = 0;
for (var n = 0; n < words.length; n++) {
if (words[n].length == 0) continue;
var testLine = line + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (words[n] != '\\n')
if (testWidth > renderQuality && n > 0) {
line = words[n] + ' ';
totalLineHeight += lineHeight;
} else {
line = testLine;
}
else {
totalLineHeight += lineHeight;
line = '';
}
}
line = '';
canvasYCounter = canvasYCounter - totalLineHeight / 2;
var firstSkip = false;
for (var n = 0; n < words.length; n++) {
if (words[n].length == 0) continue;
var testLine = line + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (words[n] != '\\n')
if (testWidth > renderQuality && n > 0) {
context.fillText(line, centerX, canvasYCounter);
line = words[n] + ' ';
canvasYCounter += lineHeight;
} else {
line = testLine;
}
else {
context.fillText(line, centerX, canvasYCounter);
line = '';
canvasYCounter += firstSkip ? lineHeight * 2 : lineHeight;
if (firstSkip) firstSkip = false;
}
}
context.fillText(line, centerX, canvasYCounter);
}
if (appearence.image != undefined) {
var imageAddress =
appearence.image.address != undefined
? appearence.image.address
: appearence.image.iconid != undefined
? `/stc/materialicons/white/${appearence.image.iconid}.png`
: null;
var imageSize =
appearence.image.size != undefined ? appearence.image.size / 100 * renderQuality : renderQuality;
if (imageAddress) {
var centerX = renderQuality / 2 + appearence.image.offsetX / 100 * renderQuality;
var centerY = renderQuality / 2 + appearence.image.offsetY / 100 * renderQuality;
if (KeyHandler.imagecache[imageAddress] != undefined)
renderImage(KeyHandler.imagecache[imageAddress]);
else {
KeyHandler.imagecache[imageAddress] = new Image();
KeyHandler.imagecache[imageAddress].src = imageAddress;
KeyHandler.imagecache[imageAddress].onload = () =>
renderImage(KeyHandler.imagecache[imageAddress]);
}
function renderImage(image) {
context.save();
context.translate(centerX, centerY);
context.rotate(appearence.image.rotation * Math.PI / 180);
context.drawImage(
image,
imageSize / 2 - imageSize,
imageSize / 2 - imageSize,
imageSize,
imageSize
);
context.restore();
}
}
}
if (appearence.system != undefined) {
if (appearence.system.border != undefined) {
var relativeThickness = appearence.system.border.thickness / 100 * renderQuality;
context.fillStyle = appearence.system.border.color;
context.fillRect(0, 0, renderQuality, relativeThickness);
context.rect(0, renderQuality - relativeThickness, renderQuality, relativeThickness);
context.rect(0, 0, relativeThickness, renderQuality);
context.rect(renderQuality - relativeThickness, 0, relativeThickness, renderQuality);
context.fill();
}
if (appearence.system.ghost == true) {
var size = 50 / 100 * renderQuality;
context.save();
context.globalAlpha = 0.7;
context.translate(renderQuality / 2, renderQuality / 2);
context.drawImage(KeyHandler.ghostImage, size / 2 - size, size / 2 - size, size, size);
context.restore();
}
}
}
},
select(x: string, y: string, multi: boolean = false) {
if (PageHandler.currentPage != null) {
if (PageHandler.currentPage.keys[y] != undefined && PageHandler.currentPage.keys[y][x] != undefined) {
// var keyConfig = PageHandler.currentPage.keys[y][x];
var query = `${x},${y}`;
if (multi) {
if (!KeyHandler.selected.includes(query)) KeyHandler.selected.push(query);
} else
KeyHandler.selected = [
query
];
document.querySelectorAll('.key').forEach((key: HTMLDivElement) => {
var checkX = key.getAttribute('x');
var checkY = key.getAttribute('y');
var checkQuery = `${checkX},${checkY}`;
if (KeyHandler.selected.includes(checkQuery)) key.classList.add('selected');
else key.classList.remove('selected');
});
if (KeyHandler.selected.length > 1) Editor.close();
else {
socket.emit('page', 'getkey', PageHandler.currentPageID, x, y, (key: Page_Key) => {
Editor.open(x, y, key);
});
}
}
}
}
};
KeyHandler.init();
var altDown = false;
window.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key == 'alt' || e.altKey == true) altDown = true;
});
window.addEventListener('keyup', (e: KeyboardEvent) => {
if (e.key == 'alt' || e.altKey == false) altDown = false;
});
interface KeyHandler {
elements: {
keys: {
[y: string]: {
[x: string]: {
key: HTMLDivElement;
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
};
};
};
};
selected: string[];
imagecache: { [iconID: string]: HTMLImageElement };
ghostImage: any;
init: () => void;
clear: () => void;
render: (x: string, y: string, data: Page_Key) => void;
select: (x: string, y: string, multi?: boolean) => void;
}
interface Page_Key {
id: string;
state: {
type: KeyTypes;
toggle: boolean;
confirm: boolean;
};
actions: Page_Key_ActionTypes;
appearence: Page_Key_Appearence;
}
interface Page_Key_Appearence {
text?: Page_Key_Text;
image?: Page_Key_Image;
background?: Page_Key_Background;
system?: {
border?: {
color: string;
thickness: number;
};
ghost?: boolean;
};
}
interface Page_Key_Text {
value: string;
size: number;
color: string;
offsetX: number;
offsetY: number;
}
interface Page_Key_Image {
address?: string;
iconid?: string;
iconstyle?: 'black' | 'white';
size: number;
offsetX: number;
offsetY: number;
rotation: number;
}
interface Page_Key_Background {
color: string;
}
type KeyTypes = 'empty' | 'custom' | 'pageup' | 'pagedown' | 'currentpage' | 'ghost';

View File

@@ -0,0 +1,17 @@
function isMac() {
return navigator.appVersion.indexOf('Mac') != -1;
}
function isWin() {
return navigator.appVersion.indexOf('Win') != -1;
}
function isLinux() {
return navigator.appVersion.indexOf('Linux') != -1;
}
function isUnix() {
return navigator.appVersion.indexOf('X11') != -1;
}
document.querySelectorAll('.shortcutKey').forEach((command: HTMLDivElement) => {
if (isMac()) command.innerText = 'Command';
else command.innerText = 'Ctrl';
});

View File

@@ -0,0 +1,17 @@
function scaleOverview() {
var overview: HTMLDivElement = document.querySelector('.overview');
var deck: HTMLDivElement = overview.querySelector('.deck');
if (overview.clientWidth > 0) {
var margin = 10;
var widthScale = overview.clientWidth / (deck.clientWidth + margin * 2);
var heightScale = overview.clientHeight / (deck.clientHeight + margin * 2);
if (deck.clientHeight * widthScale > overview.clientHeight) deck.style.transform = `scale(${heightScale})`;
else deck.style.transform = `scale(${widthScale})`;
} else setTimeout(scaleOverview, 100);
}
window.addEventListener('resize', scaleOverview);
scaleOverview();

View File

@@ -0,0 +1,141 @@
var PageHandler: PageHandler = {
elements: {
pagename: document.querySelector('.pagename'),
left: document.querySelector('.pageselector').querySelector('.box.left'),
right: document.querySelector('.pageselector').querySelector('.box.right'),
add: document.querySelector('.pageselector').querySelector('.box.add')
},
currentPageID: null,
currentPage: null,
currentIndex: null,
request(pageID: string, index: number) {
socket.emit('page', 'request', pageID, (err?: string, page?: Page_Config) => {
if (err) UndeckedNotification(`Error whilst getting page ${pageID}: ${err}`, 'error', 5000);
else {
PageHandler.currentIndex = index;
PageHandler.render(page);
}
});
},
render(page: Page_Config) {
KeyHandler.clear();
PageHandler.currentPageID = page.pageID;
PageHandler.currentPage = page;
PageList.select(page.pageID);
PageHandler.elements.pagename.value = page.name;
PageHandler.elements.pagename.removeAttribute('disabled');
for (var y in page.keys) {
for (var x in page.keys[y]) {
KeyHandler.render(x, y, page.keys[y][x]);
}
}
},
listeners() {
PageHandler.elements.pagename.oninput = () => {
if (PageHandler.currentPageID != null && PageHandler.elements.pagename.value.length > 0) {
socket.emit('page', 'setname', PageHandler.currentPageID, PageHandler.elements.pagename.value);
}
};
PageHandler.elements.add.onclick = () => {
var pageName = prompt('New page name', 'Untitled page');
if (pageName && pageName.length > 0) {
socket.emit('page', 'create', pageName);
}
};
PageHandler.elements.left.onclick = () => {
var selected = document.querySelector('.pageitem.selected');
if (selected && selected.previousElementSibling) (<HTMLElement>selected.previousElementSibling).click();
};
PageHandler.elements.right.onclick = () => {
var selected = document.querySelector('.pageitem.selected');
if (selected && selected.nextElementSibling) (<HTMLElement>selected.nextElementSibling).click();
};
}
};
PageHandler.listeners();
interface PageHandler {
elements: {
pagename: HTMLInputElement;
left: HTMLDivElement;
right: HTMLDivElement;
add: HTMLDivElement;
};
currentPageID: string;
currentPage: Page_Config;
currentIndex: number;
request: (pageID: string, index: number) => void;
render: (page: Page_Config) => void;
listeners: () => void;
}
interface Page_Config {
pageID: string;
name?: string;
keys?: Page_Config_Keys;
}
interface Page_Config_Keys {
'0'?: {
'0'?: Page_Key;
'1'?: Page_Key;
'2'?: Page_Key;
'3'?: Page_Key;
'4'?: Page_Key;
'5'?: Page_Key;
'6'?: Page_Key;
'7'?: Page_Key;
'8'?: Page_Key;
};
'1'?: {
'0'?: Page_Key;
'1'?: Page_Key;
'2'?: Page_Key;
'3'?: Page_Key;
'4'?: Page_Key;
'5'?: Page_Key;
'6'?: Page_Key;
'7'?: Page_Key;
'8'?: Page_Key;
};
'2'?: {
'0'?: Page_Key;
'1'?: Page_Key;
'2'?: Page_Key;
'3'?: Page_Key;
'4'?: Page_Key;
'5'?: Page_Key;
'6'?: Page_Key;
'7'?: Page_Key;
'8'?: Page_Key;
};
'3'?: {
'0'?: Page_Key;
'1'?: Page_Key;
'2'?: Page_Key;
'3'?: Page_Key;
'4'?: Page_Key;
'5'?: Page_Key;
'6'?: Page_Key;
'7'?: Page_Key;
'8'?: Page_Key;
};
}
type Icon = '';

View File

@@ -0,0 +1,115 @@
var PageList: PageList = {
elements: {
container: document.querySelector('.pageselector')
},
order: null,
firstRender: true,
render(pagelist: PageListItem[]) {
var list = ce('div', 'list');
PageList.order = pagelist.map((page) => {
return page.pageID;
});
pagelist.forEach((pagelistitem, index) => {
var pageitem = ce('div', 'pageitem', { pageID: pagelistitem.pageID });
var name = ce('div', 'name', null, pagelistitem.name);
pageitem.appendChild(name);
var move = ce('div', 'move');
var up = ce('div', [
'moveitem',
'up'
]);
up.appendChild(ce('img', 'normal', { src: '/stc/icon/up.png' }));
up.appendChild(ce('img', 'selected', { src: '/stc/icon/up_gray.png' }));
var down = ce('div', [
'moveitem',
'down'
]);
down.appendChild(ce('img', 'normal', { src: '/stc/icon/down.png' }));
down.appendChild(ce('img', 'selected', { src: '/stc/icon/down_gray.png' }));
up.onclick = (e: MouseEvent) => {
e.stopPropagation();
var current = PageList.order.indexOf(pagelistitem.pageID);
var newIndex = Math.max(current - 1, 0);
PageList.order.splice(current, 1);
PageList.order.splice(newIndex, 0, pagelistitem.pageID);
socket.emit('page', 'setorder', PageList.order);
};
down.onclick = (e: MouseEvent) => {
e.stopPropagation();
var current = PageList.order.indexOf(pagelistitem.pageID);
var newIndex = Math.max(current + 1, 0);
PageList.order.splice(current, 1);
PageList.order.splice(newIndex, 0, pagelistitem.pageID);
socket.emit('page', 'setorder', PageList.order);
};
move.appendChild(up);
move.appendChild(down);
pageitem.appendChild(move);
pageitem.onclick = () => {
PageHandler.request(pagelistitem.pageID, index);
};
list.appendChild(pageitem);
if (PageHandler.currentPageID == pagelistitem.pageID) {
PageHandler.request(pagelistitem.pageID, index);
}
});
var existing = PageList.elements.container.querySelector('.list');
if (existing) existing.parentElement.removeChild(existing);
PageList.elements.container.appendChild(list);
},
select(pageID: string): boolean {
var selectedFound = false;
PageList.elements.container.querySelectorAll('.pageitem').forEach((item: HTMLDivElement) => {
var itemPageID = item.getAttribute('pageID');
if (itemPageID == pageID) {
item.classList.add('selected');
selectedFound = true;
} else item.classList.remove('selected');
});
if (selectedFound) {
history.pushState(null, null, `/pages/${pageID}`);
TabController.setTitle(`Page ${pageID}`);
}
return selectedFound;
},
updateName(pageID: string, name: string) {
var item = PageList.elements.container.querySelector(`.pageitem[pageid="${pageID}"]`);
if (item) {
var nameElement: HTMLDivElement = item.querySelector('.name');
nameElement.innerText = name;
}
}
};
interface PageList {
elements: {
container: HTMLDivElement;
};
firstRender: boolean;
order: string[];
render: (pagelist: PageListItem[]) => void;
select: (pageID: string) => boolean;
updateName: (pageID: string, name: string) => void;
}
interface PageListItem {
pageID: string;
name: string;
}

View File

@@ -0,0 +1,95 @@
var TabController: TabController = {
elements: {
menu: document.querySelector('.tabmenu'),
pages: document.querySelector('.tabpages')
},
setTitle(title: string) {
var titleElement = document.querySelector('title');
titleElement.innerText = `Undecked - ${title}`;
},
show(tabname: string) {
TabController.elements.menu.querySelectorAll('.item').forEach((element: HTMLDivElement) => {
var tab = element.getAttribute('tab');
if (tab == tabname) element.classList.add('active');
else element.classList.remove('active');
});
TabController.elements.pages.querySelectorAll('.item').forEach((element: HTMLDivElement) => {
var tab = element.getAttribute('tab');
if (tab == tabname) element.classList.add('active');
else element.classList.remove('active');
});
if (tabname == 'pages') {
var pageID = PageHandler.currentPageID;
history.pushState(null, null, `/pages/pageID`);
TabController.setTitle(`Page ${pageID}`);
} else {
history.pushState(null, null, `/${tabname}`);
TabController.setTitle(tabname.charAt(0).toUpperCase() + tabname.slice(1));
}
},
registerListeners() {
TabController.elements.menu.querySelectorAll('.item').forEach((element: HTMLDivElement) => {
var tab = element.getAttribute('tab');
element.onclick = () => {
TabController.show(tab);
};
});
},
init() {
var args = window.location.pathname.split('/');
if (args.length > 0) args.splice(0, 1);
var tab = args.length > 0 ? args[0] : null;
var subTab = args.length > 1 ? args[1] : null;
if (PageList.order != null) {
if (tab != undefined && tab.length > 0) {
if (tab == 'pages') {
var valid = PageList.select(subTab);
if (valid) {
var index = PageList.order.indexOf(subTab);
PageHandler.request(PageList.order[index], index);
TabController.show('pages');
} else {
PageHandler.request(PageList.order[0], 0);
TabController.show('pages');
}
} else {
PageHandler.request(PageList.order[0], 0);
TabController.show(tab);
}
} else {
PageList.firstRender = false;
PageHandler.request(PageList.order[0], 0);
TabController.show('pages');
}
} else
setTimeout(() => {
TabController.init();
}, 200);
}
};
TabController.init();
TabController.registerListeners();
interface TabController {
elements: {
menu: HTMLDivElement;
pages: HTMLDivElement;
};
setTitle: (title: string) => void;
show: (tabname: string) => void;
registerListeners: () => void;
init: () => void;
}

View File

@@ -0,0 +1,7 @@
{
"compilerOptions": {
"watch": true,
"inlineSourceMap": true,
"noImplicitUseStrict": true
}
}

View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="/ld/main/style">
<script src="/ld/main/script"></script>
</head>
<body>
<div class="headercontainer">
<img src="/stc/logo/512.png">
<div class="title">
<div class="main">Undecked</div>
<a class="sub" target="_blank" href="http://morphix.productions">by Morphix</a>
</div>
</div>
<div class="bodycontainer">
{{{body}}}
</div>
<div class="feedback">
<div class="notificationcontainer">
<div class="notification">
Key has been saved to the clipboard.
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,8 @@
a {
color: var(--main-color);
text-decoration: none;
&:hover {
color: var(--main-hover-color);
}
}

View File

@@ -0,0 +1,21 @@
.button {
padding: 4px 15px;
background: var(--main-color);
border-radius: var(--border-radius);
text-decoration: none;
transition-duration: .2s;
cursor: pointer;
font-weight: 500;
&.secondary {
background: #565656;
&:hover {
background: #303030;
}
}
&:hover {
background: var(--main-hover-color);
}
}

View File

@@ -0,0 +1,38 @@
.feedback {
position: absolute;
inset: 0px;
z-index: 2;
overflow: hidden;
pointer-events: none;
.notificationcontainer {
position: absolute;
bottom: -50px;
left: 20px;
right: 20px;
display: flex;
justify-content: center;
align-items: flex-end;
.notification {
padding: 10px 100px;
border-radius: var(--border-radius);
font-size: 14px;
font-weight: 600;
pointer-events: auto;
cursor: pointer;
&.info {
background: var(--main-hover-color);
box-shadow: 0px 0px 4px #243d5e;
}
&.error {
background: var(--color-red-hover);
box-shadow: 0px 0px 4px #431616;
}
}
}
}

View File

@@ -0,0 +1,37 @@
input,
select,
textarea {
border: 2px solid var(--main-hover-color);
transition-duration: .2s;
border-radius: var(--border-radius);
padding: 5px 10px;
background: var(--background);
color: white;
font-family: 'Montserrat', sans-serif;
font-size: 12px;
&:hover {
border: 2px solid var(--main-color);
}
&:focus,
&:active {
border: 2px solid var(--main-color);
outline: none;
}
}
input[type="color"] {
overflow: hidden;
&::-webkit-color-swatch-wrapper {
padding: 0px;
width: calc(100% + 22px);
height: calc(100% + 12px);
margin-top: -6px;
margin-left: -11px;
outline: none;
border: none;
}
}

View File

@@ -0,0 +1,67 @@
:root {
--background: #323232;
--main-color: #4676b7;
--main-hover-color: #305383;
--main-secondary-color: #253e5e;
--border-radius: 10px;
--small-border-radius: 10px;
--panel-color: #222;
--subpanel-color: #2c2c2c;
--color-red: #af2a20;
--color-red-hover: #76201a;
--color-green: #195e19;
--color-green-hover: #124412;
}
body {
background: var(--background);
color: white;
padding: 0px;
margin: 0px;
font-family: 'Montserrat', sans-serif;
}
.headercontainer {
position: absolute;
top: 10px;
left: 10px;
right: 10px;
height: 40px;
display: flex;
justify-content: flex-start;
align-items: center;
img {
height: 100%;
}
.title {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-left: 10px;
.main {
font-size: 24px;
font-weight: 300;
}
.sub {
font-size: 12px;
font-weight: 300;
color: lightgrey;
}
}
}
.bodycontainer {
position: absolute;
top: 60px;
bottom: 10px;
left: 10px;
right: 10px;
}

View File

@@ -0,0 +1 @@
function ce(e,n,t,i,o,s){var c=document.createElement(e);if(n&&("string"==typeof n?c.classList.add(n):(e=c.classList).add.apply(e,n)),t)for(var r in t)c.setAttribute(r,t[r]);if(i&&(c.innerText=i),o&&(c.innerHTML=o),s)for(var r in s)c.style[r]=s[r];return c}var notificationTimeout,UndeckedNotification,notificationOpen=!1;window.addEventListener("DOMContentLoaded",function(){var o={isOpen:!(UndeckedNotification=function(e,n,t){o.open(e,n=void 0===n?"info":n,t=void 0===t?2e3:t)}),timeout:null,transitionDuration:200,elements:{container:document.querySelector(".feedback").querySelector(".notificationcontainer"),box:document.querySelector(".feedback").querySelector(".notification")},open:function(e,n,t){void 0===n&&(n="info");function i(){o.elements.box.classList.remove("info","error"),o.elements.box.classList.add(n),o.elements.box.innerText=e,o.elements.container.style.transitionTimingFunction="ease-out",o.elements.container.style.transitionDuration="".concat(o.transitionDuration,"ms"),o.elements.container.style.bottom="50px",o.isOpen=!0,o.timeout=setTimeout(o.close,t),o.elements.box.onclick=function(){return o.close()}}1==o.isOpen?o.close(i):i()},close:function(e){clearTimeout(o.timeout),o.elements.container.style.transitionTimingFunction="ease-in",o.elements.container.style.bottom="-50px",setTimeout(function(){e&&e(),o.isOpen=!1},o.transitionDuration+100)}}});

View File

@@ -0,0 +1,5 @@
a{color:var(--main-color);text-decoration:none}a:hover{color:var(--main-hover-color)}
.button{padding:4px 15px;background:var(--main-color);border-radius:var(--border-radius);text-decoration:none;transition-duration:.2s;cursor:pointer;font-weight:500}.button.secondary{background:#565656}.button.secondary:hover{background:#303030}.button:hover{background:var(--main-hover-color)}
.feedback{position:absolute;inset:0;z-index:2;overflow:hidden;pointer-events:none}.feedback .notificationcontainer{position:absolute;bottom:-50px;left:20px;right:20px;display:flex;justify-content:center;align-items:flex-end}.feedback .notificationcontainer .notification{padding:10px 100px;border-radius:var(--border-radius);font-size:14px;font-weight:600;pointer-events:auto;cursor:pointer}.feedback .notificationcontainer .notification.info{background:var(--main-hover-color);box-shadow:0 0 4px #243d5e}.feedback .notificationcontainer .notification.error{background:var(--color-red-hover);box-shadow:0 0 4px #431616}
input,select,textarea{border:2px solid var(--main-hover-color);transition-duration:.2s;border-radius:var(--border-radius);padding:5px 10px;background:var(--background);color:#fff;font-family:Montserrat,sans-serif;font-size:12px}input:hover,select:hover,textarea:hover{border:2px solid var(--main-color)}input:active,input:focus,select:active,select:focus,textarea:active,textarea:focus{border:2px solid var(--main-color);outline:0}input[type=color]{overflow:hidden}input[type=color]::-webkit-color-swatch-wrapper{padding:0;width:calc(100% + 22px);height:calc(100% + 12px);margin-top:-6px;margin-left:-11px;outline:0;border:none}
:root{--background:#323232;--main-color:#4676b7;--main-hover-color:#305383;--main-secondary-color:#253e5e;--border-radius:10px;--small-border-radius:10px;--panel-color:#222;--subpanel-color:#2c2c2c;--color-red:#af2a20;--color-red-hover:#76201a;--color-green:#195e19;--color-green-hover:#124412}body{background:var(--background);color:#fff;padding:0;margin:0;font-family:Montserrat,sans-serif}.headercontainer{position:absolute;top:10px;left:10px;right:10px;height:40px;display:flex;justify-content:flex-start;align-items:center}.headercontainer img{height:100%}.headercontainer .title{display:flex;flex-direction:column;align-items:flex-start;margin-left:10px}.headercontainer .title .main{font-size:24px;font-weight:300}.headercontainer .title .sub{font-size:12px;font-weight:300;color:#d3d3d3}.bodycontainer{position:absolute;top:60px;bottom:10px;left:10px;right:10px}

View File

@@ -0,0 +1,20 @@
function ce(
type: string,
classList?: string | string[],
attributes?: { [key: string]: string },
innerText?: string,
innerHTML?: string,
styling?: { [key: string]: string }
) {
var element = document.createElement(type);
if (classList)
if (typeof classList == 'string') element.classList.add(classList);
else element.classList.add(...classList);
if (attributes) for (var key in attributes) element.setAttribute(key, attributes[key]);
if (innerText) element.innerText = innerText;
if (innerHTML) element.innerHTML = innerHTML;
if (styling) for (var key in styling) element.style[key] = styling[key];
return element;
}
// declare var ce:(type:string, classList?:string|string[], attributes?:{[key:string]:string}, innerText?:string, innerHTML?:string) => HTMLDivElement;

View File

@@ -0,0 +1,54 @@
var notificationOpen = false;
var notificationTimeout: NodeJS.Timeout;
var UndeckedNotification: (message: string, type?: 'info' | 'error', time?: number) => void;
window.addEventListener('DOMContentLoaded', () => {
UndeckedNotification = (message: string, type: 'info' | 'error' = 'info', time: number = 2000) => {
_UndeckedNotification.open(message, type, time);
};
var _UndeckedNotification = {
isOpen: false,
timeout: null as NodeJS.Timeout,
transitionDuration: 200,
elements: {
container: document.querySelector('.feedback').querySelector('.notificationcontainer') as HTMLDivElement,
box: document.querySelector('.feedback').querySelector('.notification') as HTMLDivElement
},
open: (message: string, type: 'info' | 'error' = 'info', time: number) => {
var open = () => {
_UndeckedNotification.elements.box.classList.remove('info', 'error');
_UndeckedNotification.elements.box.classList.add(type);
_UndeckedNotification.elements.box.innerText = message;
_UndeckedNotification.elements.container.style.transitionTimingFunction = 'ease-out';
_UndeckedNotification.elements.container.style.transitionDuration = `${_UndeckedNotification.transitionDuration}ms`;
_UndeckedNotification.elements.container.style.bottom = '50px';
_UndeckedNotification.isOpen = true;
_UndeckedNotification.timeout = setTimeout(_UndeckedNotification.close, time);
_UndeckedNotification.elements.box.onclick = () => _UndeckedNotification.close();
};
if (_UndeckedNotification.isOpen == true) {
_UndeckedNotification.close(open);
} else open();
},
close: (callback?: Function) => {
clearTimeout(_UndeckedNotification.timeout);
_UndeckedNotification.elements.container.style.transitionTimingFunction = 'ease-in';
_UndeckedNotification.elements.container.style.bottom = '-50px';
setTimeout(() => {
if (callback) callback();
_UndeckedNotification.isOpen = false;
}, _UndeckedNotification.transitionDuration + 100);
}
};
});
// declare var UndeckedNotification: (message:string, type?:'info'|"error", time?:number) => void;

View File

@@ -0,0 +1,6 @@
{
"compilerOptions": {
"inlineSourceMap": true,
"noImplicitUseStrict": true
}
}

265
Frontend/pages/settings/package-lock.json generated Normal file
View File

@@ -0,0 +1,265 @@
{
"name": "settings",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "settings",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"socket.io-client": "^4.4.1"
}
},
"node_modules/@socket.io/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz",
"integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q=="
},
"node_modules/backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io-client": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz",
"integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==",
"dependencies": {
"@socket.io/component-emitter": "~3.0.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.0.0",
"has-cors": "1.1.0",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"ws": "~8.2.3",
"xmlhttprequest-ssl": "~2.0.0",
"yeast": "0.1.2"
}
},
"node_modules/engine.io-parser": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz",
"integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==",
"dependencies": {
"@socket.io/base64-arraybuffer": "~1.0.2"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/parseqs": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
},
"node_modules/parseuri": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
},
"node_modules/socket.io-client": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz",
"integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==",
"dependencies": {
"@socket.io/component-emitter": "~3.0.0",
"backo2": "~1.0.2",
"debug": "~4.3.2",
"engine.io-client": "~6.1.1",
"parseuri": "0.0.6",
"socket.io-parser": "~4.1.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz",
"integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==",
"dependencies": {
"@socket.io/component-emitter": "~3.0.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/ws": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
}
},
"dependencies": {
"@socket.io/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ=="
},
"@socket.io/component-emitter": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz",
"integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q=="
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"engine.io-client": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz",
"integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==",
"requires": {
"@socket.io/component-emitter": "~3.0.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.0.0",
"has-cors": "1.1.0",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"ws": "~8.2.3",
"xmlhttprequest-ssl": "~2.0.0",
"yeast": "0.1.2"
}
},
"engine.io-parser": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz",
"integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==",
"requires": {
"@socket.io/base64-arraybuffer": "~1.0.2"
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"parseqs": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
},
"parseuri": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
},
"socket.io-client": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz",
"integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==",
"requires": {
"@socket.io/component-emitter": "~3.0.0",
"backo2": "~1.0.2",
"debug": "~4.3.2",
"engine.io-client": "~6.1.1",
"parseuri": "0.0.6",
"socket.io-parser": "~4.1.1"
}
},
"socket.io-parser": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz",
"integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==",
"requires": {
"@socket.io/component-emitter": "~3.0.0",
"debug": "~4.3.1"
}
},
"ws": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
"requires": {}
},
"xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="
},
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
}
}
}

View File

@@ -0,0 +1,14 @@
{
"name": "settings",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"socket.io-client": "^4.4.1"
}
}