@sealcode/sealgen 0.17.13 → 0.17.15

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.
@@ -1,7 +1,7 @@
1
1
  import { Builder } from "./builder.js";
2
2
  export declare const COLORS_TS_PATH = "src/back/colors.ts";
3
3
  export declare const COLORS_CSS_PATH = "src/colors.css";
4
- export declare const COLORS_HTML_PATH = "src/colors.html";
4
+ export declare const COLORS_HTML_PATH = "public/dist/colors.html";
5
5
  export declare class ColorsBuilder extends Builder {
6
6
  getName(): string;
7
7
  ownsFile(file_path: string): boolean;
@@ -1 +1,2 @@
1
- export declare function make_notifier(port: number): (message: string) => void;
1
+ export declare const APP_BACK_ALIVE_SIGNAL = "app-back-alive";
2
+ export declare function make_notifier(port: number, app_port: number): (message: string) => void;
package/lib/build.js CHANGED
@@ -88,7 +88,7 @@ async function build(watch) {
88
88
  if (watch) {
89
89
  const watcher = chokidar.watch("src", { ignoreInitial: true });
90
90
  const port = await get_notifier_port();
91
- const notifier = make_notifier(port);
91
+ const notifier = make_notifier(port, 8080);
92
92
  await write_notifier_config(watch, port);
93
93
  watcher.on("all", (_, file_path) => {
94
94
  console.log("Detected a change!", file_path);
@@ -6,9 +6,10 @@ import { colord } from "colord";
6
6
  import { formatWithPrettier } from "../utils/prettier.js";
7
7
  import { Builder } from "./builder.js";
8
8
  import { exec } from "../utils/exec.js";
9
+ import { APP_BACK_ALIVE_SIGNAL } from "../notifier.js";
9
10
  const COLORS_TS_PATH = "src/back/colors.ts";
10
11
  const COLORS_CSS_PATH = "src/colors.css";
11
- const COLORS_HTML_PATH = "src/colors.html";
12
+ const COLORS_HTML_PATH = "public/dist/colors.html";
12
13
  class ColorsBuilder extends Builder {
13
14
  getName() {
14
15
  return "colors";
@@ -72,15 +73,18 @@ class ColorsBuilder extends Builder {
72
73
  group_name,
73
74
  is_main = false
74
75
  }) {
76
+ const c = colord(color);
75
77
  return (
76
78
  /* HTML */
77
79
  `<div
78
- class="color-box ${colord(color).isDark() ? "is-dark" : ""} ${is_main ? "is-main" : ""}"
80
+ class="color-box ${c.isDark() ? "is-dark" : ""} ${is_main ? "is-main" : ""} ${c.toHsl().l > 90 ? "is-almost-white" : ""}"
79
81
  style="background-color: ${color}; order: ${parseInt(shade_no)}"
80
82
  data-color-name="${name}"
81
83
  data-color-group-name="${group_name}"
82
84
  data-color-shade-number="${shade_no}"
83
- ></div>`
85
+ >
86
+ <span class="hex">${c.toHex()}</span>
87
+ </div>`
84
88
  );
85
89
  }
86
90
  for (const group_name of Object.keys(color_groups).sort(
@@ -101,13 +105,13 @@ class ColorsBuilder extends Builder {
101
105
  group_name
102
106
  });
103
107
  color_info.shades.forEach((shaded_color, index) => {
108
+ const shade_no = index.toString().padStart(2, "0");
109
+ css_colors += `--color-${group_name}-${color_name}-${shade_no}: ${shaded_color};`;
104
110
  if (Math.abs(
105
111
  colord(shaded_color).toHsl().l - colord(main_color).toHsl().l
106
112
  ) < 10) {
107
113
  return;
108
114
  }
109
- const shade_no = index.toString().padStart(2, "0");
110
- css_colors += `--color-${group_name}-${color_name}-${shade_no}: ${shaded_color};`;
111
115
  html_colors += html_color_box({
112
116
  color: shaded_color,
113
117
  shade_no,
@@ -126,11 +130,28 @@ class ColorsBuilder extends Builder {
126
130
  }`,
127
131
  "css"
128
132
  );
133
+ function makeDemoPair(fg, bg, text, classname = "") {
134
+ return (
135
+ /* HTML */
136
+ ` <div
137
+ class="pair ${classname}"
138
+ data-fg="${fg}"
139
+ data-bg="${bg}"
140
+ style="background-color: ${bg}; color: ${fg}"
141
+ >
142
+ <span>${text}</span>
143
+ </div>`
144
+ );
145
+ }
129
146
  const html = await formatWithPrettier(
130
147
  /* HTML */
131
148
  `<!DOCTYPE html>
132
149
  <html>
133
150
  <head>
151
+ <title>Color palettes</title>
152
+ <style>
153
+ ${css}
154
+ </style>
134
155
  <style>
135
156
  * {
136
157
  font-family: sans-serif;
@@ -151,6 +172,10 @@ class ColorsBuilder extends Builder {
151
172
  &:hover {
152
173
  transform: scale(1.1);
153
174
  z-index: 2;
175
+
176
+ .hex {
177
+ visibility: visible;
178
+ }
154
179
  }
155
180
 
156
181
  &.is-main {
@@ -159,22 +184,42 @@ class ColorsBuilder extends Builder {
159
184
  margin-top: -8px;
160
185
  box-shadow: 0px 0px 6px 2px white;
161
186
  z-index: 1;
187
+
188
+ &:before {
189
+ font-weight: bold;
190
+ }
191
+ }
192
+
193
+ &.is-almost-white.is-main {
194
+ box-shadow: 0px 0px 6px 1px #00000078;
195
+ }
196
+
197
+ .hex {
198
+ padding: 8px;
199
+ font-family: Menlo, Consolas, Monaco,
200
+ Liberation Mono, Lucida Console,
201
+ monospace;
202
+ opacity: 0.5;
203
+ visibility: hidden;
204
+ }
205
+
206
+ &.is-dark .hex {
207
+ color: white;
162
208
  }
163
209
  }
164
210
 
165
- .color-box:after {
211
+ .color-box:before {
166
212
  content: attr(data-color-shade-number);
167
213
  box-sizing: border-box;
168
214
  display: block;
169
215
  color: black;
170
216
  font-size: 14px;
171
217
  width: 100%;
172
- height: 100%;
173
218
  padding: 10px;
174
219
  opacity: 0.3;
175
220
  }
176
221
 
177
- .color-box.is-dark:after {
222
+ .color-box.is-dark:before {
178
223
  color: white;
179
224
  }
180
225
 
@@ -200,22 +245,88 @@ class ColorsBuilder extends Builder {
200
245
  animation-timing-function: ease-out;
201
246
  z-index: 3;
202
247
  }
248
+
249
+ .container {
250
+ width: 100vw;
251
+ display: flex;
252
+ flex-flow: row wrap;
253
+ gap: 16px;
254
+
255
+ @media (max-width: 1400px) {
256
+ .demo {
257
+ order: 1;
258
+ }
259
+ .pallete {
260
+ order: 2;
261
+ }
262
+ }
263
+
264
+ .canvas {
265
+ background-color: var(--color-brand-canvas);
266
+ display: flex;
267
+ flex-flow: column;
268
+ border: 1px dashed gray;
269
+ color: gray;
270
+
271
+ gap: 8px;
272
+ padding: 8px;
273
+
274
+ & > * {
275
+ padding: 8px;
276
+ color: black;
277
+ }
278
+
279
+ .pair.single span {
280
+ opacity: 0.5;
281
+ }
282
+ }
283
+ }
203
284
  </style>
204
285
  </head>
205
286
  <body>
206
- ${html_colors}
287
+ <div class="container">
288
+ <div class="pallete">${html_colors}</div>
289
+ <div class="demo">
290
+ <div class="canvas">
291
+ <span>canvas</span>
292
+ ${makeDemoPair(
293
+ "var(--color-brand-text-fg)",
294
+ "var(--color-brand-text-bg)",
295
+ "text-fg on text-bg"
296
+ )}
297
+ ${makeDemoPair(
298
+ "var(--color-brand-text-accent)",
299
+ "var(--color-brand-text-bg)",
300
+ "text-accent on text-bg"
301
+ )}
302
+ ${makeDemoPair(
303
+ "",
304
+ "var(--color-brand-accent)",
305
+ "accent",
306
+ "single"
307
+ )}
308
+ ${makeDemoPair(
309
+ "",
310
+ "var(--color-brand-accent2)",
311
+ "accent2",
312
+ "single"
313
+ )}
314
+ ${makeDemoPair(
315
+ "var(--color-brand-text-on-accent)",
316
+ "var(--color-brand-accent)",
317
+ "text-on-accent on accent"
318
+ )}
319
+ ${makeDemoPair(
320
+ "var(--color-brand-text-on-accent2)",
321
+ "var(--color-brand-accent2)",
322
+ "text-on-accent2 on accent2"
323
+ )}
324
+ </div>
325
+ </div>
326
+ </div>
207
327
  </body>
208
328
  <script>
209
- document.addEventListener("click", (event) => {
210
- navigator.clipboard.writeText(
211
- \`var(--color-\${event.target.getAttribute(
212
- "data-color-group-name"
213
- )}-\${event.target.getAttribute(
214
- "data-color-name"
215
- )}-\${event.target.getAttribute(
216
- "data-color-shade-number"
217
- )})\`
218
- );
329
+ function pop_toast() {
219
330
  const toast = document.createElement("div");
220
331
  document.body.appendChild(toast);
221
332
  toast.classList.add("toast");
@@ -234,8 +345,62 @@ class ColorsBuilder extends Builder {
234
345
  toast.addEventListener("animationend", () => {
235
346
  toast.remove();
236
347
  });
348
+ }
349
+
350
+ document.addEventListener("click", (event) => {
351
+ const box = event.target.closest(".color-box");
352
+ if (box) {
353
+ const css_var = box.classList.contains(
354
+ "is-main"
355
+ )
356
+ ? \`var(--color-\${box.getAttribute(
357
+ "data-color-group-name"
358
+ )}-\${box.getAttribute(
359
+ "data-color-name"
360
+ )})\`
361
+ : \`var(--color-\${box.getAttribute(
362
+ "data-color-group-name"
363
+ )}-\${box.getAttribute(
364
+ "data-color-name"
365
+ )}-\${box.getAttribute(
366
+ "data-color-shade-number"
367
+ )})\`;
368
+ navigator.clipboard.writeText(css_var);
369
+ pop_toast();
370
+ }
371
+ const pair = event.target.closest(".pair");
372
+ if (pair) {
373
+ let to_copy = "";
374
+ if (pair.classList.contains("single")) {
375
+ to_copy = pair.getAttribute("data-bg");
376
+ } else {
377
+ to_copy = \`background-color: \${pair.getAttribute(
378
+ "data-bg"
379
+ )};\\ncolor: \${pair.getAttribute(
380
+ "data-fg"
381
+ )};\`;
382
+ }
383
+ navigator.clipboard.writeText(to_copy);
384
+ pop_toast();
385
+ }
237
386
  });
238
387
  </script>
388
+ <script>
389
+ (async function () {
390
+ const response = await (
391
+ await fetch("/dist/notifier.json")
392
+ ).json();
393
+ const ws = new WebSocket(
394
+ \`http://localhost:\${response.port}\`
395
+ );
396
+ ws.addEventListener("message", (event) => {
397
+ console.log(event);
398
+ if (event.data == "${APP_BACK_ALIVE_SIGNAL}") {
399
+ document.location = document.location; // refresh
400
+ }
401
+ });
402
+ })();
403
+ </script>
239
404
  </html>`,
240
405
  "html"
241
406
  );
package/lib/notifier.js CHANGED
@@ -1,5 +1,72 @@
1
1
  import { WebSocketServer } from "ws";
2
- function make_notifier(port) {
2
+ const APP_DOWN_ERROR_MESSAGE = "App is currently down";
3
+ const APP_BACK_ALIVE_SIGNAL = "app-back-alive";
4
+ const sleep = (time) => new Promise((resolve) => {
5
+ setTimeout(resolve, time);
6
+ });
7
+ async function get_status(app_port) {
8
+ const r = await fetch(`http://127.0.0.1:${app_port}/status.json`);
9
+ return await r.json();
10
+ }
11
+ async function wait_for_run_id_to_change(app_port) {
12
+ let first_timestamp;
13
+ try {
14
+ const { started_at } = await get_status(app_port);
15
+ first_timestamp = started_at;
16
+ } catch (e) {
17
+ await wait_for_app_to_be_stable(app_port);
18
+ return;
19
+ }
20
+ if (!first_timestamp) {
21
+ throw new Error(APP_DOWN_ERROR_MESSAGE);
22
+ }
23
+ while (true) {
24
+ const { started_at } = await get_status(app_port).catch(() => ({
25
+ started_at: first_timestamp
26
+ }));
27
+ if (started_at !== first_timestamp) {
28
+ return;
29
+ }
30
+ await sleep(100);
31
+ }
32
+ }
33
+ async function wait_for_app_to_be_stable(app_port, n = 3) {
34
+ console.log("Waiting for app to be stable....");
35
+ let counter = 0;
36
+ while (true) {
37
+ console.debug("notifier.ts:53");
38
+ const { status } = await get_status(app_port).catch(() => ({
39
+ status: "down"
40
+ }));
41
+ if (status == "running") {
42
+ console.log(counter);
43
+ counter++;
44
+ } else {
45
+ counter = 0;
46
+ }
47
+ if (counter == n) {
48
+ return;
49
+ }
50
+ await sleep(100);
51
+ }
52
+ }
53
+ let restart_promise = null;
54
+ async function wait_for_app_restart(app_port) {
55
+ if (restart_promise) {
56
+ return restart_promise;
57
+ }
58
+ try {
59
+ restart_promise = wait_for_run_id_to_change(app_port);
60
+ await restart_promise;
61
+ } catch (e) {
62
+ if (e.message !== APP_DOWN_ERROR_MESSAGE) {
63
+ throw e;
64
+ }
65
+ }
66
+ restart_promise = null;
67
+ await wait_for_app_to_be_stable(app_port);
68
+ }
69
+ function make_notifier(port, app_port) {
3
70
  const server = new WebSocketServer({
4
71
  port
5
72
  });
@@ -15,9 +82,13 @@ function make_notifier(port) {
15
82
  );
16
83
  return function notify(message) {
17
84
  sockets.forEach((s) => s.send(message));
85
+ void wait_for_app_restart(app_port).then(() => {
86
+ sockets.forEach((s) => s.send(APP_BACK_ALIVE_SIGNAL));
87
+ });
18
88
  };
19
89
  }
20
90
  export {
91
+ APP_BACK_ALIVE_SIGNAL,
21
92
  make_notifier
22
93
  };
23
94
  //# sourceMappingURL=notifier.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.17.13",
3
+ "version": "0.17.15",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
package/src/build.ts CHANGED
@@ -96,7 +96,7 @@ async function build(watch: boolean): Promise<void> {
96
96
  if (watch) {
97
97
  const watcher = chokidar.watch("src", { ignoreInitial: true });
98
98
  const port = await get_notifier_port();
99
- const notifier = make_notifier(port);
99
+ const notifier = make_notifier(port, 8080); // TODO: parse the app config to get the actual port
100
100
  await write_notifier_config(watch, port);
101
101
  watcher.on("all", (_, file_path) => {
102
102
  console.log("Detected a change!", file_path);
@@ -10,9 +10,11 @@ import { formatWithPrettier } from "../utils/prettier.js";
10
10
  import { Builder } from "./builder.js";
11
11
  import { exec } from "../utils/exec.js";
12
12
 
13
+ import { APP_BACK_ALIVE_SIGNAL } from "../notifier.js";
14
+
13
15
  export const COLORS_TS_PATH = "src/back/colors.ts"; // keeping it in src/back instead of just src to make typescript hints work there
14
16
  export const COLORS_CSS_PATH = "src/colors.css";
15
- export const COLORS_HTML_PATH = "src/colors.html";
17
+ export const COLORS_HTML_PATH = "public/dist/colors.html";
16
18
 
17
19
  export class ColorsBuilder extends Builder {
18
20
  getName(): string {
@@ -88,15 +90,18 @@ export class ColorsBuilder extends Builder {
88
90
  group_name: string;
89
91
  is_main?: boolean;
90
92
  }) {
93
+ const c = colord(color);
91
94
  return /* HTML */ `<div
92
- class="color-box ${colord(color).isDark()
93
- ? "is-dark"
94
- : ""} ${is_main ? "is-main" : ""}"
95
+ class="color-box ${c.isDark() ? "is-dark" : ""} ${is_main
96
+ ? "is-main"
97
+ : ""} ${c.toHsl().l > 90 ? "is-almost-white" : ""}"
95
98
  style="background-color: ${color}; order: ${parseInt(shade_no)}"
96
99
  data-color-name="${name}"
97
100
  data-color-group-name="${group_name}"
98
101
  data-color-shade-number="${shade_no}"
99
- ></div>`;
102
+ >
103
+ <span class="hex">${c.toHex()}</span>
104
+ </div>`;
100
105
  }
101
106
 
102
107
  for (const group_name of Object.keys(color_groups).sort((key) =>
@@ -119,16 +124,17 @@ export class ColorsBuilder extends Builder {
119
124
  group_name,
120
125
  });
121
126
  color_info.shades.forEach((shaded_color, index) => {
127
+ const shade_no = index.toString().padStart(2, "0");
128
+ css_colors += `--color-${group_name}-${color_name}-${shade_no}: ${shaded_color};`;
122
129
  if (
123
130
  Math.abs(
124
131
  colord(shaded_color).toHsl().l -
125
132
  colord(main_color).toHsl().l
126
133
  ) < 10
127
134
  ) {
135
+ // this color is already in HTML as the main color, skipping adding it to html to avoid duplication
128
136
  return;
129
137
  }
130
- const shade_no = index.toString().padStart(2, "0");
131
- css_colors += `--color-${group_name}-${color_name}-${shade_no}: ${shaded_color};`;
132
138
  html_colors += html_color_box({
133
139
  color: shaded_color,
134
140
  shade_no,
@@ -149,10 +155,30 @@ export class ColorsBuilder extends Builder {
149
155
  "css"
150
156
  );
151
157
 
158
+ function makeDemoPair(
159
+ fg: string,
160
+ bg: string,
161
+ text: string,
162
+ classname = ""
163
+ ) {
164
+ return /* HTML */ ` <div
165
+ class="pair ${classname}"
166
+ data-fg="${fg}"
167
+ data-bg="${bg}"
168
+ style="background-color: ${bg}; color: ${fg}"
169
+ >
170
+ <span>${text}</span>
171
+ </div>`;
172
+ }
173
+
152
174
  const html = await formatWithPrettier(
153
175
  /* HTML */ `<!DOCTYPE html>
154
176
  <html>
155
177
  <head>
178
+ <title>Color palettes</title>
179
+ <style>
180
+ ${css}
181
+ </style>
156
182
  <style>
157
183
  * {
158
184
  font-family: sans-serif;
@@ -173,6 +199,10 @@ export class ColorsBuilder extends Builder {
173
199
  &:hover {
174
200
  transform: scale(1.1);
175
201
  z-index: 2;
202
+
203
+ .hex {
204
+ visibility: visible;
205
+ }
176
206
  }
177
207
 
178
208
  &.is-main {
@@ -181,22 +211,42 @@ export class ColorsBuilder extends Builder {
181
211
  margin-top: -8px;
182
212
  box-shadow: 0px 0px 6px 2px white;
183
213
  z-index: 1;
214
+
215
+ &:before {
216
+ font-weight: bold;
217
+ }
218
+ }
219
+
220
+ &.is-almost-white.is-main {
221
+ box-shadow: 0px 0px 6px 1px #00000078;
222
+ }
223
+
224
+ .hex {
225
+ padding: 8px;
226
+ font-family: Menlo, Consolas, Monaco,
227
+ Liberation Mono, Lucida Console,
228
+ monospace;
229
+ opacity: 0.5;
230
+ visibility: hidden;
231
+ }
232
+
233
+ &.is-dark .hex {
234
+ color: white;
184
235
  }
185
236
  }
186
237
 
187
- .color-box:after {
238
+ .color-box:before {
188
239
  content: attr(data-color-shade-number);
189
240
  box-sizing: border-box;
190
241
  display: block;
191
242
  color: black;
192
243
  font-size: 14px;
193
244
  width: 100%;
194
- height: 100%;
195
245
  padding: 10px;
196
246
  opacity: 0.3;
197
247
  }
198
248
 
199
- .color-box.is-dark:after {
249
+ .color-box.is-dark:before {
200
250
  color: white;
201
251
  }
202
252
 
@@ -222,22 +272,88 @@ export class ColorsBuilder extends Builder {
222
272
  animation-timing-function: ease-out;
223
273
  z-index: 3;
224
274
  }
275
+
276
+ .container {
277
+ width: 100vw;
278
+ display: flex;
279
+ flex-flow: row wrap;
280
+ gap: 16px;
281
+
282
+ @media (max-width: 1400px) {
283
+ .demo {
284
+ order: 1;
285
+ }
286
+ .pallete {
287
+ order: 2;
288
+ }
289
+ }
290
+
291
+ .canvas {
292
+ background-color: var(--color-brand-canvas);
293
+ display: flex;
294
+ flex-flow: column;
295
+ border: 1px dashed gray;
296
+ color: gray;
297
+
298
+ gap: 8px;
299
+ padding: 8px;
300
+
301
+ & > * {
302
+ padding: 8px;
303
+ color: black;
304
+ }
305
+
306
+ .pair.single span {
307
+ opacity: 0.5;
308
+ }
309
+ }
310
+ }
225
311
  </style>
226
312
  </head>
227
313
  <body>
228
- ${html_colors}
314
+ <div class="container">
315
+ <div class="pallete">${html_colors}</div>
316
+ <div class="demo">
317
+ <div class="canvas">
318
+ <span>canvas</span>
319
+ ${makeDemoPair(
320
+ "var(--color-brand-text-fg)",
321
+ "var(--color-brand-text-bg)",
322
+ "text-fg on text-bg"
323
+ )}
324
+ ${makeDemoPair(
325
+ "var(--color-brand-text-accent)",
326
+ "var(--color-brand-text-bg)",
327
+ "text-accent on text-bg"
328
+ )}
329
+ ${makeDemoPair(
330
+ "",
331
+ "var(--color-brand-accent)",
332
+ "accent",
333
+ "single"
334
+ )}
335
+ ${makeDemoPair(
336
+ "",
337
+ "var(--color-brand-accent2)",
338
+ "accent2",
339
+ "single"
340
+ )}
341
+ ${makeDemoPair(
342
+ "var(--color-brand-text-on-accent)",
343
+ "var(--color-brand-accent)",
344
+ "text-on-accent on accent"
345
+ )}
346
+ ${makeDemoPair(
347
+ "var(--color-brand-text-on-accent2)",
348
+ "var(--color-brand-accent2)",
349
+ "text-on-accent2 on accent2"
350
+ )}
351
+ </div>
352
+ </div>
353
+ </div>
229
354
  </body>
230
355
  <script>
231
- document.addEventListener("click", (event) => {
232
- navigator.clipboard.writeText(
233
- \`var(--color-\${event.target.getAttribute(
234
- "data-color-group-name"
235
- )}-\${event.target.getAttribute(
236
- "data-color-name"
237
- )}-\${event.target.getAttribute(
238
- "data-color-shade-number"
239
- )})\`
240
- );
356
+ function pop_toast() {
241
357
  const toast = document.createElement("div");
242
358
  document.body.appendChild(toast);
243
359
  toast.classList.add("toast");
@@ -256,8 +372,62 @@ export class ColorsBuilder extends Builder {
256
372
  toast.addEventListener("animationend", () => {
257
373
  toast.remove();
258
374
  });
375
+ }
376
+
377
+ document.addEventListener("click", (event) => {
378
+ const box = event.target.closest(".color-box");
379
+ if (box) {
380
+ const css_var = box.classList.contains(
381
+ "is-main"
382
+ )
383
+ ? \`var(--color-\${box.getAttribute(
384
+ "data-color-group-name"
385
+ )}-\${box.getAttribute(
386
+ "data-color-name"
387
+ )})\`
388
+ : \`var(--color-\${box.getAttribute(
389
+ "data-color-group-name"
390
+ )}-\${box.getAttribute(
391
+ "data-color-name"
392
+ )}-\${box.getAttribute(
393
+ "data-color-shade-number"
394
+ )})\`;
395
+ navigator.clipboard.writeText(css_var);
396
+ pop_toast();
397
+ }
398
+ const pair = event.target.closest(".pair");
399
+ if (pair) {
400
+ let to_copy = "";
401
+ if (pair.classList.contains("single")) {
402
+ to_copy = pair.getAttribute("data-bg");
403
+ } else {
404
+ to_copy = \`background-color: \${pair.getAttribute(
405
+ "data-bg"
406
+ )};\\ncolor: \${pair.getAttribute(
407
+ "data-fg"
408
+ )};\`;
409
+ }
410
+ navigator.clipboard.writeText(to_copy);
411
+ pop_toast();
412
+ }
259
413
  });
260
414
  </script>
415
+ <script>
416
+ (async function () {
417
+ const response = await (
418
+ await fetch("/dist/notifier.json")
419
+ ).json();
420
+ const ws = new WebSocket(
421
+ \`http://localhost:\${response.port}\`
422
+ );
423
+ ws.addEventListener("message", (event) => {
424
+ console.log(event);
425
+ if (event.data == "${APP_BACK_ALIVE_SIGNAL}") {
426
+ document.location = document.location; // refresh
427
+ }
428
+ });
429
+ })();
430
+ </script>
261
431
  </html>`,
262
432
  "html"
263
433
  );
package/src/notifier.ts CHANGED
@@ -1,6 +1,95 @@
1
1
  import { WebSocketServer, WebSocket } from "ws";
2
2
 
3
- export function make_notifier(port: number) {
3
+ const APP_DOWN_ERROR_MESSAGE = "App is currently down";
4
+ export const APP_BACK_ALIVE_SIGNAL = "app-back-alive";
5
+
6
+ const sleep = (time: number) =>
7
+ new Promise((resolve) => {
8
+ setTimeout(resolve, time);
9
+ });
10
+
11
+ async function get_status(
12
+ app_port: number
13
+ ): Promise<{ started_at: number; status: string }> {
14
+ const r = await fetch(`http://127.0.0.1:${app_port}/status.json`);
15
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
16
+ return (await r.json()) as { started_at: number; status: string };
17
+ }
18
+
19
+ async function wait_for_run_id_to_change(app_port: number) {
20
+ let first_timestamp: number;
21
+ try {
22
+ const { started_at } = await get_status(app_port);
23
+ first_timestamp = started_at;
24
+ } catch (e) {
25
+ await wait_for_app_to_be_stable(app_port);
26
+ return;
27
+ }
28
+
29
+ if (!first_timestamp) {
30
+ throw new Error(APP_DOWN_ERROR_MESSAGE);
31
+ }
32
+
33
+ // eslint-disable-next-line no-constant-condition
34
+ while (true) {
35
+ // eslint-disable-next-line no-await-in-loop
36
+ const { started_at } = await get_status(app_port).catch(() => ({
37
+ started_at: first_timestamp,
38
+ }));
39
+ if (started_at !== first_timestamp) {
40
+ return;
41
+ }
42
+ // eslint-disable-next-line no-await-in-loop
43
+ await sleep(100);
44
+ }
45
+ }
46
+
47
+ async function wait_for_app_to_be_stable(app_port: number, n = 3) {
48
+ // eslint-disable-next-line no-console
49
+ console.log("Waiting for app to be stable....");
50
+ let counter = 0;
51
+ // eslint-disable-next-line no-constant-condition
52
+ while (true) {
53
+ console.debug("notifier.ts:53");
54
+ // eslint-disable-next-line no-await-in-loop
55
+ const { status } = await get_status(app_port).catch(() => ({
56
+ status: "down",
57
+ }));
58
+ if (status == "running") {
59
+ // eslint-disable-next-line no-console
60
+ console.log(counter);
61
+ counter++;
62
+ } else {
63
+ counter = 0;
64
+ }
65
+ if (counter == n) {
66
+ return;
67
+ }
68
+ // eslint-disable-next-line no-await-in-loop
69
+ await sleep(100);
70
+ }
71
+ }
72
+
73
+ let restart_promise: Promise<void> | null = null;
74
+
75
+ async function wait_for_app_restart(app_port: number) {
76
+ if (restart_promise) {
77
+ return restart_promise;
78
+ }
79
+ try {
80
+ restart_promise = wait_for_run_id_to_change(app_port);
81
+ await restart_promise;
82
+ } catch (e) {
83
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
84
+ if (e.message !== APP_DOWN_ERROR_MESSAGE) {
85
+ throw e;
86
+ }
87
+ }
88
+ restart_promise = null;
89
+ await wait_for_app_to_be_stable(app_port);
90
+ }
91
+
92
+ export function make_notifier(port: number, app_port: number) {
4
93
  const server = new WebSocketServer({
5
94
  port,
6
95
  });
@@ -20,5 +109,8 @@ export function make_notifier(port: number) {
20
109
  );
21
110
  return function notify(message: string) {
22
111
  sockets.forEach((s) => s.send(message));
112
+ void wait_for_app_restart(app_port).then(() => {
113
+ sockets.forEach((s) => s.send(APP_BACK_ALIVE_SIGNAL));
114
+ });
23
115
  };
24
116
  }