websocket-text-relay 1.1.5 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -5
- package/changelog.md +10 -0
- package/docs/code-structure.md +15 -11
- package/eslint.config.js +5 -1
- package/package.json +9 -9
- package/src/ui/css/main.css +22 -78
- package/src/ui/favicon.png +0 -0
- package/src/ui/index.html +19 -14
- package/src/ui/js/components/activityLabels.js +41 -0
- package/src/ui/js/components/activityTimeSeries.js +58 -0
- package/src/ui/js/components/drawSessionLabel.js +117 -0
- package/src/ui/js/components/footerStatus.js +37 -0
- package/src/ui/js/components/headers.js +100 -0
- package/src/ui/js/components/sessionWedges.js +96 -0
- package/src/ui/js/components/statusRing.js +51 -0
- package/src/ui/js/data/wtrActivity.js +85 -0
- package/src/ui/js/data/wtrActivity.types.js +3 -0
- package/src/ui/js/data/wtrStatus.js +134 -0
- package/src/ui/js/data/wtrStatus.types.js +61 -0
- package/src/ui/js/{util → setup}/EventEmitter.js +5 -1
- package/src/ui/js/{util → setup}/WebsocketClient.js +1 -4
- package/src/ui/js/setup/dependencyManager.js +9 -0
- package/src/ui/js/setup/evalOnChange.js +18 -0
- package/src/ui/js/setup/eventSubscriber.js +21 -0
- package/src/ui/js/setup.js +143 -0
- package/src/ui/js/util/constants.js +5 -1
- package/src/ui/js/util/drawing.js +26 -76
- package/src/websocket-interface/httpServer.js +1 -1
- package/src/ui/js/components/ActivityTimeseriesGraph.js +0 -194
- package/src/ui/js/components/HeaderSummary.js +0 -22
- package/src/ui/js/components/ServerStatus.js +0 -43
- package/src/ui/js/components/SessionLabels.js +0 -319
- package/src/ui/js/components/SessionWedges.js +0 -127
- package/src/ui/js/components/StatusRing.js +0 -54
- package/src/ui/js/components/grids.js +0 -36
- package/src/ui/js/index.js +0 -121
- package/src/ui/js/main.js +0 -128
- package/src/ui/js/util/DependencyManager.js +0 -31
package/README.md
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
This application connects to your text editor using the Language Server Protocol (LSP) and then starts a websocket
|
|
4
4
|
server that front end clients can connect to and subscribe to file change events. This allows users to see their
|
|
5
5
|
front end changes evaluated immediately as they type, without having to first save the file to disk or reload the browser.
|
|
6
|
-
It's a similar concept to sites like CodePen and JsFiddle, except you can develop locally using your own text editor with all
|
|
7
|
-
of your personalized plugins instead of having to use an in browser code editor.
|
|
8
6
|
|
|
9
7
|
## Usage
|
|
10
8
|
|
|
@@ -23,14 +21,14 @@ the status UI and verify everything is running by first starting up your text ed
|
|
|
23
21
|
|
|
24
22
|
To use this on a professional level project that gives you the option to use modules, typescript, and react, I recommend using vite along with
|
|
25
23
|
the plugin [vite-plugin-websocket-text-relay](https://github.com/niels4/vite-plugin-websocket-text-relay). This plugin gives you all the power of vite when developing while also hooking
|
|
26
|
-
the live text updates into Vite's hot module reload system.
|
|
24
|
+
the live text updates into Vite's hot module reload system. See [live-demo-vite](https://github.com/niels4/live-demo-vite) for a complete project setup with Typescript, React, Vite, ViTest, and a simple router.
|
|
27
25
|
|
|
28
26
|
If you want to use this as a learning tool to play around with UI concepts using simple projects involving 1 html, css, and javascript file,
|
|
29
|
-
then check out
|
|
27
|
+
then check out [live-demo-vanillajs](https://github.com/niels4/live-demo-vanillajs). This is a great setup for following along with any short and focused web development tutorials.
|
|
30
28
|
|
|
31
29
|
And finally, the status UI for this project was also created using live updates from websocket-text-relay.
|
|
32
30
|
In addition to giving you live feedback on the status and activity of the application, it is also meant to serve as a
|
|
33
|
-
reference UI project that
|
|
31
|
+
reference UI project that doesn't use any external dependencies.
|
|
34
32
|
|
|
35
33
|
## Developing
|
|
36
34
|
|
package/changelog.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 1.1.7 - 2025/12/19
|
|
2
|
+
|
|
3
|
+
### UI Update
|
|
4
|
+
Allow default websocket port to be overridden with url search param: `?port=38378`
|
|
5
|
+
|
|
6
|
+
## 1.1.6 - 2025/12/19
|
|
7
|
+
|
|
8
|
+
### UI Update
|
|
9
|
+
Rewrite the UI to be simpler and fix browser compatibility issues.
|
|
10
|
+
|
|
1
11
|
## 1.1.5 - 2025/09/05
|
|
2
12
|
|
|
3
13
|
Minor UI spacing fix
|
package/docs/code-structure.md
CHANGED
|
@@ -87,21 +87,27 @@ The root of the static site is the `src/ui` directory.
|
|
|
87
87
|
The UI is entirely made up of SVG elements, so the only thing the index.html file has to do is set up the root
|
|
88
88
|
SVG element with some groups to act as containers for the different components to use.
|
|
89
89
|
|
|
90
|
-
### js/
|
|
90
|
+
### js/setup.js
|
|
91
91
|
|
|
92
92
|
- Sets up the websocket-text-relay client with handlers for css and javascript files.
|
|
93
93
|
- initializes the simple dependency management system that allows the UI to be split into several javascript files.
|
|
94
|
-
- Hooks up events to handle resizing the SVG element on window resize.
|
|
95
94
|
- emit data and activity events that the UI components can hook into
|
|
96
95
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
### js/util/DependencyManager.js
|
|
96
|
+
### js/setup/dependencyManager.js
|
|
100
97
|
|
|
101
98
|
This is a very quick and simple dependency management system. The dependency container is just an object in the global scope.
|
|
102
99
|
An exportDeps function is created to make it easy to specify which objects in scope are to be exported.
|
|
103
100
|
|
|
104
|
-
|
|
101
|
+
### js/setup/eventSubscriber.js
|
|
102
|
+
|
|
103
|
+
Provides the onEvent function that each file can use when subscribing to events. The events are automatically
|
|
104
|
+
unsubscribed when that file gets reevaluated. See the `js/components/sessionWedges.js` file for an example of usage.
|
|
105
|
+
|
|
106
|
+
### js/setup/evalOnChange.js
|
|
107
|
+
|
|
108
|
+
This provides a function where you can define what files should be run after the current file is updated.
|
|
109
|
+
Rather than calculate a dependency graph and try to automatically rerun files, simply define the behavior you want
|
|
110
|
+
while you are editing the file. See the `js/util/constants.js` file for an example of usage.
|
|
105
111
|
|
|
106
112
|
### js/util/drawing.js
|
|
107
113
|
|
|
@@ -115,10 +121,8 @@ The center of the UI is at (0, 0) with a minimum height and width of 2. Having a
|
|
|
115
121
|
|
|
116
122
|
### js/components/
|
|
117
123
|
|
|
118
|
-
The components directory contains the javascript
|
|
119
|
-
|
|
120
|
-
Each class handles the state for its component and has a draw function that renders it to the screen.
|
|
124
|
+
The components directory contains the javascript files that render the different elements on the screen.
|
|
121
125
|
|
|
122
|
-
|
|
126
|
+
### js/data/
|
|
123
127
|
|
|
124
|
-
|
|
128
|
+
Read data from the websocket server and construct data stores and events required by the components.
|
package/eslint.config.js
CHANGED
|
@@ -2,12 +2,16 @@ import js from "@eslint/js"
|
|
|
2
2
|
import globals from "globals"
|
|
3
3
|
import { defineConfig } from "eslint/config"
|
|
4
4
|
|
|
5
|
+
const wtrGlobals = {
|
|
6
|
+
__WTR__: "readonly",
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
export default defineConfig([
|
|
6
10
|
{
|
|
7
11
|
files: ["**/*.{js,mjs,cjs}"],
|
|
8
12
|
plugins: { js },
|
|
9
13
|
extends: ["js/recommended"],
|
|
10
|
-
languageOptions: { globals: { ...globals.node, ...globals.browser } },
|
|
14
|
+
languageOptions: { globals: { ...globals.node, ...globals.browser, ...wtrGlobals } },
|
|
11
15
|
},
|
|
12
16
|
{
|
|
13
17
|
rules: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "websocket-text-relay",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "An LSP server for sending live file updates from your text editor to the front end via websockets.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"websocket-text-relay": "./start.js"
|
|
@@ -36,15 +36,15 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/niels4/websocket-text-relay#readme",
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@eslint/js": "
|
|
40
|
-
"@vitest/coverage-v8": "
|
|
41
|
-
"@vitest/ui": "
|
|
42
|
-
"eslint": "
|
|
43
|
-
"globals": "
|
|
44
|
-
"prettier": "3.
|
|
45
|
-
"vitest": "
|
|
39
|
+
"@eslint/js": "9.39.2",
|
|
40
|
+
"@vitest/coverage-v8": "4.0.16",
|
|
41
|
+
"@vitest/ui": "4.0.16",
|
|
42
|
+
"eslint": "9.39.2",
|
|
43
|
+
"globals": "16.5.0",
|
|
44
|
+
"prettier": "3.7.4",
|
|
45
|
+
"vitest": "4.0.16"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"ws": "
|
|
48
|
+
"ws": "8.18.3"
|
|
49
49
|
}
|
|
50
50
|
}
|
package/src/ui/css/main.css
CHANGED
|
@@ -9,16 +9,12 @@
|
|
|
9
9
|
--color-evening-shadow: #514a45;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
.color_palette {
|
|
13
|
-
--bg2: var(--color-navy-nightfall);
|
|
14
|
-
--accent-active: var(var(--color-golden-sunset));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
12
|
.color_theme {
|
|
18
|
-
--
|
|
13
|
+
--accent-active: var(var(--color-golden-sunset));
|
|
14
|
+
--main-background-color: var(--color-navy-nightfall);
|
|
19
15
|
--header-text-color: var(--color-ivory-daybreak);
|
|
20
16
|
--summary-value-text-color: hsl(from var(--header-text-color) h s l / 0.9);
|
|
21
|
-
--offline-color: oklch(from var(--color-
|
|
17
|
+
--offline-color: oklch(from var(--color-tangerine-sunset) calc(l * 0.4) calc(c * 0.1) h);
|
|
22
18
|
--online-color: oklch(from var(--color-golden-sunset) calc(l * 0.8) calc(c * 0.25) h);
|
|
23
19
|
--active-color: var(--color-golden-sunset);
|
|
24
20
|
--wedge-online-color: oklch(from var(--color-azure-afternoon) calc(l * 0.5) calc(c * 0.5) h);
|
|
@@ -40,55 +36,33 @@ body {
|
|
|
40
36
|
font-size: 0.1pt;
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
stroke: #fff;
|
|
45
|
-
stroke-width: 0.001;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.grid_axis {
|
|
49
|
-
stroke-width: 0.003;
|
|
50
|
-
stroke: hsl(76, 90%, 80%);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
#header_summary_group {
|
|
39
|
+
.header {
|
|
54
40
|
fill: var(--header-text-color);
|
|
55
41
|
font-weight: 300;
|
|
42
|
+
font-size: 0.1pt;
|
|
56
43
|
}
|
|
57
44
|
|
|
58
45
|
.right_header {
|
|
59
46
|
text-anchor: end;
|
|
60
47
|
}
|
|
61
48
|
|
|
62
|
-
.
|
|
63
|
-
font-weight: 100;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
#status_ring_group {
|
|
49
|
+
.status_ring_wrapper.offline {
|
|
67
50
|
stroke: var(--offline-color);
|
|
68
51
|
}
|
|
69
52
|
|
|
70
|
-
|
|
53
|
+
.status_ring_wrapper.online {
|
|
71
54
|
stroke: var(--online-color);
|
|
72
55
|
}
|
|
73
56
|
|
|
74
|
-
|
|
57
|
+
.status_ring_wrapper.active {
|
|
75
58
|
stroke: var(--active-color);
|
|
76
59
|
}
|
|
77
60
|
|
|
78
|
-
.
|
|
79
|
-
display: none;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.single_wedge_group.online,
|
|
83
|
-
.single_wedge_group.active {
|
|
84
|
-
display: inherit;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.online .wedge_node {
|
|
61
|
+
.wedge_node {
|
|
88
62
|
fill: var(--wedge-online-color);
|
|
89
63
|
}
|
|
90
64
|
|
|
91
|
-
.active
|
|
65
|
+
.wedge_node.active {
|
|
92
66
|
fill: var(--wedge-active-color);
|
|
93
67
|
}
|
|
94
68
|
|
|
@@ -115,57 +89,27 @@ body {
|
|
|
115
89
|
fill: var(--active-color);
|
|
116
90
|
}
|
|
117
91
|
|
|
118
|
-
.
|
|
119
|
-
/* fill: #ffffff1f; */
|
|
120
|
-
fill: none;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.timeseries_path {
|
|
92
|
+
.time_series_path {
|
|
124
93
|
stroke: var(--timeseries-line-color);
|
|
125
|
-
|
|
94
|
+
stroke-width: 3;
|
|
95
|
+
vector-effect: non-scaling-stroke;
|
|
96
|
+
shape-rendering: crispEdges;
|
|
126
97
|
}
|
|
127
98
|
|
|
128
|
-
.
|
|
129
|
-
/* fill: var(--timeseries-label-color); */
|
|
130
|
-
font-size: 0.04pt;
|
|
131
|
-
font-weight: 400;
|
|
132
|
-
text-anchor: end;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.timeseries_small_label {
|
|
136
|
-
/* fill: var(--timeseries-label-color); */
|
|
99
|
+
.small_label {
|
|
137
100
|
font-size: 0.03pt;
|
|
138
101
|
font-weight: 400;
|
|
139
|
-
|
|
102
|
+
fill: var(--timeseries-label-color);
|
|
103
|
+
text-anchor: middle;
|
|
104
|
+
dominant-baseline: central;
|
|
140
105
|
}
|
|
141
106
|
|
|
142
|
-
.
|
|
107
|
+
.time_series_value {
|
|
143
108
|
fill: var(--timeseries-value-color);
|
|
144
109
|
font-size: 0.05pt;
|
|
145
110
|
font-weight: 300;
|
|
146
111
|
text-anchor: middle;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
.tooltip_text {
|
|
150
|
-
fill: #fff;
|
|
151
|
-
font-size: 0.025pt;
|
|
152
|
-
font-weight: 200;
|
|
153
|
-
text-anchor: middle;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.tooltip_outline {
|
|
157
|
-
stroke: #fff;
|
|
158
|
-
stroke-width: 0.002;
|
|
159
|
-
fill: #000;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.tooltip_display_group {
|
|
163
|
-
opacity: 0;
|
|
164
|
-
transition: opacity 0.35s ease;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.tooltip_wrapper_group:hover .tooltip_display_group {
|
|
168
|
-
opacity: 1;
|
|
112
|
+
dominant-baseline: central;
|
|
169
113
|
}
|
|
170
114
|
|
|
171
115
|
.server_status_label {
|
|
@@ -189,11 +133,11 @@ body {
|
|
|
189
133
|
text-anchor: middle;
|
|
190
134
|
}
|
|
191
135
|
|
|
192
|
-
.
|
|
136
|
+
.wedge_label_dot {
|
|
193
137
|
fill: #fff;
|
|
194
138
|
}
|
|
195
139
|
|
|
196
|
-
.
|
|
140
|
+
.wedge_label_line {
|
|
197
141
|
stroke: #fff;
|
|
198
142
|
stroke-width: 0.005;
|
|
199
143
|
}
|
|
Binary file
|
package/src/ui/index.html
CHANGED
|
@@ -6,31 +6,36 @@
|
|
|
6
6
|
|
|
7
7
|
<title>WTR Status</title>
|
|
8
8
|
|
|
9
|
+
<link rel="icon" type="image/png" href="favicon.png" />
|
|
9
10
|
<link rel="stylesheet" href="css/fonts.css" />
|
|
10
11
|
<style id="main_style"></style>
|
|
11
|
-
<script type="module" src="js/
|
|
12
|
+
<script type="module" src="js/setup.js" defer></script>
|
|
12
13
|
|
|
13
14
|
<style>
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
html {
|
|
16
|
+
height: 100dvh;
|
|
16
17
|
overscroll-behavior: none;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#svg_root {
|
|
21
|
+
position: absolute;
|
|
22
|
+
top: 0;
|
|
23
|
+
left: 0;
|
|
24
|
+
height: 100dvh;
|
|
25
|
+
width: 100dvw;
|
|
21
26
|
}
|
|
22
27
|
</style>
|
|
23
28
|
</head>
|
|
24
29
|
|
|
25
|
-
<body class="base_colors
|
|
30
|
+
<body class="base_colors color_theme">
|
|
26
31
|
<svg id="svg_root" viewBox="-1 -1 2 2" xmlns="http://www.w3.org/2000/svg">
|
|
27
|
-
<g id="
|
|
28
|
-
<g id="header_summary_group"></g>
|
|
29
|
-
<g id="status_ring_group"></g>
|
|
30
|
-
<g id="client_wedges_group" class="wedge_group"></g>
|
|
32
|
+
<g id="headers_group"></g>
|
|
31
33
|
<g id="editor_wedges_group" class="wedge_group"></g>
|
|
32
|
-
<g id="
|
|
33
|
-
<g id="
|
|
34
|
+
<g id="client_wedges_group" class="wedge_group"></g>
|
|
35
|
+
<g id="activity_time_series_group"></g>
|
|
36
|
+
<g id="activity_labels_group"></g>
|
|
37
|
+
<g id="status_ring_group"></g>
|
|
38
|
+
<g id="footer_status_group"></g>
|
|
34
39
|
</svg>
|
|
35
40
|
</body>
|
|
36
41
|
</html>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { drawText, wtrActivityDataWindowEmitter, onEvent, constants } = __WTR__
|
|
2
|
+
const { innerRingRadius } = constants
|
|
3
|
+
|
|
4
|
+
const parentGroup = document.getElementById("activity_labels_group")
|
|
5
|
+
parentGroup.innerHTML = ""
|
|
6
|
+
|
|
7
|
+
const valuePadding = 0.068
|
|
8
|
+
const labelPadding = 0.14
|
|
9
|
+
|
|
10
|
+
drawText({
|
|
11
|
+
text: "Max",
|
|
12
|
+
y: -innerRingRadius + labelPadding,
|
|
13
|
+
className: "small_label",
|
|
14
|
+
parent: parentGroup,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const maxValueText = drawText({
|
|
18
|
+
text: "0",
|
|
19
|
+
y: -innerRingRadius + valuePadding,
|
|
20
|
+
className: "time_series_value",
|
|
21
|
+
parent: parentGroup,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
drawText({
|
|
25
|
+
text: "Updates / second",
|
|
26
|
+
y: innerRingRadius - labelPadding,
|
|
27
|
+
className: "small_label",
|
|
28
|
+
parent: parentGroup,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const currentValueText = drawText({
|
|
32
|
+
text: "0",
|
|
33
|
+
y: innerRingRadius - valuePadding,
|
|
34
|
+
className: "time_series_value",
|
|
35
|
+
parent: parentGroup,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
onEvent(wtrActivityDataWindowEmitter, "data", ({ maxValue, currentValue }) => {
|
|
39
|
+
maxValueText.textContent = maxValue
|
|
40
|
+
currentValueText.textContent = currentValue
|
|
41
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const { drawSvgElement, constants, wtrActivityDataWindowEmitter, onEvent } = __WTR__
|
|
2
|
+
const { innerRingRadius, dataWindowSize } = constants
|
|
3
|
+
|
|
4
|
+
const parentGroup = document.getElementById("activity_time_series_group")
|
|
5
|
+
parentGroup.innerHTML = ""
|
|
6
|
+
|
|
7
|
+
const animationProps = { duration: 1000, easing: "linear", iterations: 1, fill: "forwards" }
|
|
8
|
+
|
|
9
|
+
const minX = -innerRingRadius
|
|
10
|
+
const width = innerRingRadius * 2
|
|
11
|
+
const height = 0.25
|
|
12
|
+
const maxY = height / 2
|
|
13
|
+
const intervalWidth = width / (dataWindowSize - 2)
|
|
14
|
+
|
|
15
|
+
const clipId = "time-series-clip"
|
|
16
|
+
|
|
17
|
+
const clipPath = drawSvgElement({
|
|
18
|
+
tag: "clipPath",
|
|
19
|
+
attributes: { id: clipId },
|
|
20
|
+
parent: parentGroup,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
drawSvgElement({
|
|
24
|
+
tag: "circle",
|
|
25
|
+
attributes: { r: innerRingRadius },
|
|
26
|
+
parent: clipPath,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const clippedGroup = drawSvgElement({
|
|
30
|
+
tag: "g",
|
|
31
|
+
attributes: { "clip-path": `url(#${clipId})` },
|
|
32
|
+
parent: parentGroup,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const valuePath = drawSvgElement({
|
|
36
|
+
tag: "path",
|
|
37
|
+
attributes: { d: "" },
|
|
38
|
+
className: "time_series_path",
|
|
39
|
+
parent: clippedGroup,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const getValueScale = (maxValue) => (maxValue === 0 ? 0.00001 : height / maxValue)
|
|
43
|
+
|
|
44
|
+
onEvent(wtrActivityDataWindowEmitter, "data", (data) => {
|
|
45
|
+
valuePath.setAttribute("d", data.path)
|
|
46
|
+
const prevValueScale = getValueScale(data.prevMaxValue)
|
|
47
|
+
const valueScale = getValueScale(data.maxValue)
|
|
48
|
+
|
|
49
|
+
const animationKeyFrames = [
|
|
50
|
+
{
|
|
51
|
+
transform: `translateX(${minX}px) translateY(${maxY}px) scaleX(${intervalWidth}) scaleY(${-prevValueScale})`,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
transform: `translateX(${minX - intervalWidth}px) translateY(${maxY}px) scaleX(${intervalWidth}) scaleY(${-valueScale})`,
|
|
55
|
+
},
|
|
56
|
+
]
|
|
57
|
+
valuePath.animate(animationKeyFrames, animationProps)
|
|
58
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const {
|
|
2
|
+
drawSvgElement,
|
|
3
|
+
drawPolarCircle,
|
|
4
|
+
drawPolarLine,
|
|
5
|
+
drawText,
|
|
6
|
+
polarToCartesian,
|
|
7
|
+
exportDeps,
|
|
8
|
+
evalOnChange,
|
|
9
|
+
} = __WTR__
|
|
10
|
+
|
|
11
|
+
evalOnChange(["js/components/sessionWedges.js"])
|
|
12
|
+
|
|
13
|
+
const labelLineDistance = 0.07
|
|
14
|
+
const underlinePadding = 0.02
|
|
15
|
+
const summaryCircleRadius = 0.014
|
|
16
|
+
const summaryValueSpacing = 0.1
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {{session: EditorStatus | ClientStatus, direction: 1 | -1}}
|
|
20
|
+
*/
|
|
21
|
+
const drawSessionLabel = ({ wedgeCenterAngle, wedgeCenterRadius, session, direction, parent }) => {
|
|
22
|
+
drawPolarCircle({
|
|
23
|
+
angle: wedgeCenterAngle,
|
|
24
|
+
radius: wedgeCenterRadius,
|
|
25
|
+
r: 0.01,
|
|
26
|
+
className: "wedge_label_dot",
|
|
27
|
+
parent,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const textStartRadius = wedgeCenterRadius + labelLineDistance
|
|
31
|
+
drawPolarLine({
|
|
32
|
+
startAngle: wedgeCenterAngle,
|
|
33
|
+
startRadius: wedgeCenterRadius,
|
|
34
|
+
endAngle: wedgeCenterAngle,
|
|
35
|
+
endRadius: textStartRadius,
|
|
36
|
+
className: "wedge_label_line",
|
|
37
|
+
parent,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const serverIndicator = session.isServer ? "* " : ""
|
|
41
|
+
const [textStartX, textStartY] = polarToCartesian(wedgeCenterAngle, textStartRadius)
|
|
42
|
+
const nameTextNode = drawText({
|
|
43
|
+
x: textStartX,
|
|
44
|
+
y: textStartY - underlinePadding,
|
|
45
|
+
text: serverIndicator + session.name,
|
|
46
|
+
textAnchor: direction === 1 ? "end" : "start",
|
|
47
|
+
className: "wedge_identifier",
|
|
48
|
+
parent,
|
|
49
|
+
})
|
|
50
|
+
const nameTextBBox = nameTextNode.getBBox()
|
|
51
|
+
|
|
52
|
+
const underlineX2 = direction === 1 ? nameTextBBox.x : nameTextBBox.x + nameTextBBox.width
|
|
53
|
+
drawSvgElement({
|
|
54
|
+
tag: "line",
|
|
55
|
+
attributes: { x1: textStartX, y1: textStartY, x2: underlineX2, y2: textStartY },
|
|
56
|
+
className: "wedge_label_line",
|
|
57
|
+
parent,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const summaryGroup = drawSvgElement({ tag: "g", parent })
|
|
61
|
+
|
|
62
|
+
let currentSummaryX = textStartX
|
|
63
|
+
|
|
64
|
+
const { leftText, rightText } =
|
|
65
|
+
direction === 1
|
|
66
|
+
? { leftText: session.openCount, rightText: session.activeOpenCount }
|
|
67
|
+
: { leftText: session.watchCount, rightText: session.activeWatchCount }
|
|
68
|
+
|
|
69
|
+
drawText({
|
|
70
|
+
x: currentSummaryX + summaryCircleRadius * 2,
|
|
71
|
+
y: textStartY,
|
|
72
|
+
text: leftText,
|
|
73
|
+
dominantBaseline: "middle",
|
|
74
|
+
className: "summary_text_value",
|
|
75
|
+
parent: summaryGroup,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
drawSvgElement({
|
|
79
|
+
tag: "circle",
|
|
80
|
+
attributes: {
|
|
81
|
+
cx: currentSummaryX,
|
|
82
|
+
cy: textStartY - summaryCircleRadius / 2,
|
|
83
|
+
r: summaryCircleRadius,
|
|
84
|
+
},
|
|
85
|
+
className: "summary_watched_circle",
|
|
86
|
+
parent: summaryGroup,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
currentSummaryX += summaryValueSpacing
|
|
90
|
+
|
|
91
|
+
drawText({
|
|
92
|
+
x: currentSummaryX + summaryCircleRadius * 2,
|
|
93
|
+
y: textStartY,
|
|
94
|
+
text: rightText,
|
|
95
|
+
dominantBaseline: "middle",
|
|
96
|
+
className: "summary_text_value",
|
|
97
|
+
parent: summaryGroup,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
drawSvgElement({
|
|
101
|
+
tag: "circle",
|
|
102
|
+
attributes: {
|
|
103
|
+
cx: currentSummaryX,
|
|
104
|
+
cy: textStartY - summaryCircleRadius / 2,
|
|
105
|
+
r: summaryCircleRadius,
|
|
106
|
+
},
|
|
107
|
+
className: "summary_active_circle",
|
|
108
|
+
parent: summaryGroup,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const summaryGroupBBox = summaryGroup.getBBox()
|
|
112
|
+
const translateX = direction === 1 ? -summaryGroupBBox.width - 0.02 : 0.05
|
|
113
|
+
const translateY = summaryGroupBBox.height / 2 + 0.014
|
|
114
|
+
summaryGroup.setAttribute("transform", `translate(${translateX}, ${translateY})`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
exportDeps({ drawSessionLabel })
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const { drawText, wtrStatusEmitter, onEvent } = __WTR__
|
|
2
|
+
|
|
3
|
+
const valueTextClass = "server_status_value"
|
|
4
|
+
const offlineTextClass = "server_status_offline"
|
|
5
|
+
|
|
6
|
+
const parentGroup = document.getElementById("footer_status_group")
|
|
7
|
+
parentGroup.innerHTML = ""
|
|
8
|
+
|
|
9
|
+
drawText({
|
|
10
|
+
x: 0,
|
|
11
|
+
y: 0.85,
|
|
12
|
+
text: "WS Server PID",
|
|
13
|
+
className: "server_status_label",
|
|
14
|
+
parent: parentGroup,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const pidElement = drawText({ x: 0, y: 0.748, text: "-1", parent: parentGroup })
|
|
18
|
+
|
|
19
|
+
const offlineElement = drawText({
|
|
20
|
+
x: 0,
|
|
21
|
+
y: 0.748,
|
|
22
|
+
text: "OFFLINE",
|
|
23
|
+
className: offlineTextClass,
|
|
24
|
+
parent: parentGroup,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
onEvent(wtrStatusEmitter, "data", (/** @type {WtrStatus} */ data) => {
|
|
28
|
+
const server = data.editors.find((editor) => editor.isServer)
|
|
29
|
+
if (!data.isOnline || server == null) {
|
|
30
|
+
pidElement.classList.remove(valueTextClass)
|
|
31
|
+
offlineElement.classList.add(offlineTextClass)
|
|
32
|
+
} else {
|
|
33
|
+
pidElement.textContent = server.lsPid
|
|
34
|
+
pidElement.classList.add(valueTextClass)
|
|
35
|
+
offlineElement.classList.remove(offlineTextClass)
|
|
36
|
+
}
|
|
37
|
+
})
|