internal/dis/componeont/control: Move paths
This commit is contained in:
parent
52559e4d68
commit
e5ddede0c7
43 changed files with 8 additions and 8 deletions
47
internal/dis/component/control/static/assets.go
Normal file
47
internal/dis/component/control/static/assets.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package static
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
// Assets represents a group of assets to be included inside a template.
|
||||
//
|
||||
// Assets are generated using the 'build.mjs' script.
|
||||
// The script is called using 'go:generate', which stores variables in the form of 'Assets{{Name}}' inside this package.
|
||||
//
|
||||
// The build script roughly works as follows:
|
||||
// - Delete any previously generated distribution directory.
|
||||
// - Bundle the entrypoint sources under 'src/entry/{{Name}}/index.{ts,css}' together with the base './src/base/index.{ts,css}'
|
||||
// - Store the output inside the 'dist' directory
|
||||
// - Generate new constants of the form {{Name}}
|
||||
//
|
||||
// Each asset group should be registered as a parameter to the 'go:generate' line.
|
||||
type Assets struct {
|
||||
Scripts string // <script> tags inserted by the asset
|
||||
Styles string // <link> tags inserted by the asset
|
||||
}
|
||||
|
||||
//go:generate node build.mjs HomeHome ControlIndex ControlInstance
|
||||
|
||||
// MustParse parses a new template from the given source
|
||||
// and registers the Asset functions to it.
|
||||
// See [Assets.RegisterFuncs].
|
||||
func (assets *Assets) MustParse(value string) *template.Template {
|
||||
return template.Must(assets.RegisterFuncs(template.New("")).Parse(value))
|
||||
}
|
||||
|
||||
// RegisterFuncs registers three new template functions called "JS", "CSS" and "json".
|
||||
//
|
||||
// "JS" and "CSS" take no arguments, and return appropriate tags to be inserted into html.
|
||||
// json takes a single argument of any type, and returns it's encoding as a string to be inserted into the page.
|
||||
func (assets *Assets) RegisterFuncs(t *template.Template) *template.Template {
|
||||
return t.Funcs(template.FuncMap{
|
||||
"JS": func() template.HTML { return template.HTML(assets.Scripts) },
|
||||
"CSS": func() template.HTML { return template.HTML(assets.Styles) },
|
||||
"json": func(data any) (string, error) {
|
||||
bytes, err := json.Marshal(data)
|
||||
return string(bytes), err
|
||||
},
|
||||
})
|
||||
}
|
||||
21
internal/dis/component/control/static/assets_dist.go
Normal file
21
internal/dis/component/control/static/assets_dist.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package static
|
||||
|
||||
// This file was automatically generated. Do not edit.
|
||||
|
||||
// AssetsHomeHome contains assets for the 'HomeHome' entrypoint.
|
||||
var AssetsHomeHome = Assets{
|
||||
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/HomeHome.38d394c2.css">`,
|
||||
}
|
||||
|
||||
// AssetsControlIndex contains assets for the 'ControlIndex' entrypoint.
|
||||
var AssetsControlIndex = Assets{
|
||||
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlIndex.cfbf936d.js"></script><script src="/static/ControlIndex.613b02c2.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css">`,
|
||||
}
|
||||
|
||||
// AssetsControlInstance contains assets for the 'ControlInstance' entrypoint.
|
||||
var AssetsControlInstance = Assets{
|
||||
Scripts: `<script nomodule="" defer src="/static/ControlIndex.613b02c2.js"></script><script type="module" src="/static/ControlIndex.cfbf936d.js"></script><script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlInstance.66b95713.js"></script><script src="/static/ControlInstance.9cc7166d.js" nomodule="" defer></script>`,
|
||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css"><link rel="stylesheet" href="/static/ControlInstance.38d394c2.css">`,
|
||||
}
|
||||
127
internal/dis/component/control/static/build.mjs
Normal file
127
internal/dis/component/control/static/build.mjs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import { Parcel } from "@parcel/core"
|
||||
import { mkdir, rm, writeFile, readFile, unlink, rmdir, } from "fs/promises"
|
||||
import { join } from "path"
|
||||
import { parse as parseHTML } from 'node-html-parser';
|
||||
|
||||
//
|
||||
// PARAMETERS
|
||||
//
|
||||
|
||||
const ENTRYPOINTS = process.argv.slice(2)
|
||||
const ENTRY_DIR = join('.', '.entry-cache') // directory to place entries into
|
||||
const DIST_DIR = join('.', 'dist')
|
||||
const PUBLIC_DIR = '/static/'
|
||||
|
||||
const DEST_PACKAGE = process.env.GOPACKAGE ?? 'static'
|
||||
const DEST_FILE = (() => {
|
||||
const source = (process.env.GOFILE ?? 'assets.go')
|
||||
const base = source.substring(0, source.length - '.go'.length)
|
||||
return base + '_dist.go'
|
||||
})()
|
||||
|
||||
//
|
||||
// PREPARE DIRECTORIES
|
||||
//
|
||||
|
||||
process.stdout.write('Preparing directories ...')
|
||||
await Promise.all([
|
||||
mkdir(ENTRY_DIR, { recursive: true }),
|
||||
rm(DIST_DIR, { recursive: true, force: true })
|
||||
])
|
||||
console.log(' Done.')
|
||||
|
||||
|
||||
//
|
||||
// WRITE ENTRY POINTS
|
||||
//
|
||||
|
||||
process.stdout.write('Collecting entry points ')
|
||||
const entries = await Promise.all(ENTRYPOINTS.map(async (name) => {
|
||||
const entry = {
|
||||
'name': name,
|
||||
'bundleName': name + '.html',
|
||||
'src': join(ENTRY_DIR, name + '.html'),
|
||||
}
|
||||
|
||||
const content = `
|
||||
<script type='module' src='../src/base/index.ts'></script>
|
||||
<script type='module' src='../src/entry/${name}/index.ts'></script>
|
||||
<link rel='stylesheet' href='../src/entry/${name}/index.css'>
|
||||
`;
|
||||
await writeFile(entry.src, content)
|
||||
|
||||
process.stdout.write('.')
|
||||
return entry;
|
||||
}))
|
||||
console.log(' Done.')
|
||||
|
||||
//
|
||||
// BUNDLEING
|
||||
//
|
||||
|
||||
process.stdout.write('Bundleing assets ...')
|
||||
const bundler = new Parcel({
|
||||
entries: entries.map(e => e.src),
|
||||
defaultConfig: '@parcel/config-default',
|
||||
shouldDisableCache: true,
|
||||
shouldContentHash: true,
|
||||
defaultTargetOptions: {
|
||||
shouldOptimize: true,
|
||||
shouldScopeHoist: true,
|
||||
sourceMaps: false,
|
||||
distDir: DIST_DIR,
|
||||
publicUrl: PUBLIC_DIR,
|
||||
engines: {
|
||||
browsers: "defaults",
|
||||
}
|
||||
}
|
||||
});
|
||||
const { bundleGraph } = await bundler.run()
|
||||
console.log(' Done.')
|
||||
|
||||
|
||||
//
|
||||
// FIND ASSETS IN OUTPUT
|
||||
//
|
||||
|
||||
process.stdout.write('Find Assets in Output ')
|
||||
const bundles = bundleGraph.getBundles()
|
||||
const assets = await Promise.all(entries.map(async (entry) => {
|
||||
const mainBundle = bundles.find(b => b.name === entry.bundleName)
|
||||
if (mainBundle === undefined) throw new Error('Unable to find bundle for ' + entry.name)
|
||||
|
||||
// read, then delete the generated output file
|
||||
const { filePath } = mainBundle
|
||||
const html = parseHTML(await readFile(filePath))
|
||||
await unlink(filePath)
|
||||
|
||||
const scripts = html.querySelectorAll('script').map(script => script.outerHTML).join('')
|
||||
const links = html.querySelectorAll('link').map(link => link.outerHTML).join('')
|
||||
|
||||
process.stdout.write('.')
|
||||
return { ...entry, scripts, links }
|
||||
}))
|
||||
console.log(' Done.')
|
||||
|
||||
//
|
||||
// GENERATE GO
|
||||
//
|
||||
|
||||
process.stdout.write(`Writing ${DEST_FILE} ...`)
|
||||
const goAssets = assets.map(({ name, scripts, links }) => {
|
||||
return `
|
||||
// Assets${name} contains assets for the '${name}' entrypoint.
|
||||
var Assets${name} = Assets{
|
||||
\tScripts: \`${scripts}\`,
|
||||
\tStyles: \`${links}\`,\t
|
||||
}`.trim()
|
||||
}).join('\n\n')
|
||||
const goSource = `package ${DEST_PACKAGE}
|
||||
|
||||
// This file was automatically generated. Do not edit.
|
||||
|
||||
${goAssets}
|
||||
`;
|
||||
|
||||
await writeFile(DEST_FILE, goSource)
|
||||
console.log(' Done.')
|
||||
1
internal/dis/component/control/static/dist/ControlIndex.613b02c2.js
vendored
Normal file
1
internal/dis/component/control/static/dist/ControlIndex.613b02c2.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
internal/dis/component/control/static/dist/ControlIndex.6d2ae968.css
vendored
Normal file
1
internal/dis/component/control/static/dist/ControlIndex.6d2ae968.css
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.wisski{padding:1em}.wisski h3{padding:0}.wisski a.pure-button{float:right;position:relative;bottom:1em}.wisski.running{background-color:#9ada07}.wisski.stopped{background-color:#ff7a7a}
|
||||
1
internal/dis/component/control/static/dist/ControlIndex.6d59e220.css
vendored
Normal file
1
internal/dis/component/control/static/dist/ControlIndex.6d59e220.css
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.modal-terminal{width:66vw;height:66vh;background-color:#fff;background-clip:padding-box;-webkit-background-clip:padding-box;z-index:1000;border:17vh solid #000c;border-width:17vh 17vw;margin:-17vh -17vw;position:fixed;top:17vh;left:17vw;overflow:auto}.modal-terminal button{z-index:1001;position:fixed;top:17vh;right:17vw}.modal-terminal pre,.modal-terminal button{margin:5px}
|
||||
1
internal/dis/component/control/static/dist/ControlIndex.cfbf936d.js
vendored
Normal file
1
internal/dis/component/control/static/dist/ControlIndex.cfbf936d.js
vendored
Normal file
File diff suppressed because one or more lines are too long
0
internal/dis/component/control/static/dist/ControlInstance.38d394c2.css
vendored
Normal file
0
internal/dis/component/control/static/dist/ControlInstance.38d394c2.css
vendored
Normal file
1
internal/dis/component/control/static/dist/ControlInstance.66b95713.js
vendored
Normal file
1
internal/dis/component/control/static/dist/ControlInstance.66b95713.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},r={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in r){var o=r[e];delete r[e];var i={id:e,exports:{}};return n[e]=i,o.call(i.exports,i,i.exports),i.exports}var l=new Error("Cannot find module '"+e+"'");throw l.code="MODULE_NOT_FOUND",l}).register=function(e,n){r[e]=n},e.parcelRequireafa4=o),o("gJkWt");
|
||||
1
internal/dis/component/control/static/dist/ControlInstance.9cc7166d.js
vendored
Normal file
1
internal/dis/component/control/static/dist/ControlInstance.9cc7166d.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},o={},r=e.parcelRequireafa4;null==r&&((r=function(e){if(e in n)return n[e].exports;if(e in o){var r=o[e];delete o[e];var i={id:e,exports:{}};return n[e]=i,r.call(i.exports,i,i.exports),i.exports}var f=new Error("Cannot find module '"+e+"'");throw f.code="MODULE_NOT_FOUND",f}).register=function(e,n){o[e]=n},e.parcelRequireafa4=r),r("8s4Fe")}();
|
||||
0
internal/dis/component/control/static/dist/HomeHome.38d394c2.css
vendored
Normal file
0
internal/dis/component/control/static/dist/HomeHome.38d394c2.css
vendored
Normal file
0
internal/dis/component/control/static/dist/HomeHome.38d394c2.js
vendored
Normal file
0
internal/dis/component/control/static/dist/HomeHome.38d394c2.js
vendored
Normal file
1
internal/dis/component/control/static/dist/HomeHome.a75f04fa.css
vendored
Normal file
1
internal/dis/component/control/static/dist/HomeHome.a75f04fa.css
vendored
Normal file
File diff suppressed because one or more lines are too long
11
internal/dis/component/control/static/package.json
Normal file
11
internal/dis/component/control/static/package.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "wisski-distillery-frontend",
|
||||
"version": "1.0.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.5",
|
||||
"node-html-parser": "^6.1.1",
|
||||
"parcel": "^2.7.0"
|
||||
}
|
||||
}
|
||||
50
internal/dis/component/control/static/src/base/index.css
Normal file
50
internal/dis/component/control/static/src/base/index.css
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
body {
|
||||
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
header,
|
||||
main,
|
||||
footer {
|
||||
margin: 2em;
|
||||
}
|
||||
|
||||
.padding {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.overflow {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.overflow table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.overflow table td,
|
||||
.overflow table th{
|
||||
padding: .5em .5em;
|
||||
}
|
||||
|
||||
.overflow table td:not(:last-child),
|
||||
.overflow table th:not(:last-child) {
|
||||
width: 1px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.overflow table td:last-child,
|
||||
.overflow table th:last-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hspace {
|
||||
display: block;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.pure-button-action {
|
||||
background-color: rgb(66, 184, 221) !important;
|
||||
}
|
||||
.pure-button-success {
|
||||
background-color: rgb(28, 184, 65) !important;
|
||||
}
|
||||
4
internal/dis/component/control/static/src/base/index.ts
Normal file
4
internal/dis/component/control/static/src/base/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import "purecss/build/pure.css"
|
||||
import "purecss/build/grids-responsive.css"
|
||||
|
||||
import "./index.css"
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
.wisski {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.wisski h3 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.wisski a.pure-button {
|
||||
float: right;
|
||||
position: relative;
|
||||
bottom: 1em;
|
||||
}
|
||||
|
||||
.wisski.running {
|
||||
background-color: #9ADA07;
|
||||
}
|
||||
|
||||
.wisski.stopped {
|
||||
background-color: #ff7a7a;
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import "~/src/lib/remote"
|
||||
import "~/src/lib/highlight"
|
||||
|
|
@ -0,0 +1 @@
|
|||
@import url("../ControlIndex/index.css")
|
||||
|
|
@ -0,0 +1 @@
|
|||
import "../ControlIndex/index"
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
.header-link {
|
||||
position: relative;
|
||||
left: 0.5em;
|
||||
opacity: 0;
|
||||
font-size: 0.8em;
|
||||
|
||||
transition: opacity 0.2s ease-in-out 0.1s;
|
||||
-webkit-transition: opacity 0.2s ease-in-out 0.1s;
|
||||
-moz-transition: opacity 0.2s ease-in-out 0.1s;
|
||||
-ms-transition: opacity 0.2s ease-in-out 0.1s;
|
||||
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h2:hover .header-link,
|
||||
h3:hover .header-link,
|
||||
h4:hover .header-link,
|
||||
h5:hover .header-link,
|
||||
h6:hover .header-link {
|
||||
color: black !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import "./index.css"
|
||||
|
||||
/** Adapted from http://blog.parkermoore.de/2014/08/01/header-anchor-links-in-vanilla-javascript-for-github-pages-and-jekyll/ */
|
||||
const anchorForId = (id: string) => {
|
||||
const anchor = document.createElement("a")
|
||||
anchor.className = "header-link"
|
||||
anchor.href = "#" + id
|
||||
anchor.innerHTML = "#"
|
||||
return anchor
|
||||
}
|
||||
|
||||
const linkifyAnchors = (level: number) => {
|
||||
const headers = document.getElementsByTagName("h" + level);
|
||||
Array.from(headers).forEach((header) => {
|
||||
if (typeof header.id === "undefined" || header.id === "") return
|
||||
header.appendChild(anchorForId(header.id))
|
||||
})
|
||||
}
|
||||
|
||||
// linkify all the anchors from 1 ... 6
|
||||
(new Array(6)).fill(0).forEach((_, i) => linkifyAnchors(i + 1))
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import dayjs from "dayjs"
|
||||
const types: Record<string, (element: HTMLElement) => HTMLElement | string> = {
|
||||
"date": (element) => {
|
||||
return dayjs(element.innerText).format('YYYY-MM-DD HH:mm:ss ([UTC]Z)')
|
||||
},
|
||||
"path": (element) => {
|
||||
const text = element.innerText.split("/");
|
||||
return text[text.length - 1];
|
||||
},
|
||||
"pathbuilder": (element) => {
|
||||
// create a link and get the blob
|
||||
const filename = (element.getAttribute('data-name') ?? 'pathbuilder') + ".xml"
|
||||
const [link, blob] = make_download_link(filename, element.innerText, "application/xml")
|
||||
|
||||
link.className = "pure-button"
|
||||
const title = filename + ' (' + blob.size + ' Bytes)';
|
||||
link.append(title)
|
||||
return link
|
||||
}
|
||||
}
|
||||
|
||||
const make_download_link = (filename: string, content: string, type: string): [HTMLAnchorElement, Blob] => {
|
||||
const blob = new Blob(
|
||||
[content],
|
||||
{
|
||||
type: type ?? "text/plain"
|
||||
}
|
||||
);
|
||||
|
||||
const link = document.createElement("a")
|
||||
link.target = "_blank"
|
||||
link.download = filename
|
||||
link.href = URL.createObjectURL(blob)
|
||||
|
||||
return [link, blob]
|
||||
}
|
||||
|
||||
Object.keys(types).forEach(key => {
|
||||
const f = types[key];
|
||||
const elements = document.querySelectorAll("code." + key) as NodeListOf<HTMLElement>
|
||||
elements.forEach(element => {
|
||||
const newElement = f(element)
|
||||
if (typeof newElement === 'string') {
|
||||
element.innerHTML = ""
|
||||
element.appendChild(document.createTextNode(newElement))
|
||||
return
|
||||
}
|
||||
|
||||
element.parentNode!.replaceChild(newElement, element)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
.modal-terminal {
|
||||
width: 66vw;
|
||||
height: 66vh;
|
||||
|
||||
position: fixed;
|
||||
left: 17vw;
|
||||
top: 17vh;
|
||||
|
||||
background-color: white;
|
||||
|
||||
background-clip: padding-box;
|
||||
-webkit-background-clip: padding-box;
|
||||
|
||||
border-left: 17vw solid rgba(0, 0, 0, 0.8);
|
||||
border-right: 17vw solid rgba(0, 0, 0, 0.8);
|
||||
margin-left: -17vw;
|
||||
margin-right: -17vw;
|
||||
|
||||
border-top: 17vh solid rgba(0, 0, 0, 0.8);
|
||||
border-bottom: 17vh solid rgba(0, 0, 0, 0.8);
|
||||
margin-top: -17vh;
|
||||
margin-bottom: -17vh;
|
||||
|
||||
overflow: auto;
|
||||
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-terminal button {
|
||||
position: fixed;
|
||||
top: 17vh;
|
||||
right: 17vw;
|
||||
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.modal-terminal pre,
|
||||
.modal-terminal button
|
||||
{
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
125
internal/dis/component/control/static/src/lib/remote/index.ts
Normal file
125
internal/dis/component/control/static/src/lib/remote/index.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import "./index.css"
|
||||
import connectSocket from './socket';
|
||||
|
||||
type Println = ((line: string, flush?: boolean) => void) & {
|
||||
paintedFrames: number;
|
||||
missedFrames: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* makeTextBuffer returns a println() function that efficiently writes text into target, and keeps at most size elements in the traceback.
|
||||
* scrollContainer is used to scroll on every painted update.
|
||||
*/
|
||||
function makeTextBuffer(target: HTMLElement, scrollContainer: HTMLElement, size: number): Println {
|
||||
let lastAnimationFrame: number | null = null; // last scheduled animation frame
|
||||
|
||||
const buffer: Array<string> = []; // the internal buffer of lines
|
||||
const paint = () => {
|
||||
println.paintedFrames++
|
||||
target.innerText = buffer.join("\n")
|
||||
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
||||
lastAnimationFrame = null
|
||||
}
|
||||
|
||||
const println = (line: string, flush?: boolean) => {
|
||||
// add the line
|
||||
buffer.push(line)
|
||||
if (size !== 0 && buffer.length > size) {
|
||||
buffer.splice(0, buffer.length - size)
|
||||
}
|
||||
|
||||
// and update the browser in the next animation frame
|
||||
if (lastAnimationFrame !== null) {
|
||||
println.missedFrames++
|
||||
window.cancelAnimationFrame(lastAnimationFrame)
|
||||
}
|
||||
|
||||
// force a repaint!
|
||||
if(flush) return paint();
|
||||
|
||||
// schedule an animation frame
|
||||
lastAnimationFrame = window.requestAnimationFrame(paint);
|
||||
}
|
||||
println.paintedFrames = 0;
|
||||
println.missedFrames = 0;
|
||||
|
||||
return println;
|
||||
}
|
||||
|
||||
const elements = document.getElementsByClassName('remote-action')
|
||||
Array.from(elements).forEach((element) => {
|
||||
const action = element.getAttribute('data-action') as string;
|
||||
const reload = element.hasAttribute('data-force-reload');
|
||||
const param = element.getAttribute('data-param') as string | undefined;
|
||||
const bufferSize = (function () {
|
||||
const number = parseInt(element.getAttribute('data-buffer') ?? "", 10) ?? 0;
|
||||
return (isFinite(number) && number > 0) ? number : 0;
|
||||
})()
|
||||
|
||||
element.addEventListener('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
// create a modal dialog and append it to the body
|
||||
const modal = document.createElement("div")
|
||||
modal.className = "modal-terminal"
|
||||
document.body.append(modal)
|
||||
|
||||
// create a <pre> to write stuff into
|
||||
const target = document.createElement("pre")
|
||||
const println = makeTextBuffer(target, modal, bufferSize)
|
||||
modal.append(target)
|
||||
|
||||
|
||||
// create a button to eventually close everything
|
||||
const button = document.createElement("button")
|
||||
button.className = "pure-button pure-button-success"
|
||||
button.append(reload ? "Close & Reload" : "Close")
|
||||
button.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (reload) {
|
||||
button.setAttribute('disabled', 'disabled');
|
||||
target.innerHTML = 'Reloading page ...'
|
||||
location.reload()
|
||||
return;
|
||||
}
|
||||
|
||||
modal.parentNode?.removeChild(modal);
|
||||
})
|
||||
|
||||
const onbeforeunload = window.onbeforeunload;
|
||||
window.onbeforeunload = () => "A remote session is in progress. Are you sure you want to leave?";
|
||||
|
||||
// when closing, add a button to the modal!
|
||||
let didClose = false
|
||||
const close = function () {
|
||||
if (didClose) return
|
||||
didClose = true
|
||||
|
||||
window.onbeforeunload = onbeforeunload;
|
||||
modal.append(button)
|
||||
// DEBUG: print terminal stats!
|
||||
// const quota = (println.paintedFrames / (println.missedFrames + println.paintedFrames)) * 100
|
||||
// println(`Terminal: painted=${println.paintedFrames} missed=${println.missedFrames} (${quota}%)`, true)
|
||||
}
|
||||
|
||||
println("Connecting ...", true)
|
||||
|
||||
// connect to the socket and send the action
|
||||
connectSocket((socket) => {
|
||||
println("Connected", true)
|
||||
socket.send(action);
|
||||
if (typeof param === 'string') {
|
||||
socket.send(param);
|
||||
}
|
||||
}, (data) => {
|
||||
println(data);
|
||||
}).then(() => {
|
||||
println("Connection closed.", true)
|
||||
close();
|
||||
}).catch(() => {
|
||||
println("Connection errored.", true)
|
||||
close();
|
||||
});
|
||||
});
|
||||
})
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
export default function connectSocket(onOpen: (socket: WebSocket) => void, onData: (data: any) => void): Promise<CloseEvent> {
|
||||
return new Promise((rs, rj) => {
|
||||
const socket = new WebSocket(location.href.replace('http', 'ws'));
|
||||
|
||||
socket.onclose = rs;
|
||||
socket.onerror = rj;
|
||||
|
||||
socket.onmessage = (ev) => onData(ev.data)
|
||||
socket.onopen = () => onOpen(socket);
|
||||
});
|
||||
}
|
||||
32
internal/dis/component/control/static/static.go
Normal file
32
internal/dis/component/control/static/static.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Package static implements serving of fully static resources
|
||||
package static
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||
"github.com/tkw1536/goprogram/stream"
|
||||
)
|
||||
|
||||
type Static struct {
|
||||
component.Base
|
||||
}
|
||||
|
||||
func (*Static) Routes() []string { return []string{"/static/"} }
|
||||
|
||||
//go:embed dist
|
||||
var staticFS embed.FS
|
||||
|
||||
func (static *Static) Handler(route string, context context.Context, io stream.IOStream) (http.Handler, error) {
|
||||
// take the filesystem
|
||||
fs, err := fs.Sub(staticFS, "dist")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// and serve it
|
||||
return http.StripPrefix(route, http.FileServer(http.FS(fs))), nil
|
||||
}
|
||||
105
internal/dis/component/control/static/tsconfig.json
Normal file
105
internal/dis/component/control/static/tsconfig.json
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
"paths": {
|
||||
"~/*": ["./*"],
|
||||
}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
1468
internal/dis/component/control/static/yarn.lock
Normal file
1468
internal/dis/component/control/static/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue