diff --git a/client-src/index.js b/client-src/index.js index 706c77cfe1..981bb99ee8 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -142,7 +142,7 @@ const onSocketMessage = { // Fixes #1042. overlay doesn't clear if errors are fixed but warnings remain. if (options.overlay) { - overlay.send("DISMISS"); + overlay.send({ type: "DISMISS" }); } sendMessage("Invalid"); @@ -199,7 +199,7 @@ const onSocketMessage = { log.info("Nothing changed."); if (options.overlay) { - overlay.send("DISMISS"); + overlay.send({ type: "DISMISS" }); } sendMessage("StillOk"); @@ -208,7 +208,7 @@ const onSocketMessage = { sendMessage("Ok"); if (options.overlay) { - overlay.send("DISMISS"); + overlay.send({ type: "DISMISS" }); } reloadApp(options, status); @@ -317,7 +317,7 @@ const onSocketMessage = { log.info("Disconnected!"); if (options.overlay) { - overlay.send("DISMISS"); + overlay.send({ type: "DISMISS" }); } sendMessage("Close"); diff --git a/client-src/overlay.js b/client-src/overlay.js index 7e2ac75e96..fba6bdb47d 100644 --- a/client-src/overlay.js +++ b/client-src/overlay.js @@ -3,7 +3,6 @@ import ansiHTML from "ansi-html-community"; import { encode } from "html-entities"; -import { interpret } from "@xstate/fsm"; import { containerStyle, dismissButtonStyle, @@ -143,7 +142,7 @@ const createOverlay = (options) => { closeButtonElement.ariaLabel = "Dismiss"; closeButtonElement.addEventListener("click", () => { // eslint-disable-next-line no-use-before-define - overlayService.send("DISMISS"); + overlayService.send({ type: "DISMISS" }); }); contentElement.appendChild(headerElement); @@ -254,14 +253,12 @@ const createOverlay = (options) => { }, trustedTypesPolicyName); } - const overlayMachine = createOverlayMachine({ + const overlayService = createOverlayMachine({ showOverlay: ({ level = "error", messages }) => show(level, messages, options.trustedTypesPolicyName), hideOverlay: hide, }); - const overlayService = interpret(overlayMachine).start(); - listenToRuntimeError((err) => { console.log(err); overlayService.send({ diff --git a/client-src/overlay/fsm.js b/client-src/overlay/fsm.js new file mode 100644 index 0000000000..95abd60f58 --- /dev/null +++ b/client-src/overlay/fsm.js @@ -0,0 +1,57 @@ +/** + * @typedef {Object} StateDefinitions + * @property {{[event: string]: { target: string; actions?: Array }}} [on] + */ + +/** + * @typedef {Object} Options + * @property {{[state: string]: StateDefinitions}} states + * @property {object} context; + * @property {string} initial + */ + +/** + * @typedef {Object} Implementation + * @property {{[actionName: string]: (ctx: object, event: any) => object}} actions + */ + +/** + * A simplified `createMachine` from `@xstate/fsm` with the following differences: + * + * - the returned machine is technically a "service". No `interpret(machine).start()` is needed. + * - the state definition only support `on` and target must be declared with { target: 'nextState', actions: [] } explicitly. + * - event passed to `send` must be an object with `type` property. + * - actions implementation will be [assign action](https://xstate.js.org/docs/guides/context.html#assign-action) if you return any value. + * Do not return anything if you just want to invoke side effect. + * + * The goal of this custom function is to avoid installing the entire `'xstate/fsm'` package, while enabling modeling using + * state machine. You can copy the first parameter into the editor at https://stately.ai/viz to visualize the state machine. + * + * @param {Options} options + * @param {Implementation} implementation + */ +function createMachine({ states, context, initial }, { actions }) { + let currentState = initial; + let currentContext = context; + + return { + send: (event) => { + const transitionConfig = states[currentState].on?.[event.type]; + + if (transitionConfig) { + currentState = transitionConfig.target; + transitionConfig.actions?.forEach((actName) => { + const nextContextValue = actions[actName]?.(currentContext, event); + if (nextContextValue) { + currentContext = { + ...currentContext, + ...nextContextValue, + }; + } + }); + } + }, + }; +} + +export default createMachine; diff --git a/client-src/overlay/state-machine.js b/client-src/overlay/state-machine.js index 5f322daa3f..d9ed764198 100644 --- a/client-src/overlay/state-machine.js +++ b/client-src/overlay/state-machine.js @@ -1,4 +1,4 @@ -import { createMachine, assign } from "@xstate/fsm"; +import createMachine from "./fsm.js"; /** * @typedef {Object} ShowOverlayData @@ -19,7 +19,6 @@ const createOverlayMachine = (options) => { const { hideOverlay, showOverlay } = options; const overlayMachine = createMachine( { - id: "overlay", initial: "hidden", context: { level: "error", @@ -27,7 +26,6 @@ const createOverlayMachine = (options) => { }, states: { hidden: { - entry: "hideOverlay", on: { BUILD_ERROR: { target: "displayBuildError", @@ -41,7 +39,10 @@ const createOverlayMachine = (options) => { }, displayBuildError: { on: { - DISMISS: { target: "hidden", actions: "dismissMessages" }, + DISMISS: { + target: "hidden", + actions: ["dismissMessages", "hideOverlay"], + }, BUILD_ERROR: { target: "displayBuildError", actions: ["appendMessages", "showOverlay"], @@ -50,7 +51,10 @@ const createOverlayMachine = (options) => { }, displayRuntimeError: { on: { - DISMISS: { target: "hidden", actions: "dismissMessages" }, + DISMISS: { + target: "hidden", + actions: ["dismissMessages", "hideOverlay"], + }, RUNTIME_ERROR: { target: "displayRuntimeError", actions: ["appendMessages", "showOverlay"], @@ -65,18 +69,24 @@ const createOverlayMachine = (options) => { }, { actions: { - dismissMessages: assign({ - messages: [], - level: "error", - }), - appendMessages: assign({ - messages: (context, event) => context.messages.concat(event.messages), - level: (context, event) => event.level || context.level, - }), - setMessages: assign({ - messages: (_, event) => event.messages, - level: (context, event) => event.level || context.level, - }), + dismissMessages: () => { + return { + messages: [], + level: "error", + }; + }, + appendMessages: (context, event) => { + return { + messages: context.messages.concat(event.messages), + level: event.level || context.level, + }; + }, + setMessages: (context, event) => { + return { + messages: event.messages, + level: event.level || context.level, + }; + }, hideOverlay, showOverlay, }, diff --git a/package-lock.json b/package-lock.json index a5e41f09c6..ef55211816 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "@types/serve-static": "^1.13.10", "@types/sockjs": "^0.3.33", "@types/ws": "^8.5.1", - "@xstate/fsm": "^2.0.0", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.0.11", "chokidar": "^3.5.3", @@ -3999,11 +3998,6 @@ } } }, - "node_modules/@xstate/fsm": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-2.0.0.tgz", - "integrity": "sha512-p/zcvBMoU2ap5byMefLkR+AM+Eh99CU/SDEQeccgKlmFNOMDwphaRGqdk+emvel/SaGZ7Rf9sDvzAplLzLdEVQ==" - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -18770,11 +18764,6 @@ "dev": true, "requires": {} }, - "@xstate/fsm": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-2.0.0.tgz", - "integrity": "sha512-p/zcvBMoU2ap5byMefLkR+AM+Eh99CU/SDEQeccgKlmFNOMDwphaRGqdk+emvel/SaGZ7Rf9sDvzAplLzLdEVQ==" - }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", diff --git a/package.json b/package.json index 78e9dccbd4..0ea6a2d3c7 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "@types/serve-static": "^1.13.10", "@types/sockjs": "^0.3.33", "@types/ws": "^8.5.1", - "@xstate/fsm": "^2.0.0", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.0.11", "chokidar": "^3.5.3",