pmptr 0.1.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/LICENSE +21 -0
- package/README.md +189 -0
- package/assets/icon.png +0 -0
- package/assets/icon.svg +10 -0
- package/assets/icons/1024x1024.png +0 -0
- package/assets/icons/128x128.png +0 -0
- package/assets/icons/16x16.png +0 -0
- package/assets/icons/256x256.png +0 -0
- package/assets/icons/32x32.png +0 -0
- package/assets/icons/512x512.png +0 -0
- package/assets/icons/64x64.png +0 -0
- package/bin/pmptr.js +12 -0
- package/package.json +72 -0
- package/src/control/control.css +317 -0
- package/src/control/control.html +260 -0
- package/src/control/control.js +313 -0
- package/src/main/main.js +223 -0
- package/src/main/preload.js +35 -0
- package/src/prompter/prompter-preload.js +17 -0
- package/src/prompter/prompter.css +233 -0
- package/src/prompter/prompter.html +44 -0
- package/src/prompter/prompter.js +198 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="color-scheme" content="dark" />
|
|
6
|
+
<title>pmptr - control</title>
|
|
7
|
+
<link rel="stylesheet" href="control.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header class="topbar">
|
|
11
|
+
<div class="brand">pmptr</div>
|
|
12
|
+
<div class="sub">virtual teleprompter</div>
|
|
13
|
+
</header>
|
|
14
|
+
|
|
15
|
+
<main class="layout">
|
|
16
|
+
<section class="card">
|
|
17
|
+
<div class="card-head">
|
|
18
|
+
<h2>Script</h2>
|
|
19
|
+
<div class="row tight">
|
|
20
|
+
<button id="loadSample" type="button" class="ghost">
|
|
21
|
+
Load sample
|
|
22
|
+
</button>
|
|
23
|
+
<button id="clearScript" type="button" class="ghost">
|
|
24
|
+
Clear
|
|
25
|
+
</button>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
<textarea
|
|
29
|
+
id="script"
|
|
30
|
+
spellcheck="false"
|
|
31
|
+
placeholder="Paste your script here. Blank lines become paragraph breaks."
|
|
32
|
+
></textarea>
|
|
33
|
+
<div class="hint" id="scriptMeta">0 words · 0 chars</div>
|
|
34
|
+
</section>
|
|
35
|
+
|
|
36
|
+
<section class="card">
|
|
37
|
+
<h2>Reading</h2>
|
|
38
|
+
<div class="grid">
|
|
39
|
+
<label class="field">
|
|
40
|
+
<span class="lbl"
|
|
41
|
+
>Speed <output id="speedOut">40</output>
|
|
42
|
+
<span class="unit">px/s</span></span
|
|
43
|
+
>
|
|
44
|
+
<input id="speed" type="range" min="5" max="300" step="1" value="40" />
|
|
45
|
+
</label>
|
|
46
|
+
<label class="field">
|
|
47
|
+
<span class="lbl"
|
|
48
|
+
>Text size <output id="fontOut">44</output>
|
|
49
|
+
<span class="unit">px</span></span
|
|
50
|
+
>
|
|
51
|
+
<input id="font" type="range" min="16" max="120" step="1" value="44" />
|
|
52
|
+
</label>
|
|
53
|
+
<label class="field">
|
|
54
|
+
<span class="lbl"
|
|
55
|
+
>Line height
|
|
56
|
+
<output id="lhOut">1.45</output></span
|
|
57
|
+
>
|
|
58
|
+
<input
|
|
59
|
+
id="lh"
|
|
60
|
+
type="range"
|
|
61
|
+
min="1"
|
|
62
|
+
max="2.4"
|
|
63
|
+
step="0.05"
|
|
64
|
+
value="1.45"
|
|
65
|
+
/>
|
|
66
|
+
</label>
|
|
67
|
+
<label class="field">
|
|
68
|
+
<span class="lbl"
|
|
69
|
+
>Letter spacing
|
|
70
|
+
<output id="lsOut">0</output>
|
|
71
|
+
<span class="unit">px</span></span
|
|
72
|
+
>
|
|
73
|
+
<input
|
|
74
|
+
id="ls"
|
|
75
|
+
type="range"
|
|
76
|
+
min="-2"
|
|
77
|
+
max="10"
|
|
78
|
+
step="0.5"
|
|
79
|
+
value="0"
|
|
80
|
+
/>
|
|
81
|
+
</label>
|
|
82
|
+
<label class="field">
|
|
83
|
+
<span class="lbl"
|
|
84
|
+
>Margin <output id="marginOut">0</output>
|
|
85
|
+
<span class="unit">px</span></span
|
|
86
|
+
>
|
|
87
|
+
<input
|
|
88
|
+
id="margin"
|
|
89
|
+
type="range"
|
|
90
|
+
min="0"
|
|
91
|
+
max="200"
|
|
92
|
+
step="2"
|
|
93
|
+
value="0"
|
|
94
|
+
/>
|
|
95
|
+
</label>
|
|
96
|
+
</div>
|
|
97
|
+
</section>
|
|
98
|
+
|
|
99
|
+
<section class="card">
|
|
100
|
+
<h2>Appearance</h2>
|
|
101
|
+
<div class="grid">
|
|
102
|
+
<label class="field">
|
|
103
|
+
<span class="lbl">Text color</span>
|
|
104
|
+
<input id="color" type="color" value="#ffffff" />
|
|
105
|
+
</label>
|
|
106
|
+
<label class="field">
|
|
107
|
+
<span class="lbl">Highlight color</span>
|
|
108
|
+
<input id="hl" type="color" value="#ffd84d" />
|
|
109
|
+
</label>
|
|
110
|
+
<label class="field">
|
|
111
|
+
<span class="lbl"
|
|
112
|
+
>Background opacity
|
|
113
|
+
<output id="bgOut">35</output>
|
|
114
|
+
<span class="unit">%</span></span
|
|
115
|
+
>
|
|
116
|
+
<input
|
|
117
|
+
id="bg"
|
|
118
|
+
type="range"
|
|
119
|
+
min="0"
|
|
120
|
+
max="100"
|
|
121
|
+
step="1"
|
|
122
|
+
value="35"
|
|
123
|
+
/>
|
|
124
|
+
</label>
|
|
125
|
+
<label class="field">
|
|
126
|
+
<span class="lbl"
|
|
127
|
+
>Background dim
|
|
128
|
+
<output id="dimOut">0</output>
|
|
129
|
+
<span class="unit">%</span></span
|
|
130
|
+
>
|
|
131
|
+
<input
|
|
132
|
+
id="dim"
|
|
133
|
+
type="range"
|
|
134
|
+
min="0"
|
|
135
|
+
max="100"
|
|
136
|
+
step="1"
|
|
137
|
+
value="0"
|
|
138
|
+
/>
|
|
139
|
+
</label>
|
|
140
|
+
<label class="field wide">
|
|
141
|
+
<span class="lbl"
|
|
142
|
+
>Text outline
|
|
143
|
+
<output id="strokeOut">0</output>
|
|
144
|
+
<span class="unit">px</span></span
|
|
145
|
+
>
|
|
146
|
+
<input
|
|
147
|
+
id="stroke"
|
|
148
|
+
type="range"
|
|
149
|
+
min="0"
|
|
150
|
+
max="6"
|
|
151
|
+
step="0.5"
|
|
152
|
+
value="0"
|
|
153
|
+
/>
|
|
154
|
+
</label>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="row tight">
|
|
157
|
+
<label class="check">
|
|
158
|
+
<input id="mirror" type="checkbox" />
|
|
159
|
+
<span>Mirror (for beam-splitter glass)</span>
|
|
160
|
+
</label>
|
|
161
|
+
<label class="check">
|
|
162
|
+
<input id="bold" type="checkbox" />
|
|
163
|
+
<span>Bold</span>
|
|
164
|
+
</label>
|
|
165
|
+
<label class="check">
|
|
166
|
+
<input id="uppercase" type="checkbox" />
|
|
167
|
+
<span>UPPERCASE</span>
|
|
168
|
+
</label>
|
|
169
|
+
<label class="check">
|
|
170
|
+
<input id="showReadingLine" type="checkbox" checked />
|
|
171
|
+
<span>Reading line</span>
|
|
172
|
+
</label>
|
|
173
|
+
</div>
|
|
174
|
+
</section>
|
|
175
|
+
|
|
176
|
+
<section class="card">
|
|
177
|
+
<h2>Window</h2>
|
|
178
|
+
<div class="grid">
|
|
179
|
+
<label class="field">
|
|
180
|
+
<span class="lbl"
|
|
181
|
+
>Width <output id="winWidthOut">900</output>
|
|
182
|
+
<span class="unit">px</span></span
|
|
183
|
+
>
|
|
184
|
+
<input
|
|
185
|
+
id="winWidth"
|
|
186
|
+
type="range"
|
|
187
|
+
min="240"
|
|
188
|
+
max="2400"
|
|
189
|
+
step="10"
|
|
190
|
+
value="900"
|
|
191
|
+
/>
|
|
192
|
+
</label>
|
|
193
|
+
<label class="field">
|
|
194
|
+
<span class="lbl"
|
|
195
|
+
>Height <output id="winHeightOut">280</output>
|
|
196
|
+
<span class="unit">px</span></span
|
|
197
|
+
>
|
|
198
|
+
<input
|
|
199
|
+
id="winHeight"
|
|
200
|
+
type="range"
|
|
201
|
+
min="80"
|
|
202
|
+
max="900"
|
|
203
|
+
step="10"
|
|
204
|
+
value="280"
|
|
205
|
+
/>
|
|
206
|
+
</label>
|
|
207
|
+
<label class="field">
|
|
208
|
+
<span class="lbl">Position preset</span>
|
|
209
|
+
<select id="position">
|
|
210
|
+
<option value="top-center" selected>Top center</option>
|
|
211
|
+
<option value="top-left">Top left</option>
|
|
212
|
+
<option value="top-right">Top right</option>
|
|
213
|
+
<option value="bottom-center">Bottom center</option>
|
|
214
|
+
</select>
|
|
215
|
+
</label>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="row tight">
|
|
218
|
+
<label class="check">
|
|
219
|
+
<input id="alwaysOnTop" type="checkbox" checked />
|
|
220
|
+
<span>Always on top</span>
|
|
221
|
+
</label>
|
|
222
|
+
<label class="check">
|
|
223
|
+
<input id="clickThrough" type="checkbox" />
|
|
224
|
+
<span>Click-through (lock)</span>
|
|
225
|
+
</label>
|
|
226
|
+
</div>
|
|
227
|
+
</section>
|
|
228
|
+
|
|
229
|
+
<section class="card actions">
|
|
230
|
+
<button id="openBtn" class="primary" type="button">
|
|
231
|
+
Open floating prompter
|
|
232
|
+
</button>
|
|
233
|
+
<button id="closeBtn" type="button" disabled>Close prompter</button>
|
|
234
|
+
<span class="state" id="state">prompter closed</span>
|
|
235
|
+
</section>
|
|
236
|
+
|
|
237
|
+
<details class="card hint-card">
|
|
238
|
+
<summary>Shortcuts & tips</summary>
|
|
239
|
+
<ul>
|
|
240
|
+
<li>
|
|
241
|
+
<b>Space</b> - play / pause. <b>R</b> - reset to top.
|
|
242
|
+
<b>↑ / ↓</b> - speed ± 5. <b>L</b> - toggle click-through.
|
|
243
|
+
<b>Esc</b> - close prompter.
|
|
244
|
+
</li>
|
|
245
|
+
<li>
|
|
246
|
+
When the prompter is <b>locked</b> (click-through on), the HUD
|
|
247
|
+
still works because we forward mouse events to interactive
|
|
248
|
+
children. The reading area itself is fully click-through.
|
|
249
|
+
</li>
|
|
250
|
+
<li>
|
|
251
|
+
Drag the bottom-right corner of the floating window to resize
|
|
252
|
+
live. Position preset recenters it.
|
|
253
|
+
</li>
|
|
254
|
+
</ul>
|
|
255
|
+
</details>
|
|
256
|
+
</main>
|
|
257
|
+
|
|
258
|
+
<script src="control.js"></script>
|
|
259
|
+
</body>
|
|
260
|
+
</html>
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
const api = window.pmptr;
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
script: "",
|
|
5
|
+
speed: 40,
|
|
6
|
+
font: 44,
|
|
7
|
+
lh: 1.45,
|
|
8
|
+
ls: 0,
|
|
9
|
+
margin: 0,
|
|
10
|
+
color: "#ffffff",
|
|
11
|
+
hl: "#ffd84d",
|
|
12
|
+
bg: 35,
|
|
13
|
+
dim: 0,
|
|
14
|
+
stroke: 0,
|
|
15
|
+
mirror: false,
|
|
16
|
+
bold: false,
|
|
17
|
+
uppercase: false,
|
|
18
|
+
showReadingLine: true,
|
|
19
|
+
alwaysOnTop: true,
|
|
20
|
+
clickThrough: false,
|
|
21
|
+
winWidth: 900,
|
|
22
|
+
winHeight: 280,
|
|
23
|
+
position: "top-center",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const SAMPLE = `Welcome to pmptr.
|
|
27
|
+
|
|
28
|
+
This is a minimal virtual teleprompter that floats above your work as a transparent, always-on-top window.
|
|
29
|
+
|
|
30
|
+
Paste your own script on the left, adjust the speed, colors, and opacity, then open the floating prompter.
|
|
31
|
+
|
|
32
|
+
A few tips:
|
|
33
|
+
|
|
34
|
+
* The "lock" toggle makes the prompter click-through, so you can keep working with your mouse on whatever is behind it.
|
|
35
|
+
* Drag the bottom-right corner of the floating window to resize it.
|
|
36
|
+
* The reading line stays fixed in the middle; the text scrolls past it.
|
|
37
|
+
|
|
38
|
+
Good luck on stage.`;
|
|
39
|
+
|
|
40
|
+
const $ = (id) => document.getElementById(id);
|
|
41
|
+
|
|
42
|
+
const els = {
|
|
43
|
+
script: $("script"),
|
|
44
|
+
scriptMeta: $("scriptMeta"),
|
|
45
|
+
loadSample: $("loadSample"),
|
|
46
|
+
clearScript: $("clearScript"),
|
|
47
|
+
speed: $("speed"),
|
|
48
|
+
speedOut: $("speedOut"),
|
|
49
|
+
font: $("font"),
|
|
50
|
+
fontOut: $("fontOut"),
|
|
51
|
+
lh: $("lh"),
|
|
52
|
+
lhOut: $("lhOut"),
|
|
53
|
+
ls: $("ls"),
|
|
54
|
+
lsOut: $("lsOut"),
|
|
55
|
+
margin: $("margin"),
|
|
56
|
+
marginOut: $("marginOut"),
|
|
57
|
+
color: $("color"),
|
|
58
|
+
hl: $("hl"),
|
|
59
|
+
bg: $("bg"),
|
|
60
|
+
bgOut: $("bgOut"),
|
|
61
|
+
dim: $("dim"),
|
|
62
|
+
dimOut: $("dimOut"),
|
|
63
|
+
stroke: $("stroke"),
|
|
64
|
+
strokeOut: $("strokeOut"),
|
|
65
|
+
mirror: $("mirror"),
|
|
66
|
+
bold: $("bold"),
|
|
67
|
+
uppercase: $("uppercase"),
|
|
68
|
+
showReadingLine: $("showReadingLine"),
|
|
69
|
+
alwaysOnTop: $("alwaysOnTop"),
|
|
70
|
+
clickThrough: $("clickThrough"),
|
|
71
|
+
winWidth: $("winWidth"),
|
|
72
|
+
winWidthOut: $("winWidthOut"),
|
|
73
|
+
winHeight: $("winHeight"),
|
|
74
|
+
winHeightOut: $("winHeightOut"),
|
|
75
|
+
position: $("position"),
|
|
76
|
+
openBtn: $("openBtn"),
|
|
77
|
+
closeBtn: $("closeBtn"),
|
|
78
|
+
state: $("state"),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
let state = { ...DEFAULTS };
|
|
82
|
+
let saveTimer = null;
|
|
83
|
+
let prompterOpen = false;
|
|
84
|
+
|
|
85
|
+
function snapshot() {
|
|
86
|
+
state = {
|
|
87
|
+
...state,
|
|
88
|
+
script: els.script.value,
|
|
89
|
+
speed: +els.speed.value,
|
|
90
|
+
font: +els.font.value,
|
|
91
|
+
lh: +els.lh.value,
|
|
92
|
+
ls: +els.ls.value,
|
|
93
|
+
margin: +els.margin.value,
|
|
94
|
+
color: els.color.value,
|
|
95
|
+
hl: els.hl.value,
|
|
96
|
+
bg: +els.bg.value,
|
|
97
|
+
dim: +els.dim.value,
|
|
98
|
+
stroke: +els.stroke.value,
|
|
99
|
+
mirror: els.mirror.checked,
|
|
100
|
+
bold: els.bold.checked,
|
|
101
|
+
uppercase: els.uppercase.checked,
|
|
102
|
+
showReadingLine: els.showReadingLine.checked,
|
|
103
|
+
alwaysOnTop: els.alwaysOnTop.checked,
|
|
104
|
+
clickThrough: els.clickThrough.checked,
|
|
105
|
+
winWidth: +els.winWidth.value,
|
|
106
|
+
winHeight: +els.winHeight.value,
|
|
107
|
+
position: els.position.value,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function renderOutputs() {
|
|
112
|
+
els.speedOut.value = els.speed.value;
|
|
113
|
+
els.fontOut.value = els.font.value;
|
|
114
|
+
els.lhOut.value = (+els.lh.value).toFixed(2);
|
|
115
|
+
els.lsOut.value = (+els.ls.value).toFixed(1);
|
|
116
|
+
els.marginOut.value = els.margin.value;
|
|
117
|
+
els.bgOut.value = els.bg.value;
|
|
118
|
+
els.dimOut.value = els.dim.value;
|
|
119
|
+
els.strokeOut.value = (+els.stroke.value).toFixed(1);
|
|
120
|
+
els.winWidthOut.value = els.winWidth.value;
|
|
121
|
+
els.winHeightOut.value = els.winHeight.value;
|
|
122
|
+
updateMeta();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function updateMeta() {
|
|
126
|
+
const t = els.script.value || "";
|
|
127
|
+
const words = t.trim() ? t.trim().split(/\s+/).length : 0;
|
|
128
|
+
const minutes = state.speed > 0 ? Math.max(0.1, words / (state.speed * 1.4)) : 0;
|
|
129
|
+
els.scriptMeta.textContent = `${words} word${words === 1 ? "" : "s"} · ${t.length} chars · ≈ ${minutes.toFixed(1)} min @ current size`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function fillForm() {
|
|
133
|
+
els.script.value = state.script || "";
|
|
134
|
+
els.speed.value = state.speed;
|
|
135
|
+
els.font.value = state.font;
|
|
136
|
+
els.lh.value = state.lh;
|
|
137
|
+
els.ls.value = state.ls;
|
|
138
|
+
els.margin.value = state.margin;
|
|
139
|
+
els.color.value = state.color;
|
|
140
|
+
els.hl.value = state.hl;
|
|
141
|
+
els.bg.value = state.bg;
|
|
142
|
+
els.dim.value = state.dim;
|
|
143
|
+
els.stroke.value = state.stroke;
|
|
144
|
+
els.mirror.checked = state.mirror;
|
|
145
|
+
els.bold.checked = state.bold;
|
|
146
|
+
els.uppercase.checked = state.uppercase;
|
|
147
|
+
els.showReadingLine.checked = state.showReadingLine;
|
|
148
|
+
els.alwaysOnTop.checked = state.alwaysOnTop;
|
|
149
|
+
els.clickThrough.checked = state.clickThrough;
|
|
150
|
+
els.winWidth.value = state.winWidth;
|
|
151
|
+
els.winHeight.value = state.winHeight;
|
|
152
|
+
els.position.value = state.position;
|
|
153
|
+
renderOutputs();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function queueSave() {
|
|
157
|
+
clearTimeout(saveTimer);
|
|
158
|
+
saveTimer = setTimeout(() => api.saveSettings(state), 250);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function pushToPrompter() {
|
|
162
|
+
if (!prompterOpen) return;
|
|
163
|
+
await api.sendSettings(state);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function applyClickThrough(enabled) {
|
|
167
|
+
if (!prompterOpen) return;
|
|
168
|
+
api.setClickThrough(enabled);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function applyAlwaysOnTop(enabled) {
|
|
172
|
+
if (!prompterOpen) return;
|
|
173
|
+
api.setAlwaysOnTop(enabled);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function applyPosition() {
|
|
177
|
+
if (!prompterOpen) return;
|
|
178
|
+
api.setBounds({ width: state.winWidth, height: state.winHeight });
|
|
179
|
+
api.sendCommand({ type: "position", value: state.position });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function openPrompter() {
|
|
183
|
+
snapshot();
|
|
184
|
+
await api.openPrompter({
|
|
185
|
+
windowWidth: state.winWidth,
|
|
186
|
+
windowHeight: state.winHeight,
|
|
187
|
+
position: state.position,
|
|
188
|
+
alwaysOnTop: state.alwaysOnTop,
|
|
189
|
+
});
|
|
190
|
+
prompterOpen = true;
|
|
191
|
+
els.openBtn.disabled = true;
|
|
192
|
+
els.closeBtn.disabled = false;
|
|
193
|
+
setState("prompter open", "ok");
|
|
194
|
+
await pushToPrompter();
|
|
195
|
+
if (state.clickThrough) applyClickThrough(true);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function closePrompter() {
|
|
199
|
+
await api.closePrompter();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function setState(text, kind) {
|
|
203
|
+
els.state.textContent = text;
|
|
204
|
+
els.state.classList.remove("ok", "warn");
|
|
205
|
+
if (kind) els.state.classList.add(kind);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function wire() {
|
|
209
|
+
const inputs = [
|
|
210
|
+
"speed",
|
|
211
|
+
"font",
|
|
212
|
+
"lh",
|
|
213
|
+
"ls",
|
|
214
|
+
"margin",
|
|
215
|
+
"color",
|
|
216
|
+
"hl",
|
|
217
|
+
"bg",
|
|
218
|
+
"dim",
|
|
219
|
+
"stroke",
|
|
220
|
+
"winWidth",
|
|
221
|
+
"winHeight",
|
|
222
|
+
"position",
|
|
223
|
+
];
|
|
224
|
+
for (const k of inputs) {
|
|
225
|
+
els[k].addEventListener("input", () => {
|
|
226
|
+
snapshot();
|
|
227
|
+
renderOutputs();
|
|
228
|
+
queueSave();
|
|
229
|
+
pushToPrompter();
|
|
230
|
+
if (k === "winWidth" || k === "winHeight" || k === "position") {
|
|
231
|
+
applyPosition();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const toggles = [
|
|
236
|
+
"mirror",
|
|
237
|
+
"bold",
|
|
238
|
+
"uppercase",
|
|
239
|
+
"showReadingLine",
|
|
240
|
+
"alwaysOnTop",
|
|
241
|
+
"clickThrough",
|
|
242
|
+
];
|
|
243
|
+
for (const k of toggles) {
|
|
244
|
+
els[k].addEventListener("change", () => {
|
|
245
|
+
snapshot();
|
|
246
|
+
queueSave();
|
|
247
|
+
pushToPrompter();
|
|
248
|
+
if (k === "clickThrough") applyClickThrough(els.clickThrough.checked);
|
|
249
|
+
if (k === "alwaysOnTop") applyAlwaysOnTop(els.alwaysOnTop.checked);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
els.script.addEventListener("input", () => {
|
|
253
|
+
snapshot();
|
|
254
|
+
renderOutputs();
|
|
255
|
+
queueSave();
|
|
256
|
+
pushToPrompter();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
els.openBtn.addEventListener("click", openPrompter);
|
|
260
|
+
els.closeBtn.addEventListener("click", closePrompter);
|
|
261
|
+
|
|
262
|
+
els.loadSample.addEventListener("click", () => {
|
|
263
|
+
els.script.value = SAMPLE;
|
|
264
|
+
snapshot();
|
|
265
|
+
renderOutputs();
|
|
266
|
+
queueSave();
|
|
267
|
+
pushToPrompter();
|
|
268
|
+
});
|
|
269
|
+
els.clearScript.addEventListener("click", () => {
|
|
270
|
+
els.script.value = "";
|
|
271
|
+
snapshot();
|
|
272
|
+
renderOutputs();
|
|
273
|
+
queueSave();
|
|
274
|
+
pushToPrompter();
|
|
275
|
+
els.script.focus();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
api.onPrompterClosed(() => {
|
|
279
|
+
prompterOpen = false;
|
|
280
|
+
els.openBtn.disabled = false;
|
|
281
|
+
els.closeBtn.disabled = true;
|
|
282
|
+
setState("prompter closed");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
api.onPrompterState((s) => {
|
|
286
|
+
if (!s || typeof s !== "object") return;
|
|
287
|
+
let changed = false;
|
|
288
|
+
if (s.locked !== undefined && s.locked !== els.clickThrough.checked) {
|
|
289
|
+
els.clickThrough.checked = !!s.locked;
|
|
290
|
+
state.clickThrough = !!s.locked;
|
|
291
|
+
changed = true;
|
|
292
|
+
}
|
|
293
|
+
if (typeof s.speed === "number" && s.speed !== +els.speed.value) {
|
|
294
|
+
els.speed.value = s.speed;
|
|
295
|
+
state.speed = s.speed;
|
|
296
|
+
els.speedOut.value = s.speed;
|
|
297
|
+
changed = true;
|
|
298
|
+
}
|
|
299
|
+
if (changed) {
|
|
300
|
+
queueSave();
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
(async function init() {
|
|
306
|
+
const saved = await api.loadSettings();
|
|
307
|
+
if (saved && typeof saved === "object") {
|
|
308
|
+
state = { ...DEFAULTS, ...saved };
|
|
309
|
+
}
|
|
310
|
+
fillForm();
|
|
311
|
+
wire();
|
|
312
|
+
setState("ready");
|
|
313
|
+
})();
|