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.
Files changed (38) hide show
  1. package/README.md +3 -5
  2. package/changelog.md +10 -0
  3. package/docs/code-structure.md +15 -11
  4. package/eslint.config.js +5 -1
  5. package/package.json +9 -9
  6. package/src/ui/css/main.css +22 -78
  7. package/src/ui/favicon.png +0 -0
  8. package/src/ui/index.html +19 -14
  9. package/src/ui/js/components/activityLabels.js +41 -0
  10. package/src/ui/js/components/activityTimeSeries.js +58 -0
  11. package/src/ui/js/components/drawSessionLabel.js +117 -0
  12. package/src/ui/js/components/footerStatus.js +37 -0
  13. package/src/ui/js/components/headers.js +100 -0
  14. package/src/ui/js/components/sessionWedges.js +96 -0
  15. package/src/ui/js/components/statusRing.js +51 -0
  16. package/src/ui/js/data/wtrActivity.js +85 -0
  17. package/src/ui/js/data/wtrActivity.types.js +3 -0
  18. package/src/ui/js/data/wtrStatus.js +134 -0
  19. package/src/ui/js/data/wtrStatus.types.js +61 -0
  20. package/src/ui/js/{util → setup}/EventEmitter.js +5 -1
  21. package/src/ui/js/{util → setup}/WebsocketClient.js +1 -4
  22. package/src/ui/js/setup/dependencyManager.js +9 -0
  23. package/src/ui/js/setup/evalOnChange.js +18 -0
  24. package/src/ui/js/setup/eventSubscriber.js +21 -0
  25. package/src/ui/js/setup.js +143 -0
  26. package/src/ui/js/util/constants.js +5 -1
  27. package/src/ui/js/util/drawing.js +26 -76
  28. package/src/websocket-interface/httpServer.js +1 -1
  29. package/src/ui/js/components/ActivityTimeseriesGraph.js +0 -194
  30. package/src/ui/js/components/HeaderSummary.js +0 -22
  31. package/src/ui/js/components/ServerStatus.js +0 -43
  32. package/src/ui/js/components/SessionLabels.js +0 -319
  33. package/src/ui/js/components/SessionWedges.js +0 -127
  34. package/src/ui/js/components/StatusRing.js +0 -54
  35. package/src/ui/js/components/grids.js +0 -36
  36. package/src/ui/js/index.js +0 -121
  37. package/src/ui/js/main.js +0 -128
  38. 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 this [simple reference project](https://github.com/niels4/wtr-simple-example). This is a great setup for following along with any short and focused web development tutorials.
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 is more complicated than a single javascript file, but still doesn't use any external dependencies.
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
@@ -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/index.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
- Whenever a javascript file is edited, its exports are updated and the main.js file is rerun.
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
- An onEvent function is also created and exported here, it prevents event leaks by cleaning up any registered event handlers whenever the javascript is reevaluated.
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 classes that render the different elements on the screen.
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
- Any components affected by data updates will also have an update function. When called the component will update the elements it manages with the new data.
126
+ ### js/data/
123
127
 
124
- Some components also respond to text update activity, these components will have a triggerActivity function as well.
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.5",
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": "^9.33.0",
40
- "@vitest/coverage-v8": "^3.2.4",
41
- "@vitest/ui": "^3.2.4",
42
- "eslint": "^9.33.0",
43
- "globals": "^16.3.0",
44
- "prettier": "3.6.2",
45
- "vitest": "^3.2.4"
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": "^8.18.3"
48
+ "ws": "8.18.3"
49
49
  }
50
50
  }
@@ -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
- --main-background-color: var(--bg2);
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-golden-sunset) calc(l * 0.5) calc(c * 0.25) h);
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
- #grid_group {
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
- .header_number {
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
- #status_ring_group.online {
53
+ .status_ring_wrapper.online {
71
54
  stroke: var(--online-color);
72
55
  }
73
56
 
74
- #status_ring_group.active {
57
+ .status_ring_wrapper.active {
75
58
  stroke: var(--active-color);
76
59
  }
77
60
 
78
- .single_wedge_group {
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 .wedge_node {
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
- .timeseries_bg {
119
- /* fill: #ffffff1f; */
120
- fill: none;
121
- }
122
-
123
- .timeseries_path {
92
+ .time_series_path {
124
93
  stroke: var(--timeseries-line-color);
125
- clip-path: url(#inner_circle_clip);
94
+ stroke-width: 3;
95
+ vector-effect: non-scaling-stroke;
96
+ shape-rendering: crispEdges;
126
97
  }
127
98
 
128
- .timeseries_big_label {
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
- text-anchor: end;
102
+ fill: var(--timeseries-label-color);
103
+ text-anchor: middle;
104
+ dominant-baseline: central;
140
105
  }
141
106
 
142
- .timeseries_value {
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
- .test_circle {
136
+ .wedge_label_dot {
193
137
  fill: #fff;
194
138
  }
195
139
 
196
- .test_line {
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/index.js" defer></script>
12
+ <script type="module" src="js/setup.js" defer></script>
12
13
 
13
14
  <style>
14
- body {
15
- margin: 0;
15
+ html {
16
+ height: 100dvh;
16
17
  overscroll-behavior: none;
17
- display: grid;
18
- align-items: center;
19
- justify-content: center;
20
- padding-top: 2px;
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 color_palette color_theme">
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="grid_group"></g>
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="activity_timeseries_graph"></g>
33
- <g id="server_status_group"></g>
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
+ })