initial commit

This commit is contained in:
Daniel Sommer 2022-02-12 03:54:17 +01:00
commit e089856edb
19 changed files with 767 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
node_modules
dist
yarn-error.log
yarn.lock

21
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,21 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "chromium: tabber",
"url": "http://localhost:9000",
"webRoot": "${workspaceFolder}/src",
"breakOnLoad": true,
"sourceMapPathOverrides": {
"webpack:///src/*": "${webRoot}/*"
},
"runtimeArgs": [
"--auto-open-devtools-for-tabs"
],
"preLaunchTask": "serve",
"postDebugTask": "terminate all tasks"
}
]
}

45
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,45 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "run backend",
"type": "shell",
"command": "node",
"args": [
"../pbc/pbc.js"
]
},
{
"label": "build",
"type": "shell",
"command": "yarn",
"args": [
"build"
]
},
{
"label": "serve",
"type": "shell",
"command": "yarn",
"args": [
"run",
"serve"
],
"isBackground": true
},
{
"label": "terminate all tasks",
"command": "echo ${input:terminate}",
"type": "shell",
"problemMatcher": []
}
],
"inputs": [
{
"id": "terminate",
"type": "command",
"command": "workbench.action.tasks.terminate",
"args": "terminateAll"
}
]
}

20
README.md Normal file
View file

@ -0,0 +1,20 @@
# pbc
pedal board control
## yarn
### setup
```
yarn install
```
### compile and serve with hot-reloads for development
```
yarn run serve
```
### compile, minify and build for production
```
yarn run build
```

27
package.json Normal file
View file

@ -0,0 +1,27 @@
{
"name": "pbc",
"author": "Daniel Sommer <daniel.sommer@velvettear.de>",
"description": "pedal board control",
"license": "MIT",
"version": "0.0.1",
"private": true,
"repository": {
"type": "git",
"url": "https://git.velvettear.de/velvettear/pbc.git"
},
"scripts": {
"serve": "vue-cli-service serve --host 0.0.0.0 --port 9000",
"build": "vue-cli-service build"
},
"dependencies": {
"purecss": "^2.0.6",
"remixicon": "^2.5.0",
"vue": "^2.6.14",
"vue-resource": "^1.5.3",
"vue-router": "^3.5.3"
},
"devDependencies": {
"@vue/cli-service": "^3.12.1",
"vue-template-compiler": "^2.6.11"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

27
public/index.html Normal file
View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="">
<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="icon" href="<%= BASE_URL %>favicon.ico">
<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=Fira+Code:wght@300;400;500;600;700&display=swap"
rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
</html>

57
src/App.vue Normal file
View file

@ -0,0 +1,57 @@
<template>
<div id="page">
<Menu />
<div id="content" class="pure-g"></div>
</div>
</template>
<script>
import Menu from "@/components/Menu.vue";
import "@/../node_modules/remixicon/fonts/remixicon.css";
export default {
name: "App",
components: {
Menu,
},
data() {
return {
};
},
};
</script>
<style>
html {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}
body {
font-family: "Fira Code", monospace;
font-weight: 300;
font-size: 1.5em;
text-align: center;
color: #f1f1f1;
margin: 0;
background: #000000;
}
a {
text-decoration: none;
color: #ff8800;
}
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-track {
background: #3e3e3e99;
}
::-webkit-scrollbar-thumb {
background: #ff880099;
}
::-webkit-scrollbar-thumb:hover {
background: #ffffff99;
}
</style>

View file

@ -0,0 +1,39 @@
<template>
<div class="control">
<div>
{{ control.name }}
</div>
</div>
</template>
<script>
export default {
name: "Control",
props: {
control: Object,
},
data() {
return {};
},
};
</script>
<style scoped>
.pedal {
font-family: "Fira Code", monospace;
letter-spacing: 0;
}
.content {
margin: 2.5%;
padding: 8px;
border-width: 2px;
border-style: solid;
border-radius: 2px;
font-size: 1em;
font-weight: 300;
}
.header {
font-size: 1.5em;
font-weight: 900;
}
</style>

111
src/components/HostInfo.vue Normal file
View file

@ -0,0 +1,111 @@
<template>
<div class="menu-hostinfo">
<table v-if="config.enabled && data">
<tr v-if="config.hostname && config.hostname.enabled && data.hostname">
<td class="fat">{{ config.hostname.title || "host:" }}</td>
<td>{{ data.hostname }}</td>
</tr>
<tr v-if="config.os && config.os.enabled && data.os">
<td class="fat">{{ config.os.title || "os:" }}</td>
<td>{{ data.os }}</td>
</tr>
<tr v-if="config.kernel && config.kernel.enabled && data.kernel">
<td class="fat">{{ config.kernel.title || "kernel:" }}</td>
<td>{{ data.kernel }}</td>
</tr>
<tr v-if="config.uptime && config.uptime.enabled && data.uptime">
<td class="fat">{{ config.uptime.title || "uptime:" }}</td>
<td>{{ data.uptime }}</td>
</tr>
<tr v-if="config.cpu && config.cpu.enabled && data.cpu">
<td class="fat">{{ config.cpu.title || "cpu:" }}</td>
<td>{{ data.cpu }}</td>
</tr>
<tr v-if="config.load && config.load.enabled && data.load">
<td class="fat">{{ config.load.title || "load:" }}</td>
<td>
{{ data.load }}
</td>
</tr>
<tr
v-if="
config.memory &&
config.memory.enabled &&
data.memory.used &&
data.memory.total
"
>
<td class="fat">{{ config.memory.title || "memory:" }}</td>
<td>
{{ data.memory.used }} /
{{ data.memory.total }}
</td>
</tr>
<tr
v-if="
config.temperature && config.temperature.enabled && data.temperature
"
>
<td class="fat">{{ config.temperature.title || "temperature:" }}</td>
<td>
{{ data.temperature }}
</td>
</tr>
</table>
</div>
</template>
<script>
import { get } from "@/libs/requests.js";
import { formatInfo } from "@/libs/util.js";
export default {
name: "HostInfo",
data() {
return {
config: require("@/config.json").hostinfo,
data: undefined,
};
},
methods: {
fetchData() {
if (!this.config.enabled) {
return;
}
get("/info")
.then((result) => {
this.data = formatInfo(result);
})
.then(() => {
setTimeout(this.fetchData, this.config.refresh);
});
},
},
mounted() {
this.fetchData();
},
};
</script>
<style scoped>
.menu-hostinfo {
display: flex;
flex-direction: column;
font-size: 0.5em;
margin-top: 32px;
margin-left: 8px;
margin-right: 8px;
width: 100%;
}
.menu-hostinfo i {
font-size: 2em;
transition: transform 0.5s ease-in-out;
cursor: pointer;
}
.fat {
font-weight: 600;
}
td {
text-align: left;
}
</style>

98
src/components/Menu.vue Normal file
View file

@ -0,0 +1,98 @@
<template>
<div id="menu" :class="{ colored: menu.expanded }">
<div id="menu-button">
<i
class="ri-menu-line"
:class="{ rotated: menu.expanded }"
v-on:click="toggleMenu()"
></i>
</div>
<div class="menu-content" :class="{ expanded: menu.expanded }">
<HostInfo />
<ul>
<li>entry1</li>
<li>entry2</li>
</ul>
</div>
</div>
</template>
<script>
import HostInfo from "@/components/HostInfo.vue";
import Config from "@/config.json";
export default {
name: "Menu",
components: {
HostInfo,
},
data() {
return {
menu: {
expanded: false,
},
hostinfo: Config.hostinfo.enabled
};
},
methods: {
toggleMenu() {
this.menu.expanded = !this.menu.expanded;
}
}
};
</script>
<style scoped>
#menu {
position: absolute;
display: flex;
flex-direction: column;
height: 100%;
min-width: 10%;
margin: 0;
font-size: 1em;
font-weight: 300;
transition: background-color 0.5s ease-in-out;
}
#menu-button {
display: flex;
justify-content: center;
margin-top: 8px;
margin-bottom: 8px;
}
#menu-button i {
font-size: 2em;
font-weight: 900;
transition: transform 0.5s ease-in-out;
cursor: pointer;
}
.menu-content {
display: flex;
flex-direction: column;
padding: 8px;
transition: transform 0.5s ease-in-out, opacity 0.5s ease-in-out;
transform: translateX(-100%);
opacity: 0;
}
.rotated {
transform: rotate(-90deg);
}
.expanded {
transform: translateX(0);
opacity: 1;
}
.colored {
background-color: #ff990099;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
font-size: 1em;
font-weight: 300;
padding-top: 8px;
padding-bottom: 8px;
}
</style>

59
src/components/Pedal.vue Normal file
View file

@ -0,0 +1,59 @@
<template>
<div class="pedal">
<div
class="content"
v-bind:style="{
background: pedal.color + '66',
borderColor: pedal.color,
}"
>
<div class="header">
{{ pedal.name }}
</div>
<Pedal
class="pure-u-1-2"
:control="control"
v-for="control in pedal.controls"
v-bind:key="control.name"
/>
</div>
</div>
</template>
<script>
import Control from "@/components/Control.vue";
export default {
name: "Pedal",
components: {
Control,
},
props: {
pedal: Object,
},
data() {
return {
};
}
};
</script>
<style scoped>
.pedal {
font-family: "Fira Code", monospace;
letter-spacing: 0;
}
.header {
font-size: 1.5em;
font-weight: 900;
}
.content {
margin: 2.5%;
padding: 8px;
border-width: 2px;
border-style: solid;
border-radius: 2px;
font-size: 1em;
font-weight: 300;
}
</style>

45
src/config.json Normal file
View file

@ -0,0 +1,45 @@
{
"api": {
"protocol": "http",
"host": "localhost",
"port": 3000
},
"hostinfo": {
"enabled": true,
"refresh": 3000,
"hostname": {
"title": "host:",
"enabled": true
},
"os": {
"title": "os:",
"enabled": true
},
"kernel": {
"title": "kernel:",
"enabled": false
},
"uptime": {
"title": "uptime:",
"enabled": true,
"format": "[h] hours [m] minutes"
},
"cpu": {
"title": "cpu:",
"enabled": true,
"format": "[count]x [model] @ [speed]mhz"
},
"load": {
"title": "load:",
"enabled": true
},
"memory": {
"title": "memory:",
"enabled": true
},
"temperature": {
"title": "temperature:",
"enabled": true
}
}
}

20
src/libs/requests.js Normal file
View file

@ -0,0 +1,20 @@
const config = require('../config.json');
const BASE_URL = config.api.protocol + '://' + config.api.host + ':' + config.api.port;
function get(url) {
return new Promise(function (resolve, reject) {
if (url == undefined) {
return reject('error: no url defined');
}
url = BASE_URL + url;
fetch(url)
.then(response => response.json())
.then(result => resolve(result))
.catch(reject);
});
}
module.exports = {
get
}

76
src/libs/util.js Normal file
View file

@ -0,0 +1,76 @@
const uptimeFormat = require('@/config.json').hostinfo.uptime.format;
const cpuFormat = require('@/config.json').hostinfo.cpu.format;
const formatBytes = (bytes, decimals = 2) => {
if (bytes === 0) {
return '0 Bytes';
}
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['byte', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
const formatSeconds = (seconds) => {
var secs = seconds % 60;
seconds = (seconds - secs) / 60;
var mins = seconds % 60;
var hrs = (seconds - mins) / 60;
return uptimeFormat.replace('\[h\]', fill(hrs, 2)).replace('\[m\]', fill(mins, 2)).replace('\[s\]', fill(Math.round(secs), 2));
}
const fill = (value, count, fill, front) => {
if (value == undefined || count == undefined) {
return;
}
if (fill == undefined) {
fill = '0';
}
if (front == undefined) {
front = true;
}
while (value.toString().length < count) {
if (front) {
value = fill + value;
} else {
value += fill;
}
}
return value;
}
const formatCpu = (data) => {
var model = data[0].model;
var cut = model.toLowerCase().indexOf('cpu');
if (cut != -1) {
model = model.substring(0, cut);
}
cut = model.indexOf('@');
if (cut != -1) {
model = model.substring(0, cut);
}
model = model.trim();
var speed = 0;
for (var index = 0; index < data.length; index++) {
speed += data[index].speed;
}
if (speed < 0) {
speed = 0;
}
return cpuFormat.replace('[count]', data.length).replace('[model]', model).replace('[speed]', Math.round(speed / data.length));
}
const formatInfo = (info) => {
info.memory.total = formatBytes(info.memory.total);
info.memory.used = formatBytes(info.memory.used);
info.memory.free = formatBytes(info.memory.free);
info.uptime = formatSeconds(info.uptime);
info.load = info.load[0] + ' ' + info.load[1] + ' ' + info.load[2];
info.cpu = formatCpu(info.cpu);
return info;
}
module.exports = {
formatInfo
}

13
src/main.js Normal file
View file

@ -0,0 +1,13 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router/router.js'
import '../node_modules/purecss/build/base-min.css'
import '../node_modules/purecss/build/grids-min.css'
import '../node_modules/purecss/build/grids-responsive-min.css'
Vue.config.productionTip = false
const app = new Vue({
router,
render: h => h(App),
}).$mount('#app')

72
src/pedals.json Normal file
View file

@ -0,0 +1,72 @@
[
{
"id": 0,
"name": "tubewarmth",
"color": "#880088",
"controls": [
{
"name": "on/off",
"midi": {
"channel": 0,
"controller": 0,
"value": 0
}
},
{
"name": "drive",
"midi": {
"channel": 0,
"controller": 1,
"value": 5
}
},
{
"name": "tape/tube",
"midi": {
"channel": 0,
"controller": 2,
"value": 10
}
}
]
},
{
"id": 1,
"name": "big muff",
"color": "#339900",
"controls": [
{
"name": "on/off",
"midi": {
"channel": 0,
"controller": 4,
"value": 0
}
},
{
"name": "tone",
"midi": {
"channel": 0,
"controller": 5,
"value": 0.5
}
},
{
"name": "level",
"midi": {
"channel": 0,
"controller": 6,
"value": 0.5
}
},
{
"name": "sustain",
"midi": {
"channel": 0,
"controller": 7,
"value": 0.5
}
}
]
}
]

19
src/router/router.js Normal file
View file

@ -0,0 +1,19 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
// import List from '@/components/List.vue'
// import Tab from '@/components/Tab.vue'
Vue.use(VueRouter);
const routes = [
// { path: '/', name: 'List', component: List },
// { path: '/:id', name: 'Tab', component: Tab }
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router;

14
vue.config.js Normal file
View file

@ -0,0 +1,14 @@
module.exports = {
runtimeCompiler: true,
chainWebpack: (config) => {
config
.plugin('html')
.tap((args) => {
args[0].title = "tabber";
return args;
});
},
configureWebpack: {
devtool: 'source-map'
}
}