signalk-instrument-widgets 0.5.0
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 +32 -0
- package/docs/screenshots/1_freeboard-widget.png +0 -0
- package/package.json +48 -0
- package/plugin/index.js +201 -0
- package/public/config.html +13 -0
- package/public/display.html +13 -0
- package/public/gauge.html +13 -0
- package/public/index.html +16 -0
- package/public/instruments.css +248 -0
- package/public/js/config.js +681 -0
- package/public/js/config.js.map +7 -0
- package/public/js/display.js +560 -0
- package/public/js/display.js.map +7 -0
- package/public/js/gauge.js +566 -0
- package/public/js/gauge.js.map +7 -0
- package/public/js/meter.js +536 -0
- package/public/js/meter.js.map +7 -0
- package/public/js/switch.js +519 -0
- package/public/js/switch.js.map +7 -0
- package/public/meter.html +13 -0
- package/public/switch.html +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# signalk-instrument-widgets
|
|
2
|
+
|
|
3
|
+
Add live instrument widgets to your chartplotter. Works with any chartplotter
|
|
4
|
+
that supports Signal K plotter extensions (such as Freeboard-SK).
|
|
5
|
+
|
|
6
|
+
- **Gauge** — a dial for any numeric value.
|
|
7
|
+
- **Meter** — a 0–100% bar for ratio-style values.
|
|
8
|
+
- **Switch** — shows an on/off value; tap to toggle it.
|
|
9
|
+
- **Display Value** — a clean text readout: a small label on top, the live
|
|
10
|
+
value large in the middle, and an abbreviation below — e.g. "Speed over
|
|
11
|
+
ground" / value / "SOG".
|
|
12
|
+
|
|
13
|
+
## Using the widgets
|
|
14
|
+
|
|
15
|
+
In your chartplotter, press and hold an **empty** widget area to add a
|
|
16
|
+
widget — its setup panel opens automatically. Press and hold a **placed**
|
|
17
|
+
widget to reconfigure or remove it.
|
|
18
|
+
|
|
19
|
+
Each widget is configured on its own: pick the value to show, choose the
|
|
20
|
+
units, and set the range and labels. So you can place two gauges showing two
|
|
21
|
+
different values side by side. Your settings are remembered for each widget.
|
|
22
|
+
|
|
23
|
+
## Demo switch
|
|
24
|
+
|
|
25
|
+
If your server has no real switches to control (for example a demo or
|
|
26
|
+
playback setup), the plugin can provide a demo switch so you can try the
|
|
27
|
+
Switch widget anywhere. It is on by default and can be turned off in the
|
|
28
|
+
plugin settings.
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
MIT
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "signalk-instrument-widgets",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Gauge, meter and switch widgets for Signal K chartplotters that support the plotterExtensions resource type",
|
|
5
|
+
"main": "plugin/index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"signalk-node-server-plugin",
|
|
8
|
+
"signalk-category-chart-plotters"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
"plugin",
|
|
12
|
+
"public",
|
|
13
|
+
"docs/screenshots",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "node scripts/build.mjs",
|
|
19
|
+
"test": "node --test \"test/**/*.test.js\" \"test/**/*.test.mjs\"",
|
|
20
|
+
"prepare": "node scripts/build.mjs"
|
|
21
|
+
},
|
|
22
|
+
"author": "Joel Kozikowski",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/joelkoz/signalk-instrument-widgets.git"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"esbuild": "^0.27.0",
|
|
30
|
+
"signalk-plotterext-bus": "file:../signalk-plotterext-bus"
|
|
31
|
+
},
|
|
32
|
+
"signalk-plugin-enabled-by-default": false,
|
|
33
|
+
"signalk": {
|
|
34
|
+
"displayName": "Instrument Widgets",
|
|
35
|
+
"appSupport": "none",
|
|
36
|
+
"screenshots": [
|
|
37
|
+
"./docs/screenshots/1_freeboard-widget.png"
|
|
38
|
+
],
|
|
39
|
+
"appstore": {
|
|
40
|
+
"displayName": "Instrument Widgets",
|
|
41
|
+
"categories": [
|
|
42
|
+
"chart-plotters"
|
|
43
|
+
],
|
|
44
|
+
"author": "Joel Kozikowski",
|
|
45
|
+
"description": "Gauge, meter and switch widgets for Signal K chartplotters that support the plotterExtensions resource type"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/plugin/index.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// signalk-instrument-widgets
|
|
2
|
+
//
|
|
3
|
+
// Reference extension for the plotterExtensions specification. The plugin
|
|
4
|
+
// itself is intentionally small: it registers a read-only plotterExtensions
|
|
5
|
+
// resource provider whose single resource is this extension's manifest, and
|
|
6
|
+
// (optionally) provides a demo switch path with a PUT handler so the switch
|
|
7
|
+
// widget can be exercised against playback data that has no real switches.
|
|
8
|
+
//
|
|
9
|
+
// The widget/panel web assets live in public/ and are served by the plugin
|
|
10
|
+
// itself, mounted as a top-level Express static route at
|
|
11
|
+
// /plotterext/<package-name>/. This is a public route (no token required,
|
|
12
|
+
// same as the old signalk-webapp mechanism) but, unlike a signalk-webapp, it
|
|
13
|
+
// does NOT appear in the server's Webapps launcher — these assets are only
|
|
14
|
+
// ever loaded inside a host chartplotter's iframe, never launched directly.
|
|
15
|
+
// It is deliberately NOT a /plugins/* route: those are admin-gated, which
|
|
16
|
+
// would break read-only users.
|
|
17
|
+
|
|
18
|
+
const path = require('path')
|
|
19
|
+
|
|
20
|
+
const PLUGIN_ID = 'signalk-instrument-widgets'
|
|
21
|
+
const ASSET_BASE = `/plotterext/${PLUGIN_ID}`
|
|
22
|
+
const PUBLIC_DIR = path.join(__dirname, '..', 'public')
|
|
23
|
+
const DEMO_SWITCH_PATH = 'electrical.switches.demo.state'
|
|
24
|
+
|
|
25
|
+
const pkg = require('../package.json')
|
|
26
|
+
|
|
27
|
+
function buildManifest() {
|
|
28
|
+
return {
|
|
29
|
+
name: 'Instrument Widgets',
|
|
30
|
+
description:
|
|
31
|
+
'Single-value instrument widgets: gauge, percent meter and switch.',
|
|
32
|
+
version: pkg.version,
|
|
33
|
+
apiVersion: '1',
|
|
34
|
+
requires: ['widgets', 'panels.iframe', 'signalk.stream'],
|
|
35
|
+
optional: ['signalk.put'],
|
|
36
|
+
widgets: [
|
|
37
|
+
{
|
|
38
|
+
id: 'gauge',
|
|
39
|
+
title: 'Gauge',
|
|
40
|
+
type: 'iframe',
|
|
41
|
+
url: `${ASSET_BASE}/gauge.html`,
|
|
42
|
+
size: '1x1',
|
|
43
|
+
configPanel: 'instrument-config',
|
|
44
|
+
lifecycle: 'whileEnabled'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'meter',
|
|
48
|
+
title: 'Meter (0-100%)',
|
|
49
|
+
type: 'iframe',
|
|
50
|
+
url: `${ASSET_BASE}/meter.html`,
|
|
51
|
+
size: '2x1',
|
|
52
|
+
configPanel: 'instrument-config',
|
|
53
|
+
lifecycle: 'whileEnabled'
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'switch',
|
|
57
|
+
title: 'Switch',
|
|
58
|
+
type: 'iframe',
|
|
59
|
+
url: `${ASSET_BASE}/switch.html`,
|
|
60
|
+
size: '1x1',
|
|
61
|
+
configPanel: 'instrument-config',
|
|
62
|
+
lifecycle: 'whileEnabled'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'display',
|
|
66
|
+
title: 'Display Value',
|
|
67
|
+
type: 'iframe',
|
|
68
|
+
url: `${ASSET_BASE}/display.html`,
|
|
69
|
+
size: '1x1',
|
|
70
|
+
configPanel: 'instrument-config',
|
|
71
|
+
lifecycle: 'whileEnabled'
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
panels: [
|
|
75
|
+
{
|
|
76
|
+
id: 'instrument-config',
|
|
77
|
+
title: 'Instrument Setup',
|
|
78
|
+
type: 'iframe',
|
|
79
|
+
url: `${ASSET_BASE}/config.html`,
|
|
80
|
+
lifecycle: 'onOpen'
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = (app) => {
|
|
87
|
+
let providerRegistered = false
|
|
88
|
+
let assetsMounted = false
|
|
89
|
+
let demoSwitchState = 0
|
|
90
|
+
let running = false
|
|
91
|
+
|
|
92
|
+
const debug = (msg) => app.debug(`${PLUGIN_ID}: ${msg}`)
|
|
93
|
+
|
|
94
|
+
// Serve public/ as a top-level static route. Express is provided by the
|
|
95
|
+
// Signal K server, so requiring it adds no runtime dependency of our own.
|
|
96
|
+
// Guarded so the test harness (a fake app with no .use) is a no-op.
|
|
97
|
+
const mountAssets = () => {
|
|
98
|
+
if (assetsMounted) return
|
|
99
|
+
if (typeof app.use !== 'function') return
|
|
100
|
+
let serveStatic
|
|
101
|
+
try {
|
|
102
|
+
serveStatic = require('express').static
|
|
103
|
+
} catch {
|
|
104
|
+
app.error(`${PLUGIN_ID}: express unavailable; cannot serve ${ASSET_BASE}`)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
app.use(ASSET_BASE, serveStatic(PUBLIC_DIR))
|
|
108
|
+
assetsMounted = true
|
|
109
|
+
debug(`assets served at ${ASSET_BASE}`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const registerProvider = () => {
|
|
113
|
+
if (providerRegistered) return
|
|
114
|
+
if (typeof app.registerResourceProvider !== 'function') {
|
|
115
|
+
app.error(`${PLUGIN_ID}: server has no resource provider registry`)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
app.registerResourceProvider({
|
|
119
|
+
type: 'plotterExtensions',
|
|
120
|
+
methods: {
|
|
121
|
+
listResources: async () => {
|
|
122
|
+
if (!running) return {}
|
|
123
|
+
return { [PLUGIN_ID]: buildManifest() }
|
|
124
|
+
},
|
|
125
|
+
getResource: async (id) => {
|
|
126
|
+
if (!running || id !== PLUGIN_ID) {
|
|
127
|
+
throw new Error(`No such plotterExtensions resource: ${id}`)
|
|
128
|
+
}
|
|
129
|
+
return buildManifest()
|
|
130
|
+
},
|
|
131
|
+
setResource: async () => {
|
|
132
|
+
throw new Error(`${PLUGIN_ID} is a read-only provider`)
|
|
133
|
+
},
|
|
134
|
+
deleteResource: async () => {
|
|
135
|
+
throw new Error(`${PLUGIN_ID} is a read-only provider`)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
providerRegistered = true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const emitDemoSwitch = () => {
|
|
143
|
+
app.handleMessage(PLUGIN_ID, {
|
|
144
|
+
updates: [
|
|
145
|
+
{
|
|
146
|
+
values: [{ path: DEMO_SWITCH_PATH, value: demoSwitchState }]
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const startDemoSwitch = () => {
|
|
153
|
+
app.registerPutHandler(
|
|
154
|
+
'vessels.self',
|
|
155
|
+
DEMO_SWITCH_PATH,
|
|
156
|
+
(_context, _path, value) => {
|
|
157
|
+
demoSwitchState = value === true || value === 1 || value === '1' ? 1 : 0
|
|
158
|
+
emitDemoSwitch()
|
|
159
|
+
return { state: 'COMPLETED', statusCode: 200 }
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
emitDemoSwitch()
|
|
163
|
+
debug(`demo switch active at ${DEMO_SWITCH_PATH}`)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
id: PLUGIN_ID,
|
|
168
|
+
name: 'Instrument Widgets',
|
|
169
|
+
description:
|
|
170
|
+
'Gauge, meter and switch widgets for chartplotters that support the plotterExtensions resource type.',
|
|
171
|
+
|
|
172
|
+
schema: () => ({
|
|
173
|
+
type: 'object',
|
|
174
|
+
properties: {
|
|
175
|
+
enableDemoSwitch: {
|
|
176
|
+
type: 'boolean',
|
|
177
|
+
title: 'Provide a demo switch path',
|
|
178
|
+
description:
|
|
179
|
+
`Registers a PUT handler and emits ${DEMO_SWITCH_PATH} so the ` +
|
|
180
|
+
'switch widget can be tested without real switch hardware.',
|
|
181
|
+
default: true
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}),
|
|
185
|
+
|
|
186
|
+
start(options) {
|
|
187
|
+
running = true
|
|
188
|
+
mountAssets()
|
|
189
|
+
registerProvider()
|
|
190
|
+
if (!options || options.enableDemoSwitch !== false) {
|
|
191
|
+
startDemoSwitch()
|
|
192
|
+
}
|
|
193
|
+
debug('started')
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
stop() {
|
|
197
|
+
running = false
|
|
198
|
+
debug('stopped')
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Instrument Setup</title>
|
|
7
|
+
<link rel="stylesheet" href="instruments.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body class="panel">
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script src="js/config.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Display Value</title>
|
|
7
|
+
<link rel="stylesheet" href="instruments.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body class="widget">
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script src="js/display.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Gauge</title>
|
|
7
|
+
<link rel="stylesheet" href="instruments.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body class="widget">
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script src="js/gauge.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head><meta charset="utf-8"><title>Instrument Widgets</title>
|
|
4
|
+
<link rel="stylesheet" href="instruments.css"></head>
|
|
5
|
+
<body class="panel">
|
|
6
|
+
<div id="root">
|
|
7
|
+
<h2>Instrument Widgets</h2>
|
|
8
|
+
<p class="status">This package provides gauge, meter, switch and display
|
|
9
|
+
widgets for chartplotters that support the Signal K
|
|
10
|
+
<code>plotterExtensions</code> resource type (e.g. Freeboard-SK). There is
|
|
11
|
+
nothing to configure here: in your chartplotter, press and hold an empty
|
|
12
|
+
widget area to add a widget, and press and hold a placed widget to
|
|
13
|
+
configure it.</p>
|
|
14
|
+
</div>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/* Shared styling for instrument widgets and the config panel. Widgets render
|
|
2
|
+
over the chart, so backgrounds stay translucent-dark for contrast on both
|
|
3
|
+
light and dark map styles. */
|
|
4
|
+
|
|
5
|
+
html,
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 0;
|
|
9
|
+
height: 100%;
|
|
10
|
+
font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
11
|
+
-webkit-user-select: none;
|
|
12
|
+
user-select: none;
|
|
13
|
+
background: transparent;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#root {
|
|
18
|
+
height: 100%;
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.widget-body {
|
|
26
|
+
background: rgba(20, 26, 32, 0.78);
|
|
27
|
+
border-radius: 10px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Widgets fill their frame with one translucent card. */
|
|
31
|
+
body.widget #root {
|
|
32
|
+
background: rgba(20, 26, 32, 0.78);
|
|
33
|
+
border-radius: 10px;
|
|
34
|
+
color: #e8edf2;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
body.widget svg {
|
|
38
|
+
width: 100%;
|
|
39
|
+
height: 100%;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
svg .track {
|
|
43
|
+
fill: none;
|
|
44
|
+
stroke: rgba(255, 255, 255, 0.18);
|
|
45
|
+
stroke-width: 7;
|
|
46
|
+
stroke-linecap: round;
|
|
47
|
+
}
|
|
48
|
+
svg .fill {
|
|
49
|
+
fill: none;
|
|
50
|
+
stroke: #29b6f6;
|
|
51
|
+
stroke-width: 7;
|
|
52
|
+
stroke-linecap: round;
|
|
53
|
+
}
|
|
54
|
+
svg .needle {
|
|
55
|
+
stroke: #ffb300;
|
|
56
|
+
stroke-width: 2;
|
|
57
|
+
}
|
|
58
|
+
svg .hub {
|
|
59
|
+
fill: #ffb300;
|
|
60
|
+
}
|
|
61
|
+
svg .value {
|
|
62
|
+
fill: #ffffff;
|
|
63
|
+
font-size: 20px;
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
text-anchor: middle;
|
|
66
|
+
}
|
|
67
|
+
svg .meter-value {
|
|
68
|
+
font-size: 28px;
|
|
69
|
+
}
|
|
70
|
+
svg .units {
|
|
71
|
+
fill: #9fb3c4;
|
|
72
|
+
font-size: 9px;
|
|
73
|
+
text-anchor: middle;
|
|
74
|
+
}
|
|
75
|
+
svg .label {
|
|
76
|
+
fill: #9fb3c4;
|
|
77
|
+
font-size: 10px;
|
|
78
|
+
text-anchor: middle;
|
|
79
|
+
}
|
|
80
|
+
svg .meter-label {
|
|
81
|
+
font-size: 12px;
|
|
82
|
+
}
|
|
83
|
+
svg .bound {
|
|
84
|
+
fill: #6c7f8e;
|
|
85
|
+
font-size: 8px;
|
|
86
|
+
text-anchor: middle;
|
|
87
|
+
}
|
|
88
|
+
svg .fillbar {
|
|
89
|
+
fill: #29b6f6;
|
|
90
|
+
}
|
|
91
|
+
svg rect.track {
|
|
92
|
+
fill: rgba(255, 255, 255, 0.18);
|
|
93
|
+
stroke: none;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Switch widget */
|
|
97
|
+
.switch {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 6px;
|
|
102
|
+
color: #e8edf2;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
}
|
|
105
|
+
.switch-pill {
|
|
106
|
+
width: 76px;
|
|
107
|
+
height: 36px;
|
|
108
|
+
border-radius: 18px;
|
|
109
|
+
background: rgba(255, 255, 255, 0.18);
|
|
110
|
+
position: relative;
|
|
111
|
+
transition: background 0.15s;
|
|
112
|
+
}
|
|
113
|
+
.switch-knob {
|
|
114
|
+
width: 32px;
|
|
115
|
+
height: 32px;
|
|
116
|
+
border-radius: 16px;
|
|
117
|
+
background: #cfd8dc;
|
|
118
|
+
position: absolute;
|
|
119
|
+
top: 2px;
|
|
120
|
+
left: 2px;
|
|
121
|
+
transition: left 0.15s, background 0.15s;
|
|
122
|
+
}
|
|
123
|
+
.switch.on .switch-pill {
|
|
124
|
+
background: #2e7d32;
|
|
125
|
+
}
|
|
126
|
+
.switch.on .switch-knob {
|
|
127
|
+
left: 42px;
|
|
128
|
+
background: #ffffff;
|
|
129
|
+
}
|
|
130
|
+
.switch-state {
|
|
131
|
+
font-size: 20px;
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
}
|
|
134
|
+
.switch.unknown .switch-state {
|
|
135
|
+
color: #6c7f8e;
|
|
136
|
+
}
|
|
137
|
+
.switch-label {
|
|
138
|
+
font-size: 13px;
|
|
139
|
+
color: #9fb3c4;
|
|
140
|
+
text-align: center;
|
|
141
|
+
max-width: 95%;
|
|
142
|
+
overflow: hidden;
|
|
143
|
+
text-overflow: ellipsis;
|
|
144
|
+
white-space: nowrap;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Display Value widget */
|
|
148
|
+
.display {
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
gap: 0;
|
|
154
|
+
color: #e8edf2;
|
|
155
|
+
text-align: center;
|
|
156
|
+
width: 100%;
|
|
157
|
+
padding: 2px;
|
|
158
|
+
box-sizing: border-box;
|
|
159
|
+
}
|
|
160
|
+
.display-top {
|
|
161
|
+
font-size: 13px;
|
|
162
|
+
color: #9fb3c4;
|
|
163
|
+
max-width: 97%;
|
|
164
|
+
overflow: hidden;
|
|
165
|
+
text-overflow: ellipsis;
|
|
166
|
+
white-space: nowrap;
|
|
167
|
+
}
|
|
168
|
+
.display-value {
|
|
169
|
+
font-size: 40px;
|
|
170
|
+
font-weight: 600;
|
|
171
|
+
line-height: 1.05;
|
|
172
|
+
}
|
|
173
|
+
.display-units {
|
|
174
|
+
font-size: 15px;
|
|
175
|
+
font-weight: 400;
|
|
176
|
+
color: #9fb3c4;
|
|
177
|
+
margin-left: 4px;
|
|
178
|
+
}
|
|
179
|
+
.display-bottom {
|
|
180
|
+
font-size: 22px;
|
|
181
|
+
color: #c3d2de;
|
|
182
|
+
max-width: 97%;
|
|
183
|
+
overflow: hidden;
|
|
184
|
+
text-overflow: ellipsis;
|
|
185
|
+
white-space: nowrap;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Config panel */
|
|
189
|
+
body.panel {
|
|
190
|
+
background: #1d242b;
|
|
191
|
+
color: #e8edf2;
|
|
192
|
+
overflow: auto;
|
|
193
|
+
}
|
|
194
|
+
body.panel #root {
|
|
195
|
+
display: block;
|
|
196
|
+
padding: 14px;
|
|
197
|
+
}
|
|
198
|
+
body.panel h2 {
|
|
199
|
+
margin: 0 0 12px;
|
|
200
|
+
font-size: 16px;
|
|
201
|
+
text-transform: capitalize;
|
|
202
|
+
}
|
|
203
|
+
body.panel .row {
|
|
204
|
+
display: flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
gap: 10px;
|
|
207
|
+
margin-bottom: 10px;
|
|
208
|
+
}
|
|
209
|
+
body.panel .row span {
|
|
210
|
+
flex: 0 0 110px;
|
|
211
|
+
font-size: 13px;
|
|
212
|
+
color: #9fb3c4;
|
|
213
|
+
}
|
|
214
|
+
body.panel input,
|
|
215
|
+
body.panel select {
|
|
216
|
+
flex: 1;
|
|
217
|
+
padding: 6px 8px;
|
|
218
|
+
border-radius: 6px;
|
|
219
|
+
border: 1px solid #39444d;
|
|
220
|
+
background: #12181d;
|
|
221
|
+
color: #e8edf2;
|
|
222
|
+
font-size: 13px;
|
|
223
|
+
min-width: 0;
|
|
224
|
+
}
|
|
225
|
+
body.panel .actions {
|
|
226
|
+
display: flex;
|
|
227
|
+
justify-content: flex-end;
|
|
228
|
+
gap: 8px;
|
|
229
|
+
margin-top: 14px;
|
|
230
|
+
}
|
|
231
|
+
body.panel button {
|
|
232
|
+
padding: 7px 16px;
|
|
233
|
+
border-radius: 6px;
|
|
234
|
+
border: 1px solid #39444d;
|
|
235
|
+
background: #2a333b;
|
|
236
|
+
color: #e8edf2;
|
|
237
|
+
font-size: 13px;
|
|
238
|
+
cursor: pointer;
|
|
239
|
+
}
|
|
240
|
+
body.panel button.primary {
|
|
241
|
+
background: #1976d2;
|
|
242
|
+
border-color: #1976d2;
|
|
243
|
+
}
|
|
244
|
+
body.panel .status {
|
|
245
|
+
font-size: 12px;
|
|
246
|
+
color: #9fb3c4;
|
|
247
|
+
min-height: 1em;
|
|
248
|
+
}
|