@seedtactics/insight-client 17.0.1-beta.12 → 17.0.1-beta.13

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.
@@ -33,36 +33,90 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
  import { ServerEvent } from "./api.js";
34
34
  import { JobsBackend, LogBackend } from "./backend.js";
35
35
  import { fmsInformation } from "./server-settings.js";
36
- import { useCallback, useEffect, useRef } from "react";
37
- import { lastEventCounter, onLoadCurrentSt, onLoadLast30Jobs, onLoadLast30Log, onServerEvent, } from "../cell-status/loading.js";
36
+ import { useEffect, useRef } from "react";
37
+ import { lastEventCounter, onLoadCurrentSt, onLoadLast30Jobs, onLoadLast30Log, onLiveServerEvent, onHistoricalServerEvent, } from "../cell-status/loading.js";
38
38
  import { addDays } from "date-fns";
39
39
  import { last30SchIds } from "../cell-status/scheduled-jobs.js";
40
+ import { HashSet, OrderedMap } from "@seedtactics/immutable-collections";
40
41
  import { atom, useAtomValue, useSetAtom } from "jotai";
41
42
  const websocketReconnectingAtom = atom(false);
42
43
  export const websocketReconnecting = websocketReconnectingAtom;
44
+ // True while the websocket is open and this connection has loaded or received a current status, so
45
+ // the retained current status reflects the server. Loading the historical jobs and log does not
46
+ // affect it.
47
+ const currentStatusIsLiveRW = atom(false);
48
+ export const currentStatusIsLive = currentStatusIsLiveRW;
49
+ const websocketSessionAtom = atom({ id: 0, statusFromSocket: false });
50
+ // Only historical effects wait for HTTP catch-up. A fresh session discards its predecessor's
51
+ // unapplied buffer and reloads from lastEventCounter, which has not advanced past that history.
52
+ const bufferedLogAtom = atom(null);
53
+ // HTTP can include entries whose socket messages arrive after the response. Suppress those exact
54
+ // identities, never all counters below a maximum: live publication is not guaranteed to be ordered.
55
+ const loadedLogCountersAtom = atom(HashSet.empty());
43
56
  const errorLoadingLast30RW = atom(null);
44
- export const errorLoadingLast30 = errorLoadingLast30RW;
45
- function loadInitial(set) {
46
- const now = new Date();
47
- const thirtyDaysAgo = addDays(now, -30);
48
- const curStProm = JobsBackend.currentStatus().then((st) => set(onLoadCurrentSt, st));
49
- const jobsProm = JobsBackend.recent(thirtyDaysAgo, []).then((j) => set(onLoadLast30Jobs, j));
50
- const logProm = LogBackend.get(thirtyDaysAgo, now).then((log) => set(onLoadLast30Log, log));
51
- Promise.all([curStProm, jobsProm, logProm])
52
- .catch((e) => set(errorLoadingLast30RW, e.message ?? "Error"))
53
- .finally(() => set(websocketReconnectingAtom, false));
57
+ const errorLoadingLogRW = atom(null);
58
+ export const errorLoadingLast30 = atom((get) => get(errorLoadingLast30RW) ?? get(errorLoadingLogRW));
59
+ function isCurrentSession(get, session) {
60
+ return get(websocketSessionAtom).id === session;
61
+ }
62
+ function loadCurrentStatus(get, set, session) {
63
+ return JobsBackend.currentStatus().then((st) => {
64
+ const current = get(websocketSessionAtom);
65
+ if (current.id !== session || current.statusFromSocket)
66
+ return;
67
+ set(onLoadCurrentSt, st);
68
+ set(currentStatusIsLiveRW, true);
69
+ });
54
70
  }
55
- function loadMissed(lastCntr, schIds, set) {
71
+ function loadHistory(get, set, session, from, retryDelay = 1000) {
56
72
  const now = new Date();
57
- const curStProm = JobsBackend.currentStatus().then((st) => set(onLoadCurrentSt, st));
58
- const jobsProm = JobsBackend.recent(addDays(now, -30), schIds ? Array.from(schIds) : []).then((j) => set(onLoadLast30Jobs, j));
59
- const logProm = LogBackend.recent(lastCntr, undefined).then((log) => set(onLoadLast30Log, log));
60
- Promise.all([curStProm, jobsProm, logProm])
61
- .catch((e) => set(errorLoadingLast30RW, e.message ?? "Error"))
62
- .finally(() => set(websocketReconnectingAtom, false));
73
+ const request = from === null ? LogBackend.get(addDays(now, -30), now) : LogBackend.recent(from, undefined);
74
+ return request.then((log) => {
75
+ if (!isCurrentSession(get, session))
76
+ return;
77
+ const buffered = get(bufferedLogAtom) ?? OrderedMap.empty();
78
+ const merged = log.reduce((entries, entry) => entries.set(entry.counter, entry), buffered);
79
+ set(onLoadLast30Log, merged.valuesToAscLazySeq().toRArray());
80
+ set(loadedLogCountersAtom, HashSet.from(merged.keys()));
81
+ set(bufferedLogAtom, null);
82
+ set(errorLoadingLogRW, null);
83
+ }, (e) => {
84
+ if (!isCurrentSession(get, session))
85
+ return;
86
+ set(errorLoadingLogRW, e.message ?? "Error loading log history");
87
+ // Keep buffering and retry from the same applied position without interrupting live status.
88
+ setTimeout(() => {
89
+ if (isCurrentSession(get, session)) {
90
+ void loadHistory(get, set, session, from, Math.min(retryDelay * 2, 30000));
91
+ }
92
+ }, retryDelay);
93
+ });
94
+ }
95
+ function loadConnection(get, set, session) {
96
+ const from = get(lastEventCounter);
97
+ const schIds = get(last30SchIds);
98
+ const curStProm = loadCurrentStatus(get, set, session);
99
+ const jobsProm = JobsBackend.recent(addDays(new Date(), -30), from !== null && schIds ? Array.from(schIds) : []).then((j) => {
100
+ if (isCurrentSession(get, session))
101
+ set(onLoadLast30Jobs, j);
102
+ });
103
+ const logProm = loadHistory(get, set, session, from);
104
+ finishLoading([curStProm, jobsProm, logProm], get, set, session);
105
+ }
106
+ function finishLoading(loads, get, set, session) {
107
+ Promise.all(loads)
108
+ .catch((e) => {
109
+ if (isCurrentSession(get, session))
110
+ set(errorLoadingLast30RW, e.message ?? "Error");
111
+ })
112
+ .finally(() => {
113
+ if (isCurrentSession(get, session))
114
+ set(websocketReconnectingAtom, false);
115
+ });
63
116
  }
64
117
  class ReconnectingWebsocket {
65
118
  constructor(url) {
119
+ this.session = null;
66
120
  this.userCalledClose = false;
67
121
  this.reconnectAttempts = 0;
68
122
  this.url = url;
@@ -71,6 +125,12 @@ class ReconnectingWebsocket {
71
125
  close() {
72
126
  this.userCalledClose = true;
73
127
  this.ws?.close();
128
+ this.endSession(false);
129
+ }
130
+ endSession(reconnecting) {
131
+ const session = this.session;
132
+ this.session = null;
133
+ this.handleClose?.(session, reconnecting);
74
134
  }
75
135
  connect() {
76
136
  if (this.userCalledClose)
@@ -83,7 +143,7 @@ class ReconnectingWebsocket {
83
143
  localWs.addEventListener("open", () => {
84
144
  clearTimeout(connectTimeout);
85
145
  this.reconnectAttempts = 0;
86
- this.handleOpen?.();
146
+ this.session = this.handleOpen?.() ?? null;
87
147
  });
88
148
  localWs.addEventListener("close", () => {
89
149
  clearTimeout(connectTimeout);
@@ -91,7 +151,7 @@ class ReconnectingWebsocket {
91
151
  if (this.userCalledClose) {
92
152
  return;
93
153
  }
94
- this.handleReconnecting?.();
154
+ this.endSession(true);
95
155
  const delay = Math.min(1000 * Math.pow(1.5, this.reconnectAttempts), 30000);
96
156
  setTimeout(() => {
97
157
  this.reconnectAttempts++;
@@ -99,7 +159,7 @@ class ReconnectingWebsocket {
99
159
  }, delay);
100
160
  });
101
161
  localWs.addEventListener("message", (evt) => {
102
- this.handleMessage?.(evt);
162
+ this.handleMessage?.(evt, this.session);
103
163
  });
104
164
  // Browsers expose websocket failures as opaque events with no useful detail.
105
165
  // Reconnect handling is driven by close/open, so avoid polluting stderr by
@@ -107,25 +167,51 @@ class ReconnectingWebsocket {
107
167
  }
108
168
  }
109
169
  const onOpenAtom = atom(null, (get, set) => {
110
- const lastSeenCntr = get(lastEventCounter);
111
- const schIds = get(last30SchIds);
170
+ const session = get(websocketSessionAtom).id + 1;
171
+ set(websocketSessionAtom, { id: session, statusFromSocket: false });
112
172
  set(websocketReconnectingAtom, true);
173
+ set(currentStatusIsLiveRW, false);
113
174
  set(errorLoadingLast30RW, null);
114
- if (lastSeenCntr !== null && lastSeenCntr !== undefined) {
115
- loadMissed(lastSeenCntr, schIds, set);
116
- }
117
- else {
118
- loadInitial(set);
119
- }
175
+ set(errorLoadingLogRW, null);
176
+ set(bufferedLogAtom, OrderedMap.empty());
177
+ loadConnection(get, set, session);
178
+ return session;
120
179
  });
121
- const onMessageAtom = atom(null, (_, set, evt) => {
180
+ // Every close, including disposal, ends the socket's own session; only an unexpected close
181
+ // reconnects. A connection cannot end a newer connection's session.
182
+ const onCloseAtom = atom(null, (get, set, session, reconnecting) => {
183
+ if (reconnecting)
184
+ set(websocketReconnectingAtom, true);
185
+ if (session === null || !isCurrentSession(get, session))
186
+ return;
187
+ set(websocketSessionAtom, { id: session + 1, statusFromSocket: false });
188
+ set(currentStatusIsLiveRW, false);
189
+ set(bufferedLogAtom, null);
190
+ });
191
+ const onMessageAtom = atom(null, (get, set, evt, session) => {
192
+ if (session === null || !isCurrentSession(get, session))
193
+ return;
122
194
  const serverEvt = ServerEvent.fromJS(JSON.parse(evt.data));
123
- set(onServerEvent, { evt: serverEvt, now: new Date(), expire: true });
195
+ const eventAndTime = { evt: serverEvt, now: new Date(), expire: true };
196
+ set(onLiveServerEvent, eventAndTime);
197
+ const log = serverEvt.logEntry;
198
+ const buffered = get(bufferedLogAtom);
199
+ if (!log || !get(loadedLogCountersAtom).has(log.counter)) {
200
+ if (log && buffered !== null) {
201
+ set(bufferedLogAtom, buffered.set(log.counter, log));
202
+ }
203
+ else {
204
+ set(onHistoricalServerEvent, eventAndTime);
205
+ }
206
+ }
207
+ if (serverEvt.newCurrentStatus) {
208
+ set(websocketSessionAtom, { id: session, statusFromSocket: true });
209
+ set(currentStatusIsLiveRW, true);
210
+ }
124
211
  });
125
212
  export function WebsocketConnection() {
126
213
  const onOpen = useSetAtom(onOpenAtom);
127
- const setReconnecting = useSetAtom(websocketReconnectingAtom);
128
- const onReconnecting = useCallback(() => setReconnecting(true), [setReconnecting]);
214
+ const onClose = useSetAtom(onCloseAtom);
129
215
  const onMessage = useSetAtom(onMessageAtom);
130
216
  const fmsInfoLoadable = useAtomValue(fmsInformation);
131
217
  const websocketRef = useRef(null);
@@ -151,7 +237,7 @@ export function WebsocketConnection() {
151
237
  }
152
238
  const websocket = new ReconnectingWebsocket(uri);
153
239
  websocket.handleOpen = onOpen;
154
- websocket.handleReconnecting = onReconnecting;
240
+ websocket.handleClose = onClose;
155
241
  websocket.handleMessage = onMessage;
156
242
  websocketRef.current = websocket;
157
243
  return () => {
@@ -160,6 +246,6 @@ export function WebsocketConnection() {
160
246
  websocketRef.current = null;
161
247
  }
162
248
  };
163
- }, [fmsInfoLoadable, onMessage, onOpen, onReconnecting]);
249
+ }, [fmsInfoLoadable, onMessage, onOpen, onClose]);
164
250
  return null;
165
251
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seedtactics/insight-client",
3
- "version": "17.0.1-beta.12",
3
+ "version": "17.0.1-beta.13",
4
4
  "license": "BSD-3-Clause",
5
5
  "repository": {
6
6
  "url": "https://github.com/SeedTactics/fms-insight"
@@ -42,14 +42,14 @@
42
42
  "date-fns": "^4.4.0",
43
43
  "highlight.js": "^11.12.0",
44
44
  "jdenticon": "^3.3.0",
45
- "jotai": "^2.20.3",
45
+ "jotai": "^3.0.0",
46
46
  "jotai-family": "^1.1.0",
47
47
  "jsbarcode": "^3.12.3",
48
48
  "oidc-client-ts": "^3.5.0",
49
- "react": "^19.2.8",
49
+ "react": "^19.3.0",
50
50
  "react-calendar": "^6.0.1",
51
- "react-dom": "^19.2.8",
52
- "react-error-boundary": "^6.1.4",
51
+ "react-dom": "^19.3.0",
52
+ "react-error-boundary": "^6.1.6",
53
53
  "react-resize-detector": "^12.3.0",
54
54
  "react-timeago": "^8.3.0",
55
55
  "react-to-print": "^3.3.0",
@@ -61,21 +61,21 @@
61
61
  "@types/d3-sankey": "^0.12.5",
62
62
  "@types/d3-scale": "^4.0.9",
63
63
  "@types/d3-shape": "^3.2.0",
64
- "@types/node": "^26.4.1",
65
- "@types/react": "^19.2.18",
66
- "@types/react-dom": "^19.2.5",
64
+ "@types/node": "^26.6.3",
65
+ "@types/react": "^19.3.0",
66
+ "@types/react-dom": "^19.3.0",
67
67
  "@vitejs/plugin-react": "^6.1.1",
68
- "@vitest/browser-playwright": "^4.1.11",
69
- "jsdom": "^30.0.1",
68
+ "@vitest/browser-playwright": "^5.0.2",
69
+ "jsdom": "^30.1.1",
70
70
  "nswag": "^14.7.1",
71
- "oxfmt": "^0.66.0",
72
- "oxlint": "^1.81.0",
73
- "oxlint-tsgolint": "^7.0.2001",
74
- "playwright": "^1.62.1",
71
+ "oxfmt": "^0.71.0",
72
+ "oxlint": "^1.86.0",
73
+ "oxlint-tsgolint": "^7.0.2003",
74
+ "playwright": "^1.63.0",
75
75
  "typescript": "^7.0.2",
76
- "vite": "^8.2.2",
77
- "vitest": "^4.1.11",
78
- "vitest-browser-react": "^2.2.0"
76
+ "vite": "^8.3.1",
77
+ "vitest": "^5.0.2",
78
+ "vitest-browser-react": "^2.3.0"
79
79
  },
80
80
  "scripts": {
81
81
  "type-check": "tsc --noEmit --skipLibCheck",
@@ -1,3 +0,0 @@
1
- (function(){var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=((n,r,o)=>(o=n==null?{}:e(i(n)),s(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n)))(o(((e,t)=>{function n(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw Error(`map is read-only`)}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw Error(`set is read-only`)}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach(t=>{let r=e[t],i=typeof r;(i===`object`||i===`function`)&&!Object.isFrozen(r)&&n(r)}),e}var r=class{constructor(e){e.data===void 0&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}};function i(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&#x27;`)}function a(e,...t){let n=Object.create(null);for(let t in e)n[t]=e[t];return t.forEach(function(e){for(let t in e)n[t]=e[t]}),n}let o=e=>!!e.scope,s=(e,{prefix:t})=>{if(e.startsWith(`language:`))return e.replace(`language:`,`language-`);if(e.includes(`.`)){let n=e.split(`.`);return[`${t}${n.shift()}`,...n.map((e,t)=>`${e}${`_`.repeat(t+1)}`)].join(` `)}return`${t}${e}`};var c=class{constructor(e,t){this.buffer=``,this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=i(e)}openNode(e){if(!o(e))return;let t=s(e.scope,{prefix:this.classPrefix});this.span(t)}closeNode(e){o(e)&&(this.buffer+=`</span>`)}value(){return this.buffer}span(e){this.buffer+=`<span class="${e}">`}};let l=(e={})=>{let t={children:[]};return Object.assign(t,e),t};var u=class e{constructor(){this.rootNode=l(),this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){let t=l({scope:e});this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return typeof t==`string`?e.addText(t):t.children&&(e.openNode(t),t.children.forEach(t=>this._walk(e,t)),e.closeNode(t)),e}static _collapse(t){typeof t!=`string`&&t.children&&(t.children.every(e=>typeof e==`string`)?t.children=[t.children.join(``)]:t.children.forEach(t=>{e._collapse(t)}))}},d=class extends u{constructor(e){super(),this.options=e}addText(e){e!==``&&this.add(e)}startScope(e){this.openNode(e)}endScope(){this.closeNode()}__addSublanguage(e,t){let n=e.root;t&&(n.scope=`language:${t}`),this.add(n)}toHTML(){return new c(this,this.options).value()}finalize(){return this.closeAllNodes(),!0}};function f(e){return e?typeof e==`string`?e:e.source:null}function p(e){return g(`(?=`,e,`)`)}function m(e){return g(`(?:`,e,`)*`)}function h(e){return g(`(?:`,e,`)?`)}function g(...e){return e.map(e=>f(e)).join(``)}function _(e){let t=e[e.length-1];return typeof t==`object`&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function v(...e){return`(`+(_(e).capture?``:`?:`)+e.map(e=>f(e)).join(`|`)+`)`}function y(e){return RegExp(e.toString()+`|`).exec(``).length-1}function ee(e,t){let n=e&&e.exec(t);return n&&n.index===0}let b=new RegExp(v(/\[(?:[^\\\]]|\\.)*\]/,/\(\?<(?![=!])[^>]+>/,/\(\?'[^']+'/,/\(\??/,/\\([1-9][0-9]*)/,/\\./));function x(e,{joinWith:t}){let n=0;return e.map(e=>{n+=1;let t=n,r=f(e),i=``;for(;r.length>0;){let e=b.exec(r);if(!e){i+=r;break}i+=r.substring(0,e.index),r=r.substring(e.index+e[0].length),e[0][0]===`\\`&&e[1]?i+=`\\`+String(Number(e[1])+t):(i+=e[0],(e[0]===`(`||/^\(\?[<']/.test(e[0]))&&n++)}return i}).map(e=>`(${e})`).join(t)}let S=/\b\B/,C=`[a-zA-Z]\\w*`,w=`[a-zA-Z_]\\w*`,T=`\\b\\d+(\\.\\d+)?`,E=`(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)`,D=`\\b(0b[01]+)`,O=(e={})=>{let t=/^#![ ]*\//;return e.binary&&(e.begin=g(t,/.*\b/,e.binary,/\b.*/)),a({scope:`meta`,begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{e.index!==0&&t.ignoreMatch()}},e)},k={begin:`\\\\[\\s\\S]`,relevance:0},A={scope:`string`,begin:`'`,end:`'`,illegal:`\\n`,contains:[k]},j={scope:`string`,begin:`"`,end:`"`,illegal:`\\n`,contains:[k]},M={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},N=function(e,t,n={}){let r=a({scope:`comment`,begin:e,end:t,contains:[]},n);r.contains.push({scope:`doctag`,begin:`[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)`,end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});let i=v(`I`,`a`,`is`,`so`,`us`,`to`,`at`,`if`,`in`,`it`,`on`,/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return r.contains.push({begin:g(/[ ]+/,`(`,i,/[.]?[:]?([.][ ]|[ ])/,`){3}`)}),r},P=N(`//`,`$`),F=N(`/\\*`,`\\*/`),I=N(`#`,`$`);var L=Object.freeze({__proto__:null,APOS_STRING_MODE:A,BACKSLASH_ESCAPE:k,BINARY_NUMBER_MODE:{scope:`number`,begin:D,relevance:0},BINARY_NUMBER_RE:D,COMMENT:N,C_BLOCK_COMMENT_MODE:F,C_LINE_COMMENT_MODE:P,C_NUMBER_MODE:{scope:`number`,begin:E,relevance:0},C_NUMBER_RE:E,END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})},HASH_COMMENT_MODE:I,IDENT_RE:C,MATCH_NOTHING_RE:S,METHOD_GUARD:{begin:`\\.\\s*[a-zA-Z_]\\w*`,relevance:0},NUMBER_MODE:{scope:`number`,begin:T,relevance:0},NUMBER_RE:T,PHRASAL_WORDS_MODE:M,QUOTE_STRING_MODE:j,REGEXP_MODE:{scope:`regexp`,begin:/\/(?=[^/\n]*\/)/,end:/\/[gimuy]*/,contains:[k,{begin:/\[/,end:/\]/,relevance:0,contains:[k]}]},RE_STARTERS_RE:`!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~`,SHEBANG:O,TITLE_MODE:{scope:`title`,begin:C,relevance:0},UNDERSCORE_IDENT_RE:w,UNDERSCORE_TITLE_MODE:{scope:`title`,begin:w,relevance:0}});function R(e,t){e.input[e.index-1]===`.`&&t.ignoreMatch()}function z(e,t){e.className!==void 0&&(e.scope=e.className,delete e.className)}function B(e,t){t&&e.beginKeywords&&(e.begin=`\\b(`+e.beginKeywords.split(` `).join(`|`)+`)(?!\\.)(?=\\b|\\s)`,e.__beforeBegin=R,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,e.relevance===void 0&&(e.relevance=0))}function te(e,t){Array.isArray(e.illegal)&&(e.illegal=v(...e.illegal))}function ne(e,t){if(e.match){if(e.begin||e.end)throw Error(`begin & end are not supported with match`);e.begin=e.match,delete e.match}}function re(e,t){e.relevance===void 0&&(e.relevance=1)}let ie=(e,t)=>{if(!e.beforeMatch)return;if(e.starts)throw Error(`beforeMatch cannot be used with starts`);let n=Object.assign({},e);Object.keys(e).forEach(t=>{delete e[t]}),e.keywords=n.keywords,e.begin=g(n.beforeMatch,p(n.begin)),e.starts={relevance:0,contains:[Object.assign(n,{endsParent:!0})]},e.relevance=0,delete n.beforeMatch},ae=[`of`,`and`,`for`,`in`,`not`,`or`,`if`,`then`,`parent`,`list`,`value`];function V(e,t,n=`keyword`){let r=Object.create(null);return typeof e==`string`?i(n,e.split(` `)):Array.isArray(e)?i(n,e):Object.keys(e).forEach(function(n){Object.assign(r,V(e[n],t,n))}),r;function i(e,n){t&&(n=n.map(e=>e.toLowerCase())),n.forEach(function(t){let n=t.split(`|`);r[n[0]]=[e,oe(n[0],n[1])]})}}function oe(e,t){return t?Number(t):+!se(e)}function se(e){return ae.includes(e.toLowerCase())}let H={},U=e=>{console.error(e)},W=(e,...t)=>{console.log(`WARN: ${e}`,...t)},G=(e,t)=>{H[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),H[`${e}/${t}`]=!0)},K=Error();function q(e,t,{key:n}){let r=0,i=e[n],a={},o={};for(let e=1;e<=t.length;e++)o[e+r]=i[e],a[e+r]=!0,r+=y(t[e-1]);e[n]=o,e[n]._emit=a,e[n]._multi=!0}function ce(e){if(Array.isArray(e.begin)){if(e.skip||e.excludeBegin||e.returnBegin)throw U(`skip, excludeBegin, returnBegin not compatible with beginScope: {}`),K;if(typeof e.beginScope!=`object`||e.beginScope===null)throw U(`beginScope must be object`),K;q(e,e.begin,{key:`beginScope`}),e.begin=x(e.begin,{joinWith:``})}}function le(e){if(Array.isArray(e.end)){if(e.skip||e.excludeEnd||e.returnEnd)throw U(`skip, excludeEnd, returnEnd not compatible with endScope: {}`),K;if(typeof e.endScope!=`object`||e.endScope===null)throw U(`endScope must be object`),K;q(e,e.end,{key:`endScope`}),e.end=x(e.end,{joinWith:``})}}function ue(e){e.scope&&typeof e.scope==`object`&&e.scope!==null&&(e.beginScope=e.scope,delete e.scope)}function de(e){ue(e),typeof e.beginScope==`string`&&(e.beginScope={_wrap:e.beginScope}),typeof e.endScope==`string`&&(e.endScope={_wrap:e.endScope}),ce(e),le(e)}function fe(e){function t(t,n){return new RegExp(f(t),`m`+(e.case_insensitive?`i`:``)+(e.unicodeRegex?`u`:``)+(n?`g`:``))}class n{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,t){t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),this.matchAt+=y(e)+1}compile(){this.regexes.length===0&&(this.exec=()=>null);let e=this.regexes.map(e=>e[1]);this.matcherRe=t(x(e,{joinWith:`|`}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;let t=this.matcherRe.exec(e);if(!t)return null;let n=t.findIndex((e,t)=>t>0&&e!==void 0),r=this.matchIndexes[n];return t.splice(0,n),Object.assign(t,r)}}class r{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];let t=new n;return this.rules.slice(e).forEach(([e,n])=>t.addRule(e,n)),t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){return this.regexIndex!==0}considerAll(){this.regexIndex=0}addRule(e,t){this.rules.push([e,t]),t.type===`begin`&&this.count++}exec(e){let t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex;let n=t.exec(e);if(this.resumingScanAtSamePosition()&&!(n&&n.index===this.lastIndex)){let t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}return n&&(this.regexIndex+=n.position+1,this.regexIndex===this.count&&this.considerAll()),n}}function i(e){let t=new r;return e.contains.forEach(e=>t.addRule(e.begin,{rule:e,type:`begin`})),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:`end`}),e.illegal&&t.addRule(e.illegal,{type:`illegal`}),t}function o(n,r){let a=n;if(n.isCompiled)return a;[z,ne,de,ie].forEach(e=>e(n,r)),e.compilerExtensions.forEach(e=>e(n,r)),n.__beforeBegin=null,[B,te,re].forEach(e=>e(n,r)),n.isCompiled=!0;let s=null;return typeof n.keywords==`object`&&n.keywords.$pattern&&(n.keywords=Object.assign({},n.keywords),s=n.keywords.$pattern,delete n.keywords.$pattern),s||=/\w+/,n.keywords&&=V(n.keywords,e.case_insensitive),a.keywordPatternRe=t(s,!0),r&&(n.begin||=/\B|\b/,a.beginRe=t(a.begin),!n.end&&!n.endsWithParent&&(n.end=/\B|\b/),n.end&&(a.endRe=t(a.end)),a.terminatorEnd=f(a.end)||``,n.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(n.end?`|`:``)+r.terminatorEnd)),n.illegal&&(a.illegalRe=t(n.illegal)),n.contains||=[],n.contains=[].concat(...n.contains.map(function(e){return pe(e===`self`?n:e)})),n.contains.forEach(function(e){o(e,a)}),n.starts&&o(n.starts,r),a.matcher=i(a),a}if(e.compilerExtensions||=[],e.contains&&e.contains.includes(`self`))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=a(e.classNameAliases||{}),o(e)}function J(e){return e?e.endsWithParent||J(e.starts):!1}function pe(e){return e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map(function(t){return a(e,{variants:null},t)})),e.cachedVariants?e.cachedVariants:J(e)?a(e,{starts:e.starts?a(e.starts):null}):Object.isFrozen(e)?a(e):e}var me=`11.12.0`,he=class extends Error{constructor(e,t){super(e),this.name=`HTMLInjectionError`,this.html=t}};let Y=i,X=a,Z=Symbol(`nomatch`),Q=function(e){let t=Object.create(null),i=Object.create(null),a=[],o=!0,s=`Could not find the language '{}', did you forget to load/include a language module?`,c={disableAutodetect:!0,name:`Plain text`,contains:[]},l={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:`hljs-`,cssSelector:`pre code`,languages:null,__emitter:d};function u(e){return l.noHighlightRe.test(e)}function f(e){let t=e.className+` `;t+=e.parentNode?e.parentNode.className:``;let n=l.languageDetectRe.exec(t);if(n){let t=M(n[1]);return t||(W(s.replace(`{}`,n[1])),W(`Falling back to no-highlight mode for this block.`,e)),t?n[1]:`no-highlight`}return t.split(/\s+/).find(e=>u(e)||M(e))}function _(e,t,n){let r=``,i=``;typeof t==`object`?(r=e,n=t.ignoreIllegals,i=t.language):(G(`10.7.0`,`highlight(lang, code, ...args) has been deprecated.`),G(`10.7.0`,`Please use highlight(code, options) instead.
2
- https://github.com/highlightjs/highlight.js/issues/2277`),i=e,r=t),n===void 0&&(n=!0);let a={code:r,language:i};z(`before:highlight`,a);let o=a.result?a.result:y(a.language,a.code,n);return o.code=a.code,z(`after:highlight`,o),o}function y(e,n,i,a){let c=Object.create(null);function u(e,t){return e.keywords[t]}function d(){if(!k.keywords){j.addText(N);return}let e=0;k.keywordPatternRe.lastIndex=0;let t=k.keywordPatternRe.exec(N),n=``;for(;t;){n+=N.substring(e,t.index);let r=E.case_insensitive?t[0].toLowerCase():t[0],i=u(k,r);if(i){let[e,a]=i;if(j.addText(n),n=``,c[r]=(c[r]||0)+1,c[r]<=7&&(P+=a),e.startsWith(`_`))n+=t[0];else{let n=E.classNameAliases[e]||e;m(t[0],n)}}else n+=t[0];e=k.keywordPatternRe.lastIndex,t=k.keywordPatternRe.exec(N)}n+=N.substring(e),j.addText(n)}function f(){if(N===``)return;let e=null;if(typeof k.subLanguage==`string`){if(!t[k.subLanguage]){j.addText(N);return}e=y(k.subLanguage,N,!0,A[k.subLanguage]),A[k.subLanguage]=e._top}else e=x(N,k.subLanguage.length?k.subLanguage:null);k.relevance>0&&(P+=e.relevance),j.__addSublanguage(e._emitter,e.language)}function p(){k.subLanguage==null?d():f(),N=``}function m(e,t){e!==``&&(j.startScope(t),j.addText(e),j.endScope())}function h(e,t){let n=1,r=t.length-1;for(;n<=r;){if(!e._emit[n]){n++;continue}let r=E.classNameAliases[e[n]]||e[n],i=t[n];r?m(i,r):(N=i,d(),N=``),n++}}function g(e,t){return e.scope&&typeof e.scope==`string`&&j.openNode(E.classNameAliases[e.scope]||e.scope),e.beginScope&&(e.beginScope._wrap?(m(N,E.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap),N=``):e.beginScope._multi&&(h(e.beginScope,t),N=``)),k=Object.create(e,{parent:{value:k}}),k}function _(e,t,n){let i=ee(e.endRe,n);if(i){if(e[`on:end`]){let n=new r(e);e[`on:end`](t,n),n.isMatchIgnored&&(i=!1)}if(i){for(;e.endsParent&&e.parent;)e=e.parent;return e}}if(e.endsWithParent)return _(e.parent,t,n)}function v(e){return k.matcher.regexIndex===0?(N+=e[0],1):(L=!0,0)}function b(e){let t=e[0],n=e.rule,i=new r(n),a=[n.__beforeBegin,n[`on:begin`]];for(let n of a)if(n&&(n(e,i),i.isMatchIgnored))return v(t);return n.skip?N+=t:(n.excludeBegin&&(N+=t),p(),!n.returnBegin&&!n.excludeBegin&&(N=t)),g(n,e),n.returnBegin?0:t.length}function S(e){let t=e[0],r=n.substring(e.index),i=_(k,e,r);if(!i)return Z;let a=k;k.endScope&&k.endScope._wrap?(p(),m(t,k.endScope._wrap)):k.endScope&&k.endScope._multi?(p(),h(k.endScope,e)):a.skip?N+=t:(a.returnEnd||a.excludeEnd||(N+=t),p(),a.excludeEnd&&(N=t));do k.scope&&j.closeNode(),!k.skip&&!k.subLanguage&&(P+=k.relevance),k=k.parent;while(k!==i.parent);return i.starts&&g(i.starts,e),a.returnEnd?0:t.length}function C(){let e=[];for(let t=k;t!==E;t=t.parent)t.scope&&e.unshift(t.scope);e.forEach(e=>j.openNode(e))}let w={};function T(t,r){let a=r&&r[0];if(N+=t,a==null)return p(),0;if(w.type===`begin`&&r.type===`end`&&w.index===r.index&&a===``){if(N+=n.slice(r.index,r.index+1),!o){let t=Error(`0 width match regex (${e})`);throw t.languageName=e,t.badRule=w.rule,t}return 1}if(w=r,r.type===`begin`)return b(r);if(r.type===`illegal`&&!i){let e=Error(`Illegal lexeme "`+a+`" for mode "`+(k.scope||`<unnamed>`)+`"`);throw e.mode=k,e}if(r.type===`end`){let e=S(r);if(e!==Z)return e}if(r.type===`illegal`&&a===``)return r.index===n.length||(N+=`
3
- `),1;if(I>1e5&&I>r.index*3)throw Error(`potential infinite loop, way more iterations than matches`);return N+=a,a.length}let E=M(e);if(!E)throw U(s.replace(`{}`,e)),Error(`Unknown language: "`+e+`"`);let D=fe(E),O=``,k=a||D,A={},j=new l.__emitter(l);C();let N=``,P=0,F=0,I=0,L=!1;try{if(E.__emitTokens)E.__emitTokens(n,j);else{for(k.matcher.considerAll();;){I++,L?L=!1:k.matcher.considerAll(),k.matcher.lastIndex=F;let e=k.matcher.exec(n);if(!e)break;let t=T(n.substring(F,e.index),e);F=e.index+t}T(n.substring(F))}return j.finalize(),O=j.toHTML(),{language:e,value:O,relevance:P,illegal:!1,_emitter:j,_top:k}}catch(t){if(t.message&&t.message.includes(`Illegal`))return{language:e,value:Y(n),illegal:!0,relevance:0,_illegalBy:{message:t.message,index:F,context:n.slice(F-100,F+100),mode:t.mode,resultSoFar:O},_emitter:j};if(o)return{language:e,value:Y(n),illegal:!1,relevance:0,errorRaised:t,_emitter:j,_top:k};throw t}}function b(e){let t={value:Y(e),illegal:!1,relevance:0,_top:c,_emitter:new l.__emitter(l)};return t._emitter.addText(e),t}function x(e,n){n=n||l.languages||Object.keys(t);let r=b(e),i=n.filter(M).filter(P).map(t=>y(t,e,!1));i.unshift(r);let[a,o]=i.sort((e,t)=>{if(e.relevance!==t.relevance)return t.relevance-e.relevance;if(e.language&&t.language){if(M(e.language).supersetOf===t.language)return 1;if(M(t.language).supersetOf===e.language)return-1}return 0}),s=a;return s.secondBest=o,s}function S(e,t,n){let r=t&&i[t]||n;e.classList.add(`hljs`),e.classList.add(`language-${r}`)}function C(e){let t=null,n=f(e);if(u(n))return;if(z(`before:highlightElement`,{el:e,language:n}),e.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e);return}if(e.children.length>0&&(l.ignoreUnescapedHTML||(console.warn(`One of your code blocks includes unescaped HTML. This is a potentially serious security risk.`),console.warn(`https://github.com/highlightjs/highlight.js/wiki/security`),console.warn(`The element with unescaped HTML:`),console.warn(e)),l.throwUnescapedHTML))throw new he(`One of your code blocks includes unescaped HTML.`,e.innerHTML);t=e;let r=t.textContent,i=n?_(r,{language:n,ignoreIllegals:!0}):x(r);e.innerHTML=i.value,e.dataset.highlighted=`yes`,S(e,n,i.language),e.result={language:i.language,re:i.relevance,relevance:i.relevance},i.secondBest&&(e.secondBest={language:i.secondBest.language,relevance:i.secondBest.relevance}),z(`after:highlightElement`,{el:e,result:i,text:r})}function w(e){l=X(l,e)}let T=()=>{O(),G(`10.6.0`,`initHighlighting() deprecated. Use highlightAll() now.`)};function E(){O(),G(`10.6.0`,`initHighlightingOnLoad() deprecated. Use highlightAll() now.`)}let D=!1;function O(){function e(){O()}if(document.readyState===`loading`){D||window.addEventListener(`DOMContentLoaded`,e,!1),D=!0;return}document.querySelectorAll(l.cssSelector).forEach(C)}function k(n,r){let i=null;try{i=r(e)}catch(e){if(U(`Language definition for '{}' could not be registered.`.replace(`{}`,n)),o)U(e);else throw e;i=c}i.name||(i.name=n),t[n]=i,i.rawDefinition=r.bind(null,e),i.aliases&&N(i.aliases,{languageName:n})}function A(e){delete t[e];for(let t of Object.keys(i))i[t]===e&&delete i[t]}function j(){return Object.keys(t)}function M(e){return e=(e||``).toLowerCase(),t[e]||t[i[e]]}function N(e,{languageName:t}){typeof e==`string`&&(e=[e]),e.forEach(e=>{i[e.toLowerCase()]=t})}function P(e){let t=M(e);return t&&!t.disableAutodetect}function F(e){e[`before:highlightBlock`]&&!e[`before:highlightElement`]&&(e[`before:highlightElement`]=t=>{e[`before:highlightBlock`](Object.assign({block:t.el},t))}),e[`after:highlightBlock`]&&!e[`after:highlightElement`]&&(e[`after:highlightElement`]=t=>{e[`after:highlightBlock`](Object.assign({block:t.el},t))})}function I(e){F(e),a.push(e)}function R(e){let t=a.indexOf(e);t!==-1&&a.splice(t,1)}function z(e,t){let n=e;a.forEach(function(e){e[n]&&e[n](t)})}function B(e){return G(`10.7.0`,`highlightBlock will be removed entirely in v12.0`),G(`10.7.0`,`Please use highlightElement now.`),C(e)}Object.assign(e,{highlight:_,highlightAuto:x,highlightAll:O,highlightElement:C,highlightBlock:B,configure:w,initHighlighting:T,initHighlightingOnLoad:E,registerLanguage:k,unregisterLanguage:A,listLanguages:j,getLanguage:M,registerAliases:N,autoDetection:P,inherit:X,addPlugin:I,removePlugin:R}),e.debugMode=function(){o=!1},e.safeMode=function(){o=!0},e.versionString=me,e.regex={concat:g,lookahead:p,either:v,optional:h,anyNumberOfTimes:m};for(let e in L)typeof L[e]==`object`&&n(L[e]);return Object.assign(e,L),e},$=Q({});$.newInstance=()=>Q({}),t.exports=$,$.HighlightJS=$,$.default=$}))()).default;function l(e){let t=e.regex,n={$pattern:/[A-Z]+|%/,keyword:[`THEN`,`ELSE`,`ENDIF`,`IF`,`GOTO`,`DO`,`WHILE`,`WH`,`END`,`CALL`,`SUB`,`ENDSUB`,`EQ`,`NE`,`LT`,`GT`,`LE`,`GE`,`AND`,`OR`,`XOR`,`%`],built_in:[`ATAN`,`ABS`,`ACOS`,`ASIN`,`COS`,`EXP`,`FIX`,`FUP`,`ROUND`,`LN`,`SIN`,`SQRT`,`TAN`,`EXISTS`]},r=/\b/;function i(e,t){if(e.index===0)return;let n=e.input[e.index-1];n>=`0`&&n<=`9`||n!==`_`&&t.ignoreMatch()}let a=/[+-]?((\.\d+)|(\d+)(\.\d*)?)/,o=/[GM]\s*\d+(\.\d+)?/,s=/T\s*\d+/,c=/O\s*\d+/,l=/O<.+>/,u=/[ABCUVWXYZ]\s*/,d=/[FHIJKPQRS]\s*/;return{name:`G-code (ISO 6983)`,aliases:[`nc`],case_insensitive:!0,disableAutodetect:!0,keywords:n,contains:[e.COMMENT(/\(/,/\)/),e.COMMENT(/;/,/$/),e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,{scope:`title.function`,variants:[{match:t.concat(r,o)},{begin:o,"on:begin":i},{match:t.concat(r,s)},{begin:s,"on:begin":i}]},{scope:`symbol`,variants:[{match:t.concat(r,c)},{begin:c,"on:begin":i},{match:t.concat(r,l)},{begin:l,"on:begin":i},{match:/\*\s*\d+\s*$/}]},{scope:`operator`,match:/^N\s*\d+/},{scope:`variable`,match:/-?#\s*\d+/},{scope:`property`,variants:[{match:t.concat(r,u,a)},{begin:t.concat(u,a),"on:begin":i}]},{scope:`params`,variants:[{match:t.concat(r,d,a)},{begin:t.concat(d,a),"on:begin":i}]}]}}c.registerLanguage(`gcode`,l),self.addEventListener(`message`,e=>{self.postMessage(c.highlight(e.data,{language:`gcode`}).value,[])})})();