From 979611aaa1690518bcd925096c36443a73d943da Mon Sep 17 00:00:00 2001 From: Ben Musson Date: Tue, 3 Jun 2025 16:05:31 -0400 Subject: [PATCH 1/6] react component add React Component Minimum work in progress make user component a forwardRef component make the transpile/transform/execute cycle clear introduce JavaScript resource typescript refactor typescript compiler jsx Added import map better error handling async loading for everything single servlet, single manifest request Move to routehandlers suggestion source, remove babel linting prettier format button pass through entire compilation result --- .changeset/floppy-singers-tan.md | 5 + .changeset/shy-bikes-sing.md | 6 + gradle/libs.versions.toml | 2 + .../embr/common/reflect/ReflectUtils.kt | 17 +- .../ignition/embr/periscope/Meta.kt | 2 +- .../periscope/component/embedding/React.kt | 30 ++ .../periscope/resources/TypeScriptResource.kt | 21 ++ .../svgicons/folder-closed-disabled.svg | 8 + .../svgicons/folder-closed-selected.svg | 8 + .../images/svgicons/folder-closed.svg | 8 + .../svgicons/folder-opened-selected.svg | 6 + .../images/svgicons/folder-opened.svg | 6 + .../svgicons/schema-folder-disabled.svg | 6 + .../svgicons/schema-folder-selected.svg | 6 + .../images/svgicons/schema-folder.svg | 6 + .../images/svgicons/tsx-disabled.svg | 4 + .../images/svgicons/tsx-selected.svg | 4 + .../embr/periscope/images/svgicons/tsx.svg | 4 + .../main/resources/localization.properties | 22 ++ .../embr.periscope.embedding.react/props.json | 44 +++ .../variants/base.props.json | 14 + modules/periscope/designer/build.gradle.kts | 1 + .../periscope/PeriscopeDesignerContext.kt | 56 +++- .../embr/periscope/PeriscopeDesignerHook.kt | 12 +- .../ClientResourceSuggestionSource.kt | 41 +++ .../navtree/model/HiddenActionResourceNode.kt | 38 +++ .../navtree/model/TypeScriptResourceFolder.kt | 24 ++ .../navtree/model/TypeScriptResourceNode.kt | 29 ++ .../typescript/TypeScriptResourceEditor.kt | 111 +++++++ .../typescript/TypeScriptResourceListener.kt | 48 +++ .../typescript/TypeScriptResourceWorkspace.kt | 116 +++++++ .../typescript/TypeScriptCompiler.kt | 109 +++++++ .../component-disabled.svg | 70 ++++ .../component-selected.svg | 70 ++++ .../component.icon.svg | 70 ++++ .../thumbnails/base.png | Bin 0 -> 2891 bytes modules/periscope/gateway/build.gradle.kts | 4 + .../embr/periscope/PeriscopeGatewayContext.kt | 33 +- .../embr/periscope/PeriscopeGatewayHook.kt | 14 +- .../ClientResourceJavaScriptHandler.kt | 106 ++++++ .../handlers/ClientResourceManifestHandler.kt | 68 ++++ .../periscope/utils/ProjectResourceUtils.kt | 8 + .../main/resources/localization.properties | 8 + .../periscope/ts-compiler/build.gradle.kts | 3 + modules/periscope/ts-compiler/package.json | 17 + modules/periscope/ts-compiler/src/index.ts | 29 ++ modules/periscope/ts-compiler/tsconfig.json | 4 + modules/periscope/ts-compiler/vite.config.ts | 25 ++ modules/periscope/web/src/client.ts | 4 + .../web/src/components/embedding/index.ts | 1 + .../web/src/components/embedding/react.tsx | 146 +++++++++ .../src/extensions/client-resources/index.ts | 116 +++++++ modules/periscope/web/src/extensions/index.ts | 10 + settings.gradle.kts | 1 + yarn.lock | 305 +++++++++++++++++- 55 files changed, 1903 insertions(+), 23 deletions(-) create mode 100644 .changeset/floppy-singers-tan.md create mode 100644 .changeset/shy-bikes-sing.md create mode 100644 modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/embedding/React.kt create mode 100644 modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/TypeScriptResource.kt create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed-disabled.svg create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed-selected.svg create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed.svg create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-opened-selected.svg create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-opened.svg create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder-disabled.svg create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder-selected.svg create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder.svg create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx-disabled.svg create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx-selected.svg create mode 100644 modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx.svg create mode 100644 modules/periscope/common/src/main/resources/localization.properties create mode 100644 modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/props.json create mode 100644 modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/variants/base.props.json create mode 100644 modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/ClientResourceSuggestionSource.kt create mode 100644 modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/HiddenActionResourceNode.kt create mode 100644 modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceFolder.kt create mode 100644 modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceNode.kt create mode 100644 modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceEditor.kt create mode 100644 modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceListener.kt create mode 100644 modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt create mode 100644 modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt create mode 100644 modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component-disabled.svg create mode 100644 modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component-selected.svg create mode 100644 modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component.icon.svg create mode 100644 modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/thumbnails/base.png create mode 100644 modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/handlers/ClientResourceJavaScriptHandler.kt create mode 100644 modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/handlers/ClientResourceManifestHandler.kt create mode 100644 modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/utils/ProjectResourceUtils.kt create mode 100644 modules/periscope/ts-compiler/build.gradle.kts create mode 100644 modules/periscope/ts-compiler/package.json create mode 100644 modules/periscope/ts-compiler/src/index.ts create mode 100644 modules/periscope/ts-compiler/tsconfig.json create mode 100644 modules/periscope/ts-compiler/vite.config.ts create mode 100644 modules/periscope/web/src/components/embedding/react.tsx create mode 100644 modules/periscope/web/src/extensions/client-resources/index.ts diff --git a/.changeset/floppy-singers-tan.md b/.changeset/floppy-singers-tan.md new file mode 100644 index 00000000..2eaa5948 --- /dev/null +++ b/.changeset/floppy-singers-tan.md @@ -0,0 +1,5 @@ +--- +'@embr-jvm/core-common': patch +--- + +Introduce `setPrivateProperty` extension function. diff --git a/.changeset/shy-bikes-sing.md b/.changeset/shy-bikes-sing.md new file mode 100644 index 00000000..3ac80d02 --- /dev/null +++ b/.changeset/shy-bikes-sing.md @@ -0,0 +1,6 @@ +--- +'@embr-modules/periscope-web': minor +'@embr-modules/periscope': minor +--- + +(React Component) Add React component. This component uses user-supplied to render a React component, with basic Babel transpilation support for JSX. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3b96652a..85b48c1d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ jetty = "10.0.21" kotlin = "1.9.20" kotest = "6.0.0" node-gradle = "7.1.0" +rhino = "1.7.15" snmp4j = "3.9.7" spotless = "8.2.1" @@ -33,6 +34,7 @@ ignition-perspective-gateway = { group = "com.inductiveautomation.ignitionsdk", jetty-server = { group = "org.eclipse.jetty", name = "jetty-server", version.ref = "jetty" } jetty-servlet = { group = "org.eclipse.jetty", name = "jetty-servlet", version.ref = "jetty" } jetty-servlets = { group = "org.eclipse.jetty", name = "jetty-servlets", version.ref = "jetty" } +rhino = { group = "org.mozilla", name = "rhino", version.ref ="rhino"} snmp4j = { group = "org.snmp4j", name = "snmp4j", version.ref = "snmp4j"} # test framework diff --git a/libraries/core/common/src/main/kotlin/com/mussonindustrial/embr/common/reflect/ReflectUtils.kt b/libraries/core/common/src/main/kotlin/com/mussonindustrial/embr/common/reflect/ReflectUtils.kt index 7bab80f6..6e818d62 100644 --- a/libraries/core/common/src/main/kotlin/com/mussonindustrial/embr/common/reflect/ReflectUtils.kt +++ b/libraries/core/common/src/main/kotlin/com/mussonindustrial/embr/common/reflect/ReflectUtils.kt @@ -10,10 +10,19 @@ fun T.getPrivateProperty(variableName: String): Any? { } } -fun T.getPrivateMethod(methodName: String, vararg params: Class<*> = arrayOf()): Method { - return javaClass.getDeclaredMethod(methodName, *params).let { method -> - method.trySetAccessible() - return@let method +fun Any.getPrivateMethod(methodName: String, vararg params: Class<*>): Method { + val clazz = + when (this) { + is Class<*> -> this + else -> this.javaClass + } + return clazz.getDeclaredMethod(methodName, *params).apply { trySetAccessible() } +} + +fun T.setPrivateProperty(variableName: String, value: Any?) { + javaClass.getDeclaredField(variableName).let { field -> + field.trySetAccessible() + field.set(this, value) } } diff --git a/modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/Meta.kt b/modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/Meta.kt index 3d12729e..ef825715 100644 --- a/modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/Meta.kt +++ b/modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/Meta.kt @@ -8,7 +8,7 @@ object Meta { const val BUNDLE_PREFIX = "periscope" fun addI18NBundle() { - BundleUtil.get().addBundle(BUNDLE_PREFIX, Meta::class.java, "localization") + BundleUtil.get().addBundle(BUNDLE_PREFIX, this::class.java.classLoader, "localization") } fun removeI18NBundle() { diff --git a/modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/embedding/React.kt b/modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/embedding/React.kt new file mode 100644 index 00000000..8241d169 --- /dev/null +++ b/modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/embedding/React.kt @@ -0,0 +1,30 @@ +package com.mussonindustrial.ignition.embr.periscope.component.embedding + +import com.inductiveautomation.perspective.common.api.ComponentDescriptor +import com.inductiveautomation.perspective.common.api.ComponentDescriptorImpl +import com.mussonindustrial.embr.perspective.common.component.PaletteEntry +import com.mussonindustrial.embr.perspective.common.component.PerspectiveComponent +import com.mussonindustrial.embr.perspective.common.component.addPaletteEntry +import com.mussonindustrial.ignition.embr.periscope.Meta.MODULE_ID +import com.mussonindustrial.ignition.embr.periscope.PeriscopeComponents + +class React { + companion object : PerspectiveComponent { + override val id: String = "embr.periscope.embedding.react" + + private val VARIANT_BASE = + PaletteEntry(this::class.java, id, "base", "React", "React component.") + + override val descriptor: ComponentDescriptor = + ComponentDescriptorImpl.ComponentBuilder.newBuilder() + .setPaletteCategory("Embedding +") + .setId(id) + .setModuleId(MODULE_ID) + .setSchema(schema) + .setName("React") + .addPaletteEntry(VARIANT_BASE) + .setDefaultMetaName("React") + .setResources(PeriscopeComponents.BROWSER_RESOURCES) + .build() + } +} diff --git a/modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/TypeScriptResource.kt b/modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/TypeScriptResource.kt new file mode 100644 index 00000000..c105b43f --- /dev/null +++ b/modules/periscope/common/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/TypeScriptResource.kt @@ -0,0 +1,21 @@ +package com.mussonindustrial.ignition.embr.periscope.resources + +import com.inductiveautomation.ignition.common.project.resource.ProjectResource +import com.inductiveautomation.ignition.common.project.resource.ResourceType +import com.mussonindustrial.ignition.embr.periscope.Meta + +data class TypeScriptResource(val source: String, val compiled: String) { + + companion object { + val type = ResourceType(Meta.MODULE_ID, "typescript") + const val SOURCE_KEY = "source.tsx" + const val CLIENT_KEY = "client.js" + + fun fromResource(resource: ProjectResource): TypeScriptResource { + return TypeScriptResource( + resource.getData(SOURCE_KEY)?.decodeToString() ?: "", + resource.getData(CLIENT_KEY)?.decodeToString() ?: "", + ) + } + } +} diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed-disabled.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed-disabled.svg new file mode 100644 index 00000000..39a32d7b --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed-disabled.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed-selected.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed-selected.svg new file mode 100644 index 00000000..ceb22bc9 --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed-selected.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed.svg new file mode 100644 index 00000000..39a32d7b --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-closed.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-opened-selected.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-opened-selected.svg new file mode 100644 index 00000000..215baa0d --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-opened-selected.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-opened.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-opened.svg new file mode 100644 index 00000000..6bf493eb --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/folder-opened.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder-disabled.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder-disabled.svg new file mode 100644 index 00000000..904d6bad --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder-disabled.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder-selected.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder-selected.svg new file mode 100644 index 00000000..a5ecbec2 --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder-selected.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder.svg new file mode 100644 index 00000000..904d6bad --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/schema-folder.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx-disabled.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx-disabled.svg new file mode 100644 index 00000000..df2fa5a2 --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx-disabled.svg @@ -0,0 +1,4 @@ + + + + diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx-selected.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx-selected.svg new file mode 100644 index 00000000..4da75001 --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx-selected.svg @@ -0,0 +1,4 @@ + + + + diff --git a/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx.svg b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx.svg new file mode 100644 index 00000000..df2fa5a2 --- /dev/null +++ b/modules/periscope/common/src/main/resources/com/mussonindustrial/ignition/embr/periscope/images/svgicons/tsx.svg @@ -0,0 +1,4 @@ + + + + diff --git a/modules/periscope/common/src/main/resources/localization.properties b/modules/periscope/common/src/main/resources/localization.properties new file mode 100644 index 00000000..1d9cba51 --- /dev/null +++ b/modules/periscope/common/src/main/resources/localization.properties @@ -0,0 +1,22 @@ +script.common-param.sessionId=Optional. Identifier of the session to target. If omitted, current session will be used automatically. +script.common-param.pageId=Optional. Identifier of the page to target. If omitted, current page will be used automatically. + +script.runJavaScriptBlocking.desc=Run JavaScript code on the client and block for the result. +script.runJavaScriptBlocking.param.function=JavaScript function to run. Should be formatted as an arrow function. You may return a promise and resolve it later.

Example:
() => console.log('runJavaScriptBlocking') +script.runJavaScriptBlocking.param.args=Optional. Dictionary of arguments to use when calling the function. The keys of the dictionary should match the names of the arrow function arguments. +script.runJavaScriptBlocking.returns=Return value of JavaScript function. + +script.runJavaScriptAsync.desc=Asynchronously run JavaScript code on the client. +script.runJavaScriptAsync.param.function=JavaScript function to run. Should be formatted as an arrow function. You may return a promise and resolve it later.

Example:`() => console.log('runJavaScriptAsync') +script.runJavaScriptAsync.param.args=Optional. Dictionary of arguments to use when calling the function. The keys of the dictionary should match the names of the arrow function arguments. +script.runJavaScriptAsync.param.callback=Function to run once the function has returned/resolved. Should take a single parameter containing the result of the function. + +typescript.noun=TypeScript Module +typescript.nouns=TypeScript Modules +typescript.noun-long=TypeScript Module +typescript.nouns-long=TypeScript Modules +typescript.error.notFound=TypeScript Module not found. +typescript.error.existingResource=TypeScript Module '%s' is already defined +typescript.error.emptyResource=TypeScript Module name must not be empty +typescript.action.new=New TypeScript Module +typescript.action.action.new.defaultName=NewTypeScriptModule \ No newline at end of file diff --git a/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/props.json b/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/props.json new file mode 100644 index 00000000..68bf31a2 --- /dev/null +++ b/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/props.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "component": { + "type": "string", + "description": "Component Path.", + "extension": { + "suggestion-source": "embr-periscope-client-resource" + } + }, + "props": { + "type": "object" + }, + "events": { + "type": "object", + "description": "Component events.", + "properties": { + "dom": { + "$ref": "urn:ignition-schema:schemas/components/events-dom.json" + }, + "lifecycle": { + "$ref": "urn:ignition-schema:schemas/components/events-lifecycle.json" + }, + "target": { + "type": "object", + "properties": { + "lifecycle": { + "$ref": "urn:ignition-schema:schemas/components/events-lifecycle.json" + } + } + } + } + }, + "style": { + "$ref": "urn:ignition-schema:schemas/style-properties.schema.json", + "default": { + "classes": "", + "overflow": "auto" + } + } + }, + "type": "object", + "required": ["component"] +} \ No newline at end of file diff --git a/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/variants/base.props.json b/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/variants/base.props.json new file mode 100644 index 00000000..53ba3fcc --- /dev/null +++ b/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/variants/base.props.json @@ -0,0 +1,14 @@ +{ + "component": "(props) =>
{props.text}
", + "props": { + "text": "Hello World!" + }, + "options": { + "babel": { + "presets": ["react"] + } + }, + "style": { + "classes": "" + } +} \ No newline at end of file diff --git a/modules/periscope/designer/build.gradle.kts b/modules/periscope/designer/build.gradle.kts index 5b706491..9482d4ee 100644 --- a/modules/periscope/designer/build.gradle.kts +++ b/modules/periscope/designer/build.gradle.kts @@ -8,6 +8,7 @@ dependencies { modlImplementation(projects.libraries.core.designer) compileOnly(projects.libraries.perspective.common) modlImplementation(projects.libraries.perspective.designer) + modlImplementation(projects.modules.periscope.tsCompiler) compileOnly(projects.modules.periscope.common) } \ No newline at end of file diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt index 39451a40..db5359e7 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt @@ -1,23 +1,50 @@ package com.mussonindustrial.ignition.embr.periscope import com.inductiveautomation.ignition.designer.model.DesignerContext +import com.inductiveautomation.perspective.designer.DesignerHook +import com.inductiveautomation.perspective.designer.PerspectiveNavNode import com.inductiveautomation.perspective.designer.api.PerspectiveDesignerInterface +import com.mussonindustrial.embr.common.reflect.getPrivateProperty import com.mussonindustrial.embr.designer.EmbrDesignerContext import com.mussonindustrial.embr.designer.EmbrDesignerContextImpl import com.mussonindustrial.embr.perspective.designer.component.asDesignerComponent import com.mussonindustrial.embr.perspective.designer.component.registerComponent import com.mussonindustrial.embr.perspective.designer.component.removeComponent +import com.mussonindustrial.ignition.embr.periscope.component.ClientResourceSuggestionSource import com.mussonindustrial.ignition.embr.periscope.component.ComponentIdSuggestionSource import com.mussonindustrial.ignition.embr.periscope.component.embedding.* +import com.mussonindustrial.ignition.embr.periscope.resources.typescript.TypeScriptResourceListener +import com.mussonindustrial.ignition.embr.periscope.resources.typescript.TypeScriptResourceWorkspace +import com.teamdev.jxbrowser.engine.Engine -class PeriscopeDesignerContext(private val context: DesignerContext) : +class PeriscopeDesignerContext(val context: DesignerContext) : EmbrDesignerContext by EmbrDesignerContextImpl(context) { companion object { lateinit var instance: PeriscopeDesignerContext } val perspectiveDesignerInterface: PerspectiveDesignerInterface - private val componentIdSuggestionSource: ComponentIdSuggestionSource + val perspectiveNavNode: PerspectiveNavNode + val jxBrowserEngine: Engine + + init { + instance = this + perspectiveDesignerInterface = PerspectiveDesignerInterface.get(context) + perspectiveNavNode = + DesignerHook.get(context).getPrivateProperty("navNode") as PerspectiveNavNode + jxBrowserEngine = DesignerHook.get(context).workspace.engine + perspectiveDesignerInterface.suggestionSourceRegistry.apply { + registerSuggestionSource( + ComponentIdSuggestionSource.ID, + ComponentIdSuggestionSource(this@PeriscopeDesignerContext), + ) + registerSuggestionSource( + ClientResourceSuggestionSource.ID, + ClientResourceSuggestionSource(this@PeriscopeDesignerContext), + ) + } + } + private val components = listOf( EmbeddedView.asDesignerComponent(), @@ -25,17 +52,12 @@ class PeriscopeDesignerContext(private val context: DesignerContext) : JsonView.asDesignerComponent(), Portal.asDesignerComponent(), Swiper.asDesignerComponent(), + React.asDesignerComponent(), ) - init { - instance = this - perspectiveDesignerInterface = PerspectiveDesignerInterface.get(context) - componentIdSuggestionSource = ComponentIdSuggestionSource(this) - perspectiveDesignerInterface.suggestionSourceRegistry.registerSuggestionSource( - ComponentIdSuggestionSource.ID, - componentIdSuggestionSource, - ) - } + private val handlers = listOf(TypeScriptResourceListener(this)) + + private val workspaces = listOf(TypeScriptResourceWorkspace(context, perspectiveNavNode)) fun registerComponents() { components.forEach { perspectiveDesignerInterface.registerComponent(it) } @@ -44,4 +66,16 @@ class PeriscopeDesignerContext(private val context: DesignerContext) : fun removeComponents() { components.forEach { perspectiveDesignerInterface.removeComponent(it) } } + + fun registerResourceWorkspaces() { + workspaces.forEach { registerResourceWorkspace(it) } + } + + fun registerProjectResourceHandlers() { + handlers.forEach { project!!.addProjectResourceListener(it) } + } + + fun removeProjectResourceHandlers() { + handlers.forEach { project!!.removeProjectResourceListener(it) } + } } diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerHook.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerHook.kt index f8d5293f..2a18c8d7 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerHook.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerHook.kt @@ -1,5 +1,6 @@ package com.mussonindustrial.ignition.embr.periscope +import com.inductiveautomation.ignition.common.BundleUtil import com.inductiveautomation.ignition.common.licensing.LicenseState import com.inductiveautomation.ignition.designer.model.AbstractDesignerModuleHook import com.inductiveautomation.ignition.designer.model.DesignerContext @@ -23,7 +24,7 @@ class PeriscopeDesignerHook : AbstractDesignerModuleHook() { override fun startup(context: DesignerContext, activationState: LicenseState) { logger.debug("Embr-Periscope module started.") this.context = PeriscopeDesignerContext(context) - Meta.addI18NBundle() + BundleUtil.get().addBundle(Meta.BUNDLE_PREFIX, this::class.java.classLoader, "localization") val pdi: PerspectiveDesignerInterface = PerspectiveDesignerInterface.get(context) @@ -36,6 +37,12 @@ class PeriscopeDesignerHook : AbstractDesignerModuleHook() { logger.debug("Registering components...") this.context.registerComponents() + + logger.debug("Registering resource editors...") + this.context.registerResourceWorkspaces() + + logger.debug("Registering project resource handlers...") + this.context.registerProjectResourceHandlers() } override fun shutdown() { @@ -49,5 +56,8 @@ class PeriscopeDesignerHook : AbstractDesignerModuleHook() { logger.debug("Removing components...") this.context.removeComponents() + + logger.debug("Removing project resource handlers...") + this.context.removeProjectResourceHandlers() } } diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/ClientResourceSuggestionSource.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/ClientResourceSuggestionSource.kt new file mode 100644 index 00000000..7643bf70 --- /dev/null +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/ClientResourceSuggestionSource.kt @@ -0,0 +1,41 @@ +package com.mussonindustrial.ignition.embr.periscope.component + +import com.inductiveautomation.ignition.client.jsonedit.DocumentNode +import com.inductiveautomation.ignition.common.gson.JsonElement +import com.inductiveautomation.ignition.common.gson.JsonPrimitive +import com.inductiveautomation.ignition.common.jsonschema.JsonSchema +import com.inductiveautomation.perspective.designer.api.SuggestionSource +import com.mussonindustrial.ignition.embr.periscope.PeriscopeDesignerContext +import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource +import java.util.concurrent.CompletableFuture + +class ClientResourceSuggestionSource(private val context: PeriscopeDesignerContext) : + SuggestionSource { + + companion object { + const val ID = "embr-periscope-client-resource" + } + + override fun getSuggestions( + node: DocumentNode?, + schema: JsonSchema?, + ): CompletableFuture> { + + val project = context.project + if (project == null) { + return CompletableFuture.completedFuture(mutableMapOf()) + } + + val resources = context.project!!.getResourcesOfType(TypeScriptResource.type) + val resourcePaths = + resources + .associate { + it.resourcePath.path.toString() to + JsonPrimitive(it.resourcePath.path.toString()) as JsonElement + } + .toSortedMap() + .toMutableMap() + + return CompletableFuture.completedFuture(resourcePaths) + } +} diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/HiddenActionResourceNode.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/HiddenActionResourceNode.kt new file mode 100644 index 00000000..b16ff6f8 --- /dev/null +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/HiddenActionResourceNode.kt @@ -0,0 +1,38 @@ +package com.mussonindustrial.ignition.embr.periscope.navtree.model + +import com.inductiveautomation.ignition.common.project.resource.ProjectResource +import com.inductiveautomation.ignition.designer.model.DesignerContext +import com.inductiveautomation.ignition.designer.navtree.model.AbstractNavTreeNode +import com.inductiveautomation.ignition.designer.tabbedworkspace.ResourceNode +import com.inductiveautomation.ignition.designer.tabbedworkspace.TabbedResourceWorkspace +import javax.swing.JPopupMenu +import javax.swing.tree.TreePath + +abstract class HiddenActionResourceNode( + context: DesignerContext, + workspace: TabbedResourceWorkspace, + resource: ProjectResource, +) : ResourceNode(context, workspace, resource) { + override fun initPopupMenu( + menu: JPopupMenu, + paths: Array, + selection: List, + modifiers: Int, + ) { + super.initPopupMenu(menu, paths, selection, modifiers) + if ((modifiers and SHIFT_MASK) == SHIFT_MASK) { + addShiftClickMenuItems(menu, paths, selection, modifiers) + } + } + + abstract fun addShiftClickMenuItems( + menu: JPopupMenu, + paths: Array, + selection: List, + modifiers: Int, + ) + + companion object { + const val SHIFT_MASK: Int = 64 + } +} diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceFolder.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceFolder.kt new file mode 100644 index 00000000..f61836b3 --- /dev/null +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceFolder.kt @@ -0,0 +1,24 @@ +package com.mussonindustrial.ignition.embr.periscope.navtree.model + +import com.inductiveautomation.ignition.common.project.resource.ProjectResource +import com.inductiveautomation.ignition.designer.model.DesignerContext +import com.inductiveautomation.ignition.designer.navtree.model.AbstractNavTreeNode +import com.inductiveautomation.ignition.designer.tabbedworkspace.ResourceFolderNode +import com.inductiveautomation.ignition.designer.tabbedworkspace.TabbedResourceWorkspace + +class TypeScriptResourceFolder : ResourceFolderNode { + constructor( + context: DesignerContext, + workspace: TabbedResourceWorkspace, + resource: ProjectResource, + ) : super(context, workspace, resource) + + override fun createChildNode(resource: ProjectResource): AbstractNavTreeNode? { + return if (workspace.descriptor.resourceType == resource.resourceType) { + if (resource.isFolder) TypeScriptResourceFolder(this.context, this.workspace, resource) + else TypeScriptResourceNode(this.context, this.workspace, resource) + } else { + null + } + } +} diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceNode.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceNode.kt new file mode 100644 index 00000000..b1962f31 --- /dev/null +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceNode.kt @@ -0,0 +1,29 @@ +package com.mussonindustrial.ignition.embr.periscope.navtree.model + +import com.inductiveautomation.ignition.common.project.resource.ProjectResource +import com.inductiveautomation.ignition.designer.model.DesignerContext +import com.inductiveautomation.ignition.designer.navtree.model.AbstractNavTreeNode +import com.inductiveautomation.ignition.designer.tabbedworkspace.TabbedResourceWorkspace +import javax.swing.JMenuItem +import javax.swing.JPopupMenu +import javax.swing.tree.TreePath + +class TypeScriptResourceNode( + context: DesignerContext, + workspace: TabbedResourceWorkspace, + resource: ProjectResource, +) : HiddenActionResourceNode(context, workspace, resource) { + override fun addShiftClickMenuItems( + menu: JPopupMenu, + paths: Array, + selection: List, + modifiers: Int, + ) { + + val copyJavaScript = JMenuItem("Copy JavaScript") + val pasteJavaScript = JMenuItem("Paste JavaScript") + menu.addSeparator() + menu.add(copyJavaScript) + menu.add(pasteJavaScript) + } +} diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceEditor.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceEditor.kt new file mode 100644 index 00000000..2e901240 --- /dev/null +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceEditor.kt @@ -0,0 +1,111 @@ +package com.mussonindustrial.ignition.embr.periscope.resources.typescript + +import com.inductiveautomation.ignition.common.project.resource.ProjectResource +import com.inductiveautomation.ignition.common.project.resource.ProjectResourceBuilder +import com.inductiveautomation.ignition.common.project.resource.ResourcePath +import com.inductiveautomation.ignition.designer.ext.onClick +import com.inductiveautomation.ignition.designer.gui.tools.DisplayTrackingSyntaxTextArea +import com.inductiveautomation.ignition.designer.tabbedworkspace.ResourceEditor +import com.inductiveautomation.ignition.designer.tabbedworkspace.TabbedResourceWorkspace +import com.mussonindustrial.embr.common.logging.getLoggerEx +import com.mussonindustrial.ignition.embr.periscope.PeriscopeDesignerContext +import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource +import com.mussonindustrial.ignition.embr.periscope.typescript.TypeScriptCompiler +import java.awt.Font +import java.awt.Label +import javax.swing.JButton +import javax.swing.event.DocumentEvent +import javax.swing.event.DocumentListener +import net.miginfocom.swing.MigLayout +import org.fife.rsta.ac.LanguageSupportFactory +import org.fife.rsta.ac.js.JavaScriptLanguageSupport +import org.fife.ui.rsyntaxtextarea.SyntaxConstants +import org.fife.ui.rtextarea.RTextScrollPane +import org.json.JSONException + +class TypeScriptResourceEditor(workspace: TabbedResourceWorkspace?, path: ResourcePath?) : + ResourceEditor(workspace, path) { + + private val logger = this.getLoggerEx() + private val context = PeriscopeDesignerContext.instance + + private var resource: TypeScriptResource? = null + private lateinit var textArea: DisplayTrackingSyntaxTextArea + + val compiler = TypeScriptCompiler(context.jxBrowserEngine) + + override fun init(resource: TypeScriptResource) { + this.resource = resource + + this.removeAll() + this.layout = MigLayout("", "[]", "[25px!][]") + + val title = Label(this.tabTitle) + title.font = Font(Font.DIALOG_INPUT, Font.PLAIN, 15) + this.add(title, "cell 0 0, span") + + val format = + JButton("Format").onClick { + val result = compiler.format(textArea.text, textArea.caretPosition) + if (result != null) { + textArea.text = result.formatted + textArea.caretPosition = result.cursorOffset + this.commit() + } + } + this.add(format, "cell 1 0, span") + + val lsf = LanguageSupportFactory.get() + val support = + lsf.getSupportFor(SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT) as JavaScriptLanguageSupport + support.isStrictMode = true + + textArea = DisplayTrackingSyntaxTextArea(resource.source) + LanguageSupportFactory.get().register(textArea) + textArea.apply { + syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT + isCodeFoldingEnabled = true + tabSize = 2 + tabsEmulated = true + convertTabsToSpaces() + } + + val sp = RTextScrollPane(textArea) + this.add(sp, "cell 0 1, span, growx, growy, push") + + textArea.document.addDocumentListener(SimpleDocumentListener { this.commit() }) + } + + override fun getObjectForSave(): TypeScriptResource { + val result = compiler.compile(textArea.text) + + return TypeScriptResource( + textArea.text ?: "", + result?.outputText ?: "console.error('Failed to compile.')", + ) + } + + override fun deserialize(resource: ProjectResource): TypeScriptResource { + return TypeScriptResource.fromResource(resource) + } + + @Throws(JSONException::class) + override fun serializeResource(builder: ProjectResourceBuilder, resource: TypeScriptResource) { + builder.putData(TypeScriptResource.SOURCE_KEY, resource.source.encodeToByteArray()) + builder.putData(TypeScriptResource.CLIENT_KEY, resource.compiled.encodeToByteArray()) + } +} + +class SimpleDocumentListener(val block: (DocumentEvent) -> Unit) : DocumentListener { + override fun insertUpdate(event: DocumentEvent) { + block(event) + } + + override fun removeUpdate(event: DocumentEvent) { + block(event) + } + + override fun changedUpdate(event: DocumentEvent) { + block(event) + } +} diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceListener.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceListener.kt new file mode 100644 index 00000000..033df727 --- /dev/null +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceListener.kt @@ -0,0 +1,48 @@ +package com.mussonindustrial.ignition.embr.periscope.resources.typescript + +import com.inductiveautomation.ignition.common.project.ChangeOperation +import com.inductiveautomation.ignition.common.project.ProjectResourceListener +import com.inductiveautomation.ignition.gateway.project.ResourceFilter +import com.inductiveautomation.perspective.designer.DesignerHook +import com.mussonindustrial.ignition.embr.periscope.PeriscopeDesignerContext +import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource + +class TypeScriptResourceListener(val context: PeriscopeDesignerContext) : ProjectResourceListener { + + val filter: ResourceFilter = + ResourceFilter.newBuilder().addResourceType(TypeScriptResource.type).build() + + override fun onBeforeChanges() { + super.onBeforeChanges() + } + + override fun onAfterChanges() { + DesignerHook.get(context.context).notifyProjectSaveDone() + } + + override fun manifestChanged( + projectName: String, + operation: List, + ) { + super.manifestChanged(projectName, operation) + } + + override fun resourcesCreated( + projectName: String, + resources: List, + ) {} + + override fun resourcesModified( + projectName: String, + resources: List, + ) {} + + override fun resourcesDeleted( + projectName: String, + resources: List, + ) {} + + override fun getResourceFilter(): ResourceFilter { + return filter + } +} diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt new file mode 100644 index 00000000..bdca6a82 --- /dev/null +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt @@ -0,0 +1,116 @@ +package com.mussonindustrial.ignition.embr.periscope.resources.typescript + +import com.inductiveautomation.ignition.common.BundleUtil +import com.inductiveautomation.ignition.common.model.ApplicationScope +import com.inductiveautomation.ignition.common.project.Project +import com.inductiveautomation.ignition.common.project.resource.ProjectResourceBuilder +import com.inductiveautomation.ignition.common.project.resource.ResourcePath +import com.inductiveautomation.ignition.designer.model.DesignerContext +import com.inductiveautomation.ignition.designer.navtree.icon.InteractiveSvgIcon +import com.inductiveautomation.ignition.designer.navtree.model.MutableNavTreeNode +import com.inductiveautomation.ignition.designer.tabbedworkspace.* +import com.inductiveautomation.ignition.designer.workspacewelcome.RecentlyModifiedTablePanel +import com.inductiveautomation.ignition.designer.workspacewelcome.ResourceBuilderDelegate +import com.inductiveautomation.ignition.designer.workspacewelcome.ResourceBuilderPanel +import com.inductiveautomation.ignition.designer.workspacewelcome.WorkspaceWelcomePanel +import com.mussonindustrial.embr.common.logging.getLogger +import com.mussonindustrial.ignition.embr.periscope.Meta +import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource +import java.util.* +import java.util.function.Consumer +import javax.swing.JComponent +import javax.swing.JPopupMenu +import org.json.JSONException + +class TypeScriptResourceWorkspace( + private val context: DesignerContext, + private val parent: MutableNavTreeNode, +) : + TabbedResourceWorkspace( + context, + ResourceDescriptor.builder() + .resourceType(TypeScriptResource.type) + .rootFolderText("TypeScript") + .nounKey("periscope.typescript.noun") + .rootIcon(InteractiveSvgIcon(Meta::class.java, "images/svgicons/folder-closed.svg")) + .icon(InteractiveSvgIcon(Meta::class.java, "images/svgicons/tsx.svg")) + .navTreeLocation(9999999) + .scope(ApplicationScope.GATEWAY) + .build(), + ) { + + private val logger = this.getLogger() + + private fun getNewConstructor(project: Project): Consumer { + return Consumer { builder: ProjectResourceBuilder -> + try { + builder.putData(TypeScriptResource.SOURCE_KEY, ByteArray(0)) + builder.putData(TypeScriptResource.CLIENT_KEY, ByteArray(0)) + } catch (e: JSONException) { + logger.error("Error creating new TypeScript Module.", e) + } + } + } + + override fun getKey(): String { + return TypeScriptResource.type.typeId + } + + public override fun getNavTreeNodeParent(): MutableNavTreeNode { + return this.parent + } + + override fun newResourceEditor(resourcePath: ResourcePath): ResourceEditor<*> { + return TypeScriptResourceEditor(this, resourcePath) + } + + override fun addNewResourceActions(folderNode: ResourceFolderNode, menu: JPopupMenu) { + menu.add( + object : NewResourceAction(this, folderNode, getNewConstructor(context.project!!)) { + init { + putValue(NAME, "New TypeScript Module") + putValue( + SMALL_ICON, + InteractiveSvgIcon(Meta::class.java, "images/svgicons/theme.svg"), + ) + } + + override fun newResourceName() = "NewTypeScriptModule" + } + ) + } + + override fun createWorkspaceHomeTab(): Optional { + val ws = this + + return Optional.of( + object : WorkspaceWelcomePanel(BundleUtil.i18n("periscope.typescript.nouns-long")) { + override fun createPanels(): List { + return listOf( + ResourceBuilderPanel( + context, + BundleUtil.i18n("periscope.typescript.noun"), + TypeScriptResource.type.rootPath(), + listOf( + ResourceBuilderDelegate.build( + BundleUtil.i18n("periscope.typescript.noun-long"), + InteractiveSvgIcon(Meta::class.java, "images/svgicons/tsx.svg"), + getNewConstructor(context.project!!), + ) + ), + ) { path: ResourcePath? -> + ws.open(path) + }, + RecentlyModifiedTablePanel( + context, + TypeScriptResource.type, + BundleUtil.i18n("periscope.typescript.nouns-long"), + ) { path: ResourcePath? -> + ws.open(path) + }, + ) + } + } + ) + } +} diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt new file mode 100644 index 00000000..b853495e --- /dev/null +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt @@ -0,0 +1,109 @@ +package com.mussonindustrial.ignition.embr.periscope.typescript + +import com.mussonindustrial.embr.common.logging.getLoggerEx +import com.teamdev.jxbrowser.browser.Browser +import com.teamdev.jxbrowser.engine.Engine +import com.teamdev.jxbrowser.js.JsArray +import com.teamdev.jxbrowser.js.JsObject +import com.teamdev.jxbrowser.js.JsPromise +import java.io.InputStream +import java.util.concurrent.CompletableFuture + +class TypeScriptCompiler(engine: Engine) { + + val logger = this.getLoggerEx() + + private val resourcePath = "/static/embr-periscope-ts-compiler.js" + private val compilerModule = "EmbrPeriscopeTsCompiler" + + val browser: Browser = + engine.newBrowser().apply { + navigation().loadUrl("about:blank") + val jsStream: InputStream? = object {}.javaClass.getResourceAsStream(resourcePath) + if (jsStream == null) { + logger.error("Could not find $resourcePath on classpath") + } + val jsSource = jsStream?.bufferedReader().use { it?.readText() } + if (jsSource != null) { + mainFrame().get().executeJavaScript(jsSource) + } + } + val frame = browser.mainFrame().get() + + fun compile(input: String): CompilationResult? { + val compiler = frame.executeJavaScript(compilerModule)!! + val result = compiler.call("compile", input) + + return try { + CompilationResult(result) + } catch (_: Exception) { + null + } + } + + class CompilationResult(val result: JsObject) { + + companion object { + private val requiredProperties = listOf("outputText", "diagnostics") + } + + init { + if (!result.propertyNames().containsAll(requiredProperties)) { + val missingProperties = requiredProperties.toSet().minus(result.propertyNames()) + throw Exception( + "CompilationResult is missing required properties: $missingProperties" + ) + } + } + + val outputText: String + get() = result.property("outputText").get() + + val sourceMapText: String + get() = result.property("sourceMapText").get() + + val diagnostics: JsArray? + get() = result.property("diagnostics").orElse(null) + } + + fun format(source: String, cursorPosition: Int): FormatResult? { + val compiler = frame.executeJavaScript(compilerModule)!! + val result = compiler.call("format", source, cursorPosition) + logger.info("Format Result $result") + + val future = CompletableFuture() + + result + .then { it -> + logger.info("Format Result Resolution $it") + val formatResult = it[0] as JsObject + future.complete(FormatResult(formatResult)) + } + .catchError { error -> + val errors = error.toList() + val text = errors.map { frame.json().stringify(it as JsObject) } + future.completeExceptionally(Exception(text.joinToString("::"))) + } + + return future.get() + } + + class FormatResult(val result: JsObject) { + companion object { + private val requiredProperties = listOf("formatted", "cursorOffset") + } + + init { + if (!result.propertyNames().containsAll(requiredProperties)) { + val missingProperties = requiredProperties.toSet().minus(result.propertyNames()) + throw Exception("FormatResult is missing required properties: $missingProperties") + } + } + + val formatted: String + get() = result.property("formatted").get() + + val cursorOffset: Int + get() = result.property("cursorOffset").get() + } +} diff --git a/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component-disabled.svg b/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component-disabled.svg new file mode 100644 index 00000000..5f3d573e --- /dev/null +++ b/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component-disabled.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + diff --git a/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component-selected.svg b/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component-selected.svg new file mode 100644 index 00000000..40a25d54 --- /dev/null +++ b/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component-selected.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + diff --git a/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component.icon.svg b/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component.icon.svg new file mode 100644 index 00000000..3e1a1369 --- /dev/null +++ b/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/component.icon.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + diff --git a/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/thumbnails/base.png b/modules/periscope/designer/src/main/resources/images/components/embr.periscope.embedding.react/thumbnails/base.png new file mode 100644 index 0000000000000000000000000000000000000000..a00f4a6a97c9621dcd2b6ef4c67677f70bb42aac GIT binary patch literal 2891 zcmb`JXFMB<8pdOt5PNS9rPQWcRcbZVC}I~eilnVkl%i&h^bo`dQKM;NCHAhF#;8&y z=&@JLsO@TNN3VN-_sji$K0NR9`+t5uy#FMK#XVM#5C{MOu$mZytuGsMna7N@m$%>y zJ9k+$LDu&S0M%rXjZ2_^q;IYd0Mw;1pFd={WTpUP#~=WJt@}Tr;gvOg2>^f=O~CrL zVXiv`PawjRqMc1nOf+qrv>&6AdR`0ksWPeSsOW5kzrLAUie{%ynaH5Mgkrq$xliyr z_(rrzY0GtU-QcfNLrYgfmahbgcmM}e6FL*k3|`W=*-uuJ$Gw~7>)z|v6@3&`@uyJX zz~kRdPDw`*XA5Zfg1Cf9CXhx@=SY}V0V1)lhv=l^rDeBe)IM^{P-F3Ql%V9C>%@5p zC^5aTWu!O8so`5KO0tq##%}KxrKiYLA1ZF79u64bpmKUG>Z=+0(gF&4n$(VJ)TYZX zZ?B@vU<6a1X6OqH<%i7&!#2#7QU~+%8N@wkfV~s^^9wG@*d$XxS23 zO4>7haq@kxK;NC_qG`G|VM_r$zPoL+jI*_b)Q+7D1xp{N~KHnhKF0+-(#BvoFG zl2f`sfqF(iI(RPVC3=^0t$(f$3&@+hFN+Kb>)+^gPqv@1mRb9up?=gOD|PL3Kru8{ zt)j}2Y4Vn{kxD_!*S+c*j=is@t+q&ixxo(KTxU>&u59K9HUBkY~Q;y(-1UI_IK#{!MHu2q(R6Cg((=^!Um z`4y>8r@TM)Xh9$l0?q1%9iML_aNt+0`mwPj(r8-X@8~!wjN<$6Z{%nhdFvGwfIVHk zh7Mk{4t!HkgmIio7Rms`c@2tRubvZiwF}kr*VM|t%HzNS*R@1i z=CyH?LsK1fj{`;@j|i5E+-jY`76ZC+9z)b7UxJD2`i z+4kGPYsRpcw7^+zzZp$H2g~(jx?2iQZcw&kkDXAsIkQiJ#OQ^kqFrq73RCZGHog9oS56!^WD5_B@nWH9VvrP&{)4D3KD>qMT-{gs2CZ<*8%yFd$LAD1(T zaK|_M(bTm6{+m6V|J+xyQTy;;B{V=v?pAwPmpknG)K31Mi|D5goiFagudiz&uf~ym z#7Vjxt4t3uT^ACT^-6%98_?6*&>7)&a{-DW_5F8GJE5#v=xH+ZdRZCeX@9dx&*+TpIRE#4b?zuc|m8xA*5dvXMf_!+Y3#0;j59EclxvrMQ=wx zsOVf{m0{b~3O;zGZAyA6)83F!7P%R=enGyXJMVV5)*qI@FSc`HFcrNFxz|?~-)`@} z>C2-trfVH|K7HjcHZiRkjOA6049}~?+$ZNJpL@;@#iwU&?@MSW)JBKc=F)-EU@}`i9TA?M!s;B1971xtdt$gb;_^CjzX?0CaSfk%2 zv4s~ji;Y<7l^Sj?*(4cet2u7M7ziB?a=ovCUrR4zq>7#L!g(?VY+r;FBu0obAJ3J9 zUX0F1QR2lcJGu5xx??M!ArkH+k*#vd(06oKnZ3$DBeqF=FI?-8a1VNl1iXgYes=#T zx8ZH7k`|p8eKWWOR@O$70=AN#gRCO?`1c4X!Sx-sFD!-A*S>t1RV)5-QzLY1S$8-% zltd+|N;Yy8o7owXdDF&sSM9W~U8^Uz)bi@uLkSPAC#vx5KA#oc)yoQ)-eJS*-W@`U zf3lYa)63@F{-8>DCo8^o(LpDa0=8+eRTl!St-+b4y;JL{t^BUxYil_czjudYj?Ha8 zfD7L2m)IK_OM(|aCsRzN&;R^z6V+P;=Fz9u1Z8+1HISlnqn0a3)W5ZAw6Aa_nIWv) zRHhw{y?F2%m7dc~b!{h1-UpuuWSdEAbktrIJSo|96Lsge){&nF$>zvsRlbe>BTJtsKqG#F zmp-Fopk~u;?T$s3TZ5i$C$imlVR>|GAJ>MBe3jnz!Lcr^Dh4nYo5RH%H)SniDbsu= zyIVwWd*ax*S|mig=Bd&gWU0vM3>>z9I#!?D3FT`L#B(H-r~I^3`r&NK6Zzax;^t@r z!Ecb?@4?VMedY}H7cym>En4NfPYv-CHFx<(^cFWO)ZfX|{p+`d%Eh=nrfv)7CBhTa zM+LcX_GT5g{I}eNL7Vbl7#P!#h7d#CRpp#Mm8A#ZqO$CwV&nE^wObLM{j%QM-)EfB zz3X3Y{A!xU;+*5rW)`^8a%*Wt!ehvB3h^`-4D)fmA*o1^hw$IbHjz2?M`>|a0cV(b ztV#sL1md;6&e9*2x5>7ylZA= zgyqral7783cB~C23Y$DrSpEgYWkaM zp^M1}rW0-#4?E8TbvY+7^VdC&J|;M}vf7y{!G$$FX=+GsoVBTu()@bwF&n+XW1nS(vuL-bV-*bn>5}H_|j=mezy|1Qozoo4aZ~E);&QnKysQp#z9%8 zZ+SRMfYUOMD| fH+tE#LKXjmZoeg&{_N~hfB`0k7T{_F_qcxp0EK`Q literal 0 HcmV?d00001 diff --git a/modules/periscope/gateway/build.gradle.kts b/modules/periscope/gateway/build.gradle.kts index 5b8a2bf6..b64ba2a6 100644 --- a/modules/periscope/gateway/build.gradle.kts +++ b/modules/periscope/gateway/build.gradle.kts @@ -10,4 +10,8 @@ dependencies { modlImplementation(projects.libraries.perspective.gateway) compileOnly(projects.modules.periscope.common) modlImplementation(projects.modules.periscope.web) + compileOnly(libs.jetty.server) + compileOnly(libs.jetty.servlet) + modlImplementation(libs.jetty.servlets) + modlImplementation(projects.libraries.core.servlets) } \ No newline at end of file diff --git a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayContext.kt b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayContext.kt index aa072bdd..f5b05d1d 100644 --- a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayContext.kt +++ b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayContext.kt @@ -1,9 +1,16 @@ package com.mussonindustrial.ignition.embr.periscope +import com.inductiveautomation.ignition.gateway.dataroutes.RouteAccessControl +import com.inductiveautomation.ignition.gateway.model.DiagnosticsManager import com.inductiveautomation.ignition.gateway.model.GatewayContext +import com.inductiveautomation.ignition.gateway.model.TelemetryManager import com.inductiveautomation.perspective.common.PerspectiveModule +import com.inductiveautomation.perspective.gateway.GatewayHook import com.inductiveautomation.perspective.gateway.api.PerspectiveContext +import com.inductiveautomation.perspective.gateway.api.SessionScope +import com.inductiveautomation.perspective.gateway.comm.Routes import com.inductiveautomation.perspective.gateway.model.PageModel +import com.mussonindustrial.embr.common.reflect.getPrivateMethod import com.mussonindustrial.embr.gateway.EmbrGatewayContext import com.mussonindustrial.embr.gateway.EmbrGatewayContextImpl import com.mussonindustrial.embr.perspective.common.component.addResourcesTo @@ -13,7 +20,9 @@ import com.mussonindustrial.embr.perspective.gateway.component.asGatewayComponen import com.mussonindustrial.embr.perspective.gateway.component.registerComponent import com.mussonindustrial.embr.perspective.gateway.component.removeComponent import com.mussonindustrial.embr.perspective.gateway.reflect.ViewLoader +import com.mussonindustrial.embr.servlets.ModuleServletManager import com.mussonindustrial.ignition.embr.periscope.component.embedding.* +import java.util.EnumSet import java.util.WeakHashMap class PeriscopeGatewayContext(private val context: GatewayContext) : @@ -22,7 +31,8 @@ class PeriscopeGatewayContext(private val context: GatewayContext) : lateinit var instance: PeriscopeGatewayContext } - val perspectiveContext: PerspectiveContext + val servletManager = ModuleServletManager(context.webResourceManager, "/embr/periscope") + val perspectiveContext: GatewayHook.PerspectiveGatewayContext private val components = listOf( EmbeddedView.asGatewayComponent { EmbeddedViewModelDelegate(it) }, @@ -30,11 +40,13 @@ class PeriscopeGatewayContext(private val context: GatewayContext) : JsonView.asGatewayComponent { JsonViewModelDelegate(it) }, Portal.asGatewayComponent(), Swiper.asGatewayComponent { JavaScriptProxyableComponentModelDelegate(it) }, + React.asGatewayComponent { JavaScriptProxyableComponentModelDelegate(it) }, ) init { instance = this - perspectiveContext = PerspectiveContext.get(context) + perspectiveContext = + PerspectiveContext.get(context) as GatewayHook.PerspectiveGatewayContext } private val viewLoaders = WeakHashMap() @@ -72,4 +84,21 @@ class PeriscopeGatewayContext(private val context: GatewayContext) : it.moduleId() == PerspectiveModule.MODULE_ID } } + + fun removeServlets() { + servletManager.removeAllServlets() + } + + override fun getTelemetryManager(): TelemetryManager? { + return super.getTelemetryManager() + } + + override fun getDiagnosticsManager(): DiagnosticsManager? { + return super.getDiagnosticsManager() + } + + fun requireSession(scopes: EnumSet): RouteAccessControl { + val internal = Routes::class.java.getPrivateMethod("requireSession", EnumSet::class.java) + return internal.invoke(Routes::class.java, scopes) as RouteAccessControl + } } diff --git a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayHook.kt b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayHook.kt index 6e9500c4..04d5a395 100644 --- a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayHook.kt +++ b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayHook.kt @@ -4,10 +4,12 @@ import com.inductiveautomation.ignition.common.BundleUtil import com.inductiveautomation.ignition.common.licensing.LicenseState import com.inductiveautomation.ignition.common.script.ScriptManager import com.inductiveautomation.ignition.common.script.hints.PropertiesFileDocProvider +import com.inductiveautomation.ignition.gateway.dataroutes.RouteGroup import com.inductiveautomation.ignition.gateway.model.AbstractGatewayModuleHook import com.inductiveautomation.ignition.gateway.model.GatewayContext import com.mussonindustrial.ignition.embr.periscope.Meta.SHORT_MODULE_ID -import com.mussonindustrial.ignition.embr.periscope.component.embedding.* +import com.mussonindustrial.ignition.embr.periscope.handlers.ClientResourceJavaScriptHandler +import com.mussonindustrial.ignition.embr.periscope.handlers.ClientResourceManifestHandler import com.mussonindustrial.ignition.embr.periscope.scripting.JavaScriptFunctions import com.mussonindustrial.ignition.embr.periscope.scripting.QueueFunctions import java.util.* @@ -45,6 +47,9 @@ class PeriscopeGatewayHook : AbstractGatewayModuleHook() { logger.debug("Removing components...") context.removeComponents() + + logger.debug("Removing servlets...") + context.removeServlets() } override fun getMountedResourceFolder(): Optional { @@ -80,4 +85,11 @@ class PeriscopeGatewayHook : AbstractGatewayModuleHook() { PropertiesFileDocProvider(), ) } + + override fun mountRouteHandlers(routes: RouteGroup) { + ClientResourceManifestHandler(context) + .mount(routes.newRoute("/client-resources/:project_name/manifest.json")) + ClientResourceJavaScriptHandler(context) + .mount(routes.newRoute("/client-resources/:project_name/js/*")) + } } diff --git a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/handlers/ClientResourceJavaScriptHandler.kt b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/handlers/ClientResourceJavaScriptHandler.kt new file mode 100644 index 00000000..636070d7 --- /dev/null +++ b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/handlers/ClientResourceJavaScriptHandler.kt @@ -0,0 +1,106 @@ +package com.mussonindustrial.ignition.embr.periscope.handlers + +import com.inductiveautomation.ignition.common.project.resource.ResourcePath +import com.inductiveautomation.ignition.gateway.dataroutes.HttpMethod +import com.inductiveautomation.ignition.gateway.dataroutes.RequestContext +import com.inductiveautomation.ignition.gateway.dataroutes.RouteGroup +import com.inductiveautomation.ignition.gateway.dataroutes.RouteHandler +import com.inductiveautomation.perspective.gateway.api.SessionScope +import com.mussonindustrial.embr.common.logging.getLoggerEx +import com.mussonindustrial.ignition.embr.periscope.PeriscopeGatewayContext +import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource +import com.mussonindustrial.ignition.embr.periscope.utils.getHashKey +import java.net.URLDecoder +import java.util.EnumSet +import javax.servlet.http.HttpServletResponse + +class ClientResourceJavaScriptHandler(val context: PeriscopeGatewayContext) : RouteHandler { + + val logger = this.getLoggerEx() + val resourceNameRegex = Regex("""^(.*)\.([^.]+)\.js$""") + + fun mount(mounter: RouteGroup.RouteMounter) { + mounter + .method(HttpMethod.GET) + .type("text/javascript") + .restrict(context.requireSession(EnumSet.allOf(SessionScope::class.java))) + .handler(this) + .mount() + } + + override fun handle(request: RequestContext, response: HttpServletResponse) { + logger.trace("JS resource request: $request") + + val projectName = + request.getParameter("project_name") + ?: return response.sendError( + HttpServletResponse.SC_BAD_REQUEST, + "Missing project name", + ) + + val resourcePath = request.path.substringAfter("$projectName/js/") + val resourceName = URLDecoder.decode(resourcePath, Charsets.UTF_8) + logger.trace("Project: $projectName, Resource: $resourceName") + + val parsed = + parseResourceName(resourceName) + ?: return response.sendError( + HttpServletResponse.SC_NOT_FOUND, + "Invalid resource name: $resourceName", + ) + + val project = context.projectManager.getProject(projectName).orElse(null) + if (project == null) { + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Project not found: $projectName") + return + } + + val resource = + project.getResource(ResourcePath(TypeScriptResource.type, parsed.pathName)).orElse(null) + if (resource == null) { + response.sendError( + HttpServletResponse.SC_NOT_FOUND, + "Resource not found: ${parsed.fullName}", + ) + return + } + + if (resource.getHashKey() != parsed.hash) { + response.sendError( + HttpServletResponse.SC_NOT_FOUND, + "Invalid resource hash: ${parsed.hash}", + ) + return + } + + val data = resource.getData(TypeScriptResource.CLIENT_KEY) + if (data == null) { + response.sendError( + HttpServletResponse.SC_NOT_FOUND, + "No data for resource: ${parsed.fullName}", + ) + return + } + + response.apply { + contentType = "text/javascript" + setHeader("Cache-Control", "public, max-age=604800, immutable") + outputStream.use { it.write(data) } + } + } + + private data class ParsedResourceName( + val fullName: String, + val pathName: String, + val hash: String, + ) + + private fun parseResourceName(resourceName: String): ParsedResourceName? { + val match = resourceNameRegex.find(resourceName) ?: return null + val (pathName, hash) = match.destructured + + logger.trace("resourceName: $resourceName, pathName: $pathName, hash: $hash") + + return ParsedResourceName(resourceName, pathName, hash) + } +} diff --git a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/handlers/ClientResourceManifestHandler.kt b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/handlers/ClientResourceManifestHandler.kt new file mode 100644 index 00000000..d099b940 --- /dev/null +++ b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/handlers/ClientResourceManifestHandler.kt @@ -0,0 +1,68 @@ +package com.mussonindustrial.ignition.embr.periscope.handlers + +import com.inductiveautomation.ignition.common.gson.JsonArray +import com.inductiveautomation.ignition.common.gson.JsonObject +import com.inductiveautomation.ignition.gateway.dataroutes.HttpMethod +import com.inductiveautomation.ignition.gateway.dataroutes.RequestContext +import com.inductiveautomation.ignition.gateway.dataroutes.RouteGroup +import com.inductiveautomation.ignition.gateway.dataroutes.RouteHandler +import com.inductiveautomation.perspective.gateway.api.SessionScope +import com.mussonindustrial.embr.common.logging.getLoggerEx +import com.mussonindustrial.embr.gateway.api.sendSuccess +import com.mussonindustrial.ignition.embr.periscope.PeriscopeGatewayContext +import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource +import com.mussonindustrial.ignition.embr.periscope.utils.getHashKey +import java.util.EnumSet +import javax.servlet.http.HttpServletResponse + +class ClientResourceManifestHandler(val context: PeriscopeGatewayContext) : RouteHandler { + + val logger = this.getLoggerEx() + + fun mount(routeMounter: RouteGroup.RouteMounter) { + routeMounter + .method(HttpMethod.GET) + .type("application/json") + .restrict(context.requireSession(EnumSet.allOf(SessionScope::class.java))) + .handler(this) + .mount() + } + + override fun handle(request: RequestContext, response: HttpServletResponse) { + val projectName = request.getParameter("project_name") + logger.trace("Manifest request for project: $projectName") + + val project = context.projectManager.getProject(projectName).orElse(null) + if (project == null) { + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Project not found: $projectName") + return + } + + val librariesArray = JsonArray() + + val resources = project.getResourcesOfType(TypeScriptResource.type) + logger.trace("Found ${resources.size} TypeScript resources") + + val jsArray = + JsonArray().apply { + resources.forEach { + add( + JsonObject().apply { + addProperty("path", it.resourcePath.path.toString()) + addProperty("hash", it.getHashKey()) + } + ) + } + } + + val json = + JsonObject().apply { + add("js", jsArray) + add("libraries", librariesArray) + } + + response.setHeader("Cache-Control", "no-store") + response.sendSuccess(json) + return + } +} diff --git a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/utils/ProjectResourceUtils.kt b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/utils/ProjectResourceUtils.kt new file mode 100644 index 00000000..9c41f9e5 --- /dev/null +++ b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/utils/ProjectResourceUtils.kt @@ -0,0 +1,8 @@ +package com.mussonindustrial.ignition.embr.periscope.utils + +import com.inductiveautomation.ignition.common.project.resource.ProjectResource +import org.apache.commons.codec.binary.Hex + +fun ProjectResource.getHashKey(): String { + return Hex.encodeHexString(resourceSignature.signature).substring(0, 20) +} diff --git a/modules/periscope/gateway/src/main/resources/localization.properties b/modules/periscope/gateway/src/main/resources/localization.properties index a2d98d6e..27aa7a5a 100644 --- a/modules/periscope/gateway/src/main/resources/localization.properties +++ b/modules/periscope/gateway/src/main/resources/localization.properties @@ -15,3 +15,11 @@ script.invokeOnQueue.desc=Run a function on a Perspective execution queue, with script.invokeOnQueue.param.function=Python function to run. Should be a function reference that takes a single "scope" parameter. script.invokeOnQueue.param.delay=Optional. Time in milliseconds before running the function. script.invokeOnQueue.param.scope=Optional. Lifecycle scope. If specified, must be "session", "page", or "view", otherwise the default will be "view". +javascript-module.noun=JavaScript Module +javascript-module.nouns=JavaScript Modules +javascript-module.noun-long=JavaScript Module +javascript-module.nouns-long=JavaScript Modules +javascript-module.error.notFound=JavaScript Module not found. +javascript-module.error.existingPalette=JavaScript Module '%s' is already defined +javascript-module.error.emptyPalette=JavaScript Module name must not be empty +javascript-module.action.new=New JavaScript Module diff --git a/modules/periscope/ts-compiler/build.gradle.kts b/modules/periscope/ts-compiler/build.gradle.kts new file mode 100644 index 00000000..182f26db --- /dev/null +++ b/modules/periscope/ts-compiler/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("embr.build.ignition-webjar") +} \ No newline at end of file diff --git a/modules/periscope/ts-compiler/package.json b/modules/periscope/ts-compiler/package.json new file mode 100644 index 00000000..849784b3 --- /dev/null +++ b/modules/periscope/ts-compiler/package.json @@ -0,0 +1,17 @@ +{ + "name": "@embr-modules/periscope-ts-compiler", + "private": true, + "version": "0.9.0", + "scripts": { + "dev": "vite", + "build": "tsc && nx vite:build && nx vite:test --run", + "clean": "s", + "test": "nx vite:test --run", + "preview": "nx vite:test --preview", + "lint": "eslint src/**/*.ts", + "lint:prettier": "prettier -c . --cache --ignore-path=../../.prettierignore" + }, + "dependencies": { + "@embr-js/utils": "0.6.1" + } +} diff --git a/modules/periscope/ts-compiler/src/index.ts b/modules/periscope/ts-compiler/src/index.ts new file mode 100644 index 00000000..51c51cb4 --- /dev/null +++ b/modules/periscope/ts-compiler/src/index.ts @@ -0,0 +1,29 @@ +import * as prettier from 'prettier/standalone' +import * as parserTypeScript from 'prettier/parser-typescript' +import * as prettierPluginEstree from 'prettier/plugins/estree' + +import ts from 'typescript' + +export function compile(input: string) { + return ts.transpileModule(input, { + compilerOptions: { + module: ts.ModuleKind.ESNext, + target: ts.ScriptTarget.ES2020, + strict: true, + sourceMap: false, + jsx: ts.JsxEmit.React, + }, + }) +} + +export async function format(source: string, cursorOffset: number) { + return await prettier.formatWithCursor(source, { + semi: false, + singleQuote: true, + useTabs: false, + endOfLine: 'lf', + cursorOffset, + parser: 'typescript', + plugins: [prettierPluginEstree as unknown as string, parserTypeScript], + }) +} diff --git a/modules/periscope/ts-compiler/tsconfig.json b/modules/periscope/ts-compiler/tsconfig.json new file mode 100644 index 00000000..471a66e8 --- /dev/null +++ b/modules/periscope/ts-compiler/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@embr-js/tsconfig/vite.json", + "include": ["src"] +} diff --git a/modules/periscope/ts-compiler/vite.config.ts b/modules/periscope/ts-compiler/vite.config.ts new file mode 100644 index 00000000..91aac42b --- /dev/null +++ b/modules/periscope/ts-compiler/vite.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' + +const packageName = 'embr-periscope-ts-compiler' + +export default defineConfig(({ mode }) => ({ + build: { + outDir: './dist', + lib: { + entry: resolve(__dirname, 'src/index.ts'), + fileName: () => { + return `${packageName}.js` + }, + cssFileName: `${packageName}`, + name: 'EmbrPeriscopeTsCompiler', + formats: ['umd'], + }, + }, + test: { + passWithNoTests: true, + }, + define: { + 'process.env.NODE_ENV': JSON.stringify(mode), + }, +})) diff --git a/modules/periscope/web/src/client.ts b/modules/periscope/web/src/client.ts index 10f60e92..9ff8303e 100644 --- a/modules/periscope/web/src/client.ts +++ b/modules/periscope/web/src/client.ts @@ -10,6 +10,8 @@ import { JsonViewComponentMeta, PortalComponent, PortalComponentMeta, + ReactComponent, + ReactComponentMeta, } from './components' import { installExtensions } from './extensions' import { waitForClientStore } from '@embr-js/perspective-client' @@ -20,6 +22,7 @@ export { EmbeddedViewComponent, JsonViewComponent, PortalComponent, + ReactComponent, } const components = [ @@ -28,6 +31,7 @@ const components = [ new EmbeddedViewComponentMeta(), new JsonViewComponentMeta(), new PortalComponentMeta(), + new ReactComponentMeta(), ] components.forEach((c) => ComponentRegistry.register(c)) diff --git a/modules/periscope/web/src/components/embedding/index.ts b/modules/periscope/web/src/components/embedding/index.ts index 4ef72e11..655b2fee 100644 --- a/modules/periscope/web/src/components/embedding/index.ts +++ b/modules/periscope/web/src/components/embedding/index.ts @@ -1,5 +1,6 @@ export * from './flex-repeater' export * from './json-view' export * from './portal' +export * from './react' export * from './swiper' export * from './view' diff --git a/modules/periscope/web/src/components/embedding/react.tsx b/modules/periscope/web/src/components/embedding/react.tsx new file mode 100644 index 00000000..f1b56d9b --- /dev/null +++ b/modules/periscope/web/src/components/embedding/react.tsx @@ -0,0 +1,146 @@ +import React, { + createElement, + lazy, + MutableRefObject, + Suspense, + useEffect, + useRef, +} from 'react' +import { + AbstractUIElementStore, + ComponentMeta, + ComponentProps, + ComponentStoreDelegate, + JsObject, + PComponent, + PlainObject, + PropertyTree, + SizeObject, + StyleObject, +} from '@inductiveautomation/perspective-client' +import { + ComponentDelegateJavaScriptProxy, + ComponentEvents, + ComponentLifecycleEvents, + getScriptTransform, + JavaScriptRunEvent, + useDeepCompareMemo, +} from '@embr-js/perspective-client' +import { transformProps } from '@embr-js/utils' + +import { getClientResource } from '../../extensions/client-resources' + +const COMPONENT_TYPE = 'embr.periscope.embedding.react' + +type ReactComponentProps = Record + +type ReactProps = { + component: string + props: ReactComponentProps + events: ComponentEvents & { + target: { + lifecycle: ComponentLifecycleEvents + } + } + style: StyleObject +} + +export function ReactComponent(props: ComponentProps) { + const ref: MutableRefObject = useRef(undefined) + + // Register the chart with the component delegate + useEffect(() => { + const delegate = props.store.delegate as ReactComponentDelegate + delegate.setProxyRef(ref.current as never) + }, [props.store.delegate, ref.current]) + + const component = useDeepCompareMemo(() => { + if (props.props.component == '') { + return null + } + + return lazy(() => + getClientResource( + props.store.view.page.parent, + props.props.component + ).then((resource) => { + if (resource == null) { + throw Error('Component Failed to Load') + } + return resource + }) + ) + }, [props.props.component]) + + if (component == null) { + return null + } + + // Apply transforms to the user supplied properties + const innerProps = useDeepCompareMemo(() => { + const transformedProps = transformProps(props.props.props, [ + getScriptTransform(props, props.store), + ]) as ReactComponentProps + + return { + ...transformedProps, + ref, + } as unknown + }, [props.props.props, ref]) as ReactComponentProps + + return ( +
+
}> + {createElement(component, innerProps)} + + + ) +} + +export class ReactComponentDelegate extends ComponentStoreDelegate { + private proxy = new ComponentDelegateJavaScriptProxy(this) + + setProxyRef(ref?: object) { + this.proxy.setRef(ref) + } + + handleEvent(eventName: string, eventObject: JsObject) { + if (this.proxy.handles(eventName)) { + this.proxy.handleEvent(eventObject as JavaScriptRunEvent) + } + } + + mapStateToProps(): PlainObject { + return this + } +} + +export class ReactComponentMeta implements ComponentMeta { + getComponentType(): string { + return COMPONENT_TYPE + } + + getDefaultSize(): SizeObject { + return { + width: 300, + height: 300, + } + } + + createDelegate(component: AbstractUIElementStore): ComponentStoreDelegate { + return new ReactComponentDelegate(component) + } + + getPropsReducer(tree: PropertyTree): ReactProps { + return { + component: tree.readString('component', ''), + props: tree.readObject('props', {}), + events: tree.readObject('events', {}), + style: tree.readStyle('style'), + } as never + } + + getViewComponent(): PComponent { + return ReactComponent as PComponent + } +} diff --git a/modules/periscope/web/src/extensions/client-resources/index.ts b/modules/periscope/web/src/extensions/client-resources/index.ts new file mode 100644 index 00000000..a252f0be --- /dev/null +++ b/modules/periscope/web/src/extensions/client-resources/index.ts @@ -0,0 +1,116 @@ +import { ClientStore } from '@inductiveautomation/perspective-client' +import { merge } from 'lodash' +import { getEmbrGlobals } from '@embr-js/perspective-client/src/globals' + +export type JsResource = { + path: string + hash: string +} + +export type ResourceManifest = { + js: JsResource[] +} + +export type ResourceManifestResponse = { + data: ResourceManifest +} +let loadedResourceManifest: ResourceManifest | undefined +let manifestPromise: Promise | undefined + +export async function getResourceManifest(clientStore: ClientStore) { + if (loadedResourceManifest) { + return loadedResourceManifest + } + + if (manifestPromise) return manifestPromise + + const { projectName } = clientStore + const paths = buildPaths(projectName) + + manifestPromise = (async () => { + const response = await fetch(paths.manifest) + if (!response.ok) + throw new Error(`Failed to fetch manifest: ${response.statusText}`) + + const manifestResponseJson = + (await response.json()) as ResourceManifestResponse + + loadedResourceManifest = manifestResponseJson.data + manifestPromise = undefined + return loadedResourceManifest + })() + + return manifestPromise +} + +export async function getClientResource( + clientStore: ClientStore, + path: string +) { + const { projectName } = clientStore + const { jsBase } = buildPaths(projectName) + + const manifest = await getResourceManifest(clientStore) + const hash = manifest.js.find((resource) => resource.path == path)?.hash + if (!hash) { + return null + } + + try { + return await import(`${jsBase}/${encodeURI(path)}.${hash}.js`) + } catch (err) { + console.error(`Failed to load ClientResource '${path}':`, err) + return null + } +} + +function buildPaths(projectName: string) { + const base = '/data/embr-periscope/client-resources' + return { + base, + jsBase: `${base}/${projectName}/js`, + manifest: `${base}/${projectName}/manifest.json`, + } +} + +export function installClientResourceImport(clientStore: ClientStore) { + merge(getEmbrGlobals().scripting.globals, { + periscope: { + import: async (path: string) => + await getClientResource(clientStore, path), + }, + }) +} + +export function installClientResourceImportMap(clientStore: ClientStore) { + const { projectName } = clientStore + const paths = buildPaths(projectName) + + const importMap = { + scopes: { + [paths.jsBase + '/']: { + '@/': `${paths.jsBase}/`, + }, + }, + } + + document.querySelector('script[data-import-map="periscope"]')?.remove() + + const script = document.createElement('script') + script.type = 'importmap' + script.dataset.importMap = 'periscope' + script.textContent = JSON.stringify(importMap, null, 2) + void ( + document.currentScript?.after(script) ?? document.head.appendChild(script) + ) +} + +export const PROTOCOL = { + REFRESH: 'periscope-client-resource-refresh', +} + +export function installClientResourceListener(clientStore: ClientStore) { + clientStore.connection.handlers.set(PROTOCOL.REFRESH, async () => { + window.location.reload() + }) +} diff --git a/modules/periscope/web/src/extensions/index.ts b/modules/periscope/web/src/extensions/index.ts index 0eefb679..69e7a711 100644 --- a/modules/periscope/web/src/extensions/index.ts +++ b/modules/periscope/web/src/extensions/index.ts @@ -1,10 +1,20 @@ import { ClientStore } from '@inductiveautomation/perspective-client' +import { + installClientResourceImportMap, + installClientResourceListener, + installClientResourceImport, + getResourceManifest, +} from './client-resources' import { installRunJavaScript } from './runJavaScript' import { installToasts } from './toast' export * from './runJavaScript' export function installExtensions(clientStore: ClientStore) { + getResourceManifest(clientStore).then(() => {}) installRunJavaScript(clientStore) installToasts(clientStore) + installClientResourceImportMap(clientStore) + installClientResourceImport(clientStore) + installClientResourceListener(clientStore) } diff --git a/settings.gradle.kts b/settings.gradle.kts index af3c29fa..a2147cca 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -43,6 +43,7 @@ include( ":modules:periscope:common", ":modules:periscope:designer", ":modules:periscope:gateway", + ":modules:periscope:ts-compiler", ":modules:periscope:web", ":modules:thermo:common", diff --git a/yarn.lock b/yarn.lock index eeb53843..d95918fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,11 +19,46 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.9", "@babel/compat-data@^7.26.0": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02" integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g== +"@babel/compat-data@^7.27.2": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" + integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== + +"@babel/core@7.27.4": + version "7.27.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.4.tgz#cc1fc55d0ce140a1828d1dd2a2eba285adbfb3ce" + integrity sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.27.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.27.3" + "@babel/helpers" "^7.27.4" + "@babel/parser" "^7.27.4" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.27.4" + "@babel/types" "^7.27.3" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/core@^7.23.2": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" @@ -56,6 +91,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" +"@babel/generator@^7.27.3", "@babel/generator@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" @@ -63,6 +109,13 @@ dependencies: "@babel/types" "^7.25.9" +"@babel/helper-annotate-as-pure@^7.27.1": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" + integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== + dependencies: + "@babel/types" "^7.27.3" + "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" @@ -74,6 +127,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83" @@ -107,6 +171,11 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + "@babel/helper-member-expression-to-functions@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" @@ -123,6 +192,14 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + "@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" @@ -132,6 +209,15 @@ "@babel/helper-validator-identifier" "^7.25.9" "@babel/traverse" "^7.25.9" +"@babel/helper-module-transforms@^7.27.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + "@babel/helper-optimise-call-expression@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" @@ -144,6 +230,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== +"@babel/helper-plugin-utils@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + "@babel/helper-remap-async-to-generator@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92" @@ -175,16 +266,31 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + "@babel/helper-validator-option@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + "@babel/helper-wrap-function@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0" @@ -202,6 +308,21 @@ "@babel/template" "^7.25.9" "@babel/types" "^7.26.0" +"@babel/helpers@^7.27.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.6", "@babel/parser@^7.27.2", "@babel/parser@^7.27.4", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + "@babel/parser@^7.25.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" @@ -290,6 +411,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" +"@babel/plugin-syntax-jsx@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-syntax-typescript@^7.25.9", "@babel/plugin-syntax-typescript@^7.3.3": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" @@ -602,6 +730,39 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" +"@babel/plugin-transform-react-display-name@^7.27.1": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz#6f20a7295fea7df42eb42fed8f896813f5b934de" + integrity sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-development@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz#47ff95940e20a3a70e68ad3d4fcb657b647f6c98" + integrity sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.27.1" + +"@babel/plugin-transform-react-jsx@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz#1023bc94b78b0a2d68c82b5e96aed573bcfb9db0" + integrity sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/plugin-transform-react-pure-annotations@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz#339f1ce355eae242e0649f232b1c68907c02e879" + integrity sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-transform-regenerator@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b" @@ -799,6 +960,18 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" +"@babel/preset-react@7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.27.1.tgz#86ea0a5ca3984663f744be2fd26cb6747c3fd0ec" + integrity sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-transform-react-display-name" "^7.27.1" + "@babel/plugin-transform-react-jsx" "^7.27.1" + "@babel/plugin-transform-react-jsx-development" "^7.27.1" + "@babel/plugin-transform-react-pure-annotations" "^7.27.1" + "@babel/preset-typescript@^7.22.5": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" @@ -836,6 +1009,11 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/standalone@7.27.4": + version "7.27.4" + resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.27.4.tgz#640fb78bebcf4bda10ef2ba75c12e6a20ff9ce53" + integrity sha512-vFUF+yFDDyR3+ZUOvYeLMLb6Vl1l6LCvp61/0kMzRyB3Z6ljPz5rxbmsFVBZcm6Mvu9QyaZuJQIXYZ/DA20MfQ== + "@babel/template@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" @@ -845,6 +1023,15 @@ "@babel/parser" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + "@babel/traverse@^7.16.0", "@babel/traverse@^7.25.9": version "7.26.4" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" @@ -858,6 +1045,27 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.4", "@babel/traverse@^7.28.3": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.6", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/types@^7.25.4", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.4.4": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" @@ -1447,6 +1655,14 @@ dependencies: "@sinclair/typebox" "^0.34.0" +"@jridgewell/gen-mapping@^0.3.12": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -1479,6 +1695,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@jridgewell/trace-mapping@^0.3.29": version "0.3.29" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz#a58d31eaadaf92c6695680b2e1d464a9b8fbf7fc" @@ -2046,6 +2270,51 @@ dependencies: tslib "^2.4.0" +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*", "@types/babel__generator@^7.6.8": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__standalone@7.1.9": + version "7.1.9" + resolved "https://registry.yarnpkg.com/@types/babel__standalone/-/babel__standalone-7.1.9.tgz#51ae2d49beaee9a8be1cd6a2997af83a5077d03a" + integrity sha512-IcCNPLqpevUD7UpV8QB0uwQPOyoOKACFf0YtYWRHcmxcakaje4Q7dbG2+jMqxw/I8Zk0NHvEps66WwS7z/UaaA== + dependencies: + "@babel/parser" "^7.25.6" + "@babel/types" "^7.25.6" + "@types/babel__core" "^7.20.5" + "@types/babel__generator" "^7.6.8" + "@types/babel__template" "^7.4.4" + "@types/babel__traverse" "^7.20.6" + +"@types/babel__template@*", "@types/babel__template@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.20.6": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + "@types/chai@^5.2.2": version "5.2.2" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.2.tgz#6f14cea18180ffc4416bc0fd12be05fdd73bdd6b" @@ -9131,7 +9400,7 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9149,6 +9418,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -9268,7 +9546,7 @@ stringstream@~0.0.4: resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9289,6 +9567,13 @@ strip-ansi@^4.0.0, strip-ansi@~4.0.0: dependencies: ansi-regex "^3.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -9768,6 +10053,11 @@ typescript@5.9.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== +typescript@5.9.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + ua-parser-js@^0.7.31: version "0.7.39" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.39.tgz#c71efb46ebeabc461c4612d22d54f88880fabe7e" @@ -10303,7 +10593,7 @@ worker-farm@~1.3.1: errno ">=0.1.1 <0.2.0-0" xtend ">=4.0.0 <4.1.0-0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -10320,6 +10610,15 @@ wrap-ansi@^2.0.0: string-width "^1.0.1" strip-ansi "^3.0.1" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 112b724ada14c1640e2a434d0cfaf596bfab917c Mon Sep 17 00:00:00 2001 From: Ben Musson Date: Fri, 6 Mar 2026 00:53:29 -0500 Subject: [PATCH 2/6] loading seems to work --- .../perspective-client/src/index.ts | 1 + .../waitForClientStore/waitForClientStore.ts | 4 +- .../embr.periscope.embedding.react/props.json | 2 +- .../periscope/PeriscopeDesignerContext.kt | 6 +- ... => TypeScriptResourceSuggestionSource.kt} | 4 +- .../typescript/TypeScriptResourceWorkspace.kt | 2 +- .../main/resources/localization.properties | 1 + modules/periscope/ts-compiler/src/index.ts | 92 ++++ modules/periscope/web/src/client.ts | 2 +- .../src/extensions/client-resources/index.ts | 52 +- modules/periscope/web/src/extensions/index.ts | 12 +- yarn.lock | 467 +++++++----------- 12 files changed, 297 insertions(+), 348 deletions(-) rename modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/{ClientResourceSuggestionSource.kt => TypeScriptResourceSuggestionSource.kt} (90%) diff --git a/libraries/javascript/perspective-client/src/index.ts b/libraries/javascript/perspective-client/src/index.ts index 16dde5e5..aca76621 100644 --- a/libraries/javascript/perspective-client/src/index.ts +++ b/libraries/javascript/perspective-client/src/index.ts @@ -1,5 +1,6 @@ export * from './components' export * from './hooks' +export * from './globals' export * from './scripting' export * from './transforms' export * from './utils' diff --git a/libraries/javascript/perspective-client/src/utils/waitForClientStore/waitForClientStore.ts b/libraries/javascript/perspective-client/src/utils/waitForClientStore/waitForClientStore.ts index 85c7a9ab..4e038f34 100644 --- a/libraries/javascript/perspective-client/src/utils/waitForClientStore/waitForClientStore.ts +++ b/libraries/javascript/perspective-client/src/utils/waitForClientStore/waitForClientStore.ts @@ -7,12 +7,12 @@ import { getClientStore } from '../index' export default function waitForClientStore( callback: (clientStore: ClientStore) => void ) { - setTimeout(function () { + requestAnimationFrame(() => { const clientStore = getClientStore() if (clientStore) { callback(clientStore) } else { waitForClientStore(callback) } - }, 100) + }) } diff --git a/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/props.json b/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/props.json index 68bf31a2..8957111b 100644 --- a/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/props.json +++ b/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/props.json @@ -5,7 +5,7 @@ "type": "string", "description": "Component Path.", "extension": { - "suggestion-source": "embr-periscope-client-resource" + "suggestion-source": "embr-periscope-typescript-resource" } }, "props": { diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt index db5359e7..ac040bed 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt @@ -10,8 +10,8 @@ import com.mussonindustrial.embr.designer.EmbrDesignerContextImpl import com.mussonindustrial.embr.perspective.designer.component.asDesignerComponent import com.mussonindustrial.embr.perspective.designer.component.registerComponent import com.mussonindustrial.embr.perspective.designer.component.removeComponent -import com.mussonindustrial.ignition.embr.periscope.component.ClientResourceSuggestionSource import com.mussonindustrial.ignition.embr.periscope.component.ComponentIdSuggestionSource +import com.mussonindustrial.ignition.embr.periscope.component.TypeScriptResourceSuggestionSource import com.mussonindustrial.ignition.embr.periscope.component.embedding.* import com.mussonindustrial.ignition.embr.periscope.resources.typescript.TypeScriptResourceListener import com.mussonindustrial.ignition.embr.periscope.resources.typescript.TypeScriptResourceWorkspace @@ -39,8 +39,8 @@ class PeriscopeDesignerContext(val context: DesignerContext) : ComponentIdSuggestionSource(this@PeriscopeDesignerContext), ) registerSuggestionSource( - ClientResourceSuggestionSource.ID, - ClientResourceSuggestionSource(this@PeriscopeDesignerContext), + TypeScriptResourceSuggestionSource.ID, + TypeScriptResourceSuggestionSource(this@PeriscopeDesignerContext), ) } } diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/ClientResourceSuggestionSource.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/TypeScriptResourceSuggestionSource.kt similarity index 90% rename from modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/ClientResourceSuggestionSource.kt rename to modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/TypeScriptResourceSuggestionSource.kt index 7643bf70..4c52bccf 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/ClientResourceSuggestionSource.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/TypeScriptResourceSuggestionSource.kt @@ -9,11 +9,11 @@ import com.mussonindustrial.ignition.embr.periscope.PeriscopeDesignerContext import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource import java.util.concurrent.CompletableFuture -class ClientResourceSuggestionSource(private val context: PeriscopeDesignerContext) : +class TypeScriptResourceSuggestionSource(private val context: PeriscopeDesignerContext) : SuggestionSource { companion object { - const val ID = "embr-periscope-client-resource" + const val ID = "embr-periscope-typescript-resource" } override fun getSuggestions( diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt index bdca6a82..59cccadd 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt @@ -71,7 +71,7 @@ class TypeScriptResourceWorkspace( putValue(NAME, "New TypeScript Module") putValue( SMALL_ICON, - InteractiveSvgIcon(Meta::class.java, "images/svgicons/theme.svg"), + InteractiveSvgIcon(Meta::class.java, "images/svgicons/tsx.svg"), ) } diff --git a/modules/periscope/gateway/src/main/resources/localization.properties b/modules/periscope/gateway/src/main/resources/localization.properties index 27aa7a5a..0c8eae90 100644 --- a/modules/periscope/gateway/src/main/resources/localization.properties +++ b/modules/periscope/gateway/src/main/resources/localization.properties @@ -15,6 +15,7 @@ script.invokeOnQueue.desc=Run a function on a Perspective execution queue, with script.invokeOnQueue.param.function=Python function to run. Should be a function reference that takes a single "scope" parameter. script.invokeOnQueue.param.delay=Optional. Time in milliseconds before running the function. script.invokeOnQueue.param.scope=Optional. Lifecycle scope. If specified, must be "session", "page", or "view", otherwise the default will be "view". + javascript-module.noun=JavaScript Module javascript-module.nouns=JavaScript Modules javascript-module.noun-long=JavaScript Module diff --git a/modules/periscope/ts-compiler/src/index.ts b/modules/periscope/ts-compiler/src/index.ts index 51c51cb4..2c3e5b7c 100644 --- a/modules/periscope/ts-compiler/src/index.ts +++ b/modules/periscope/ts-compiler/src/index.ts @@ -13,6 +13,9 @@ export function compile(input: string) { sourceMap: false, jsx: ts.JsxEmit.React, }, + transformers: { + before: [importTransformer], + }, }) } @@ -27,3 +30,92 @@ export async function format(source: string, cursorOffset: number) { plugins: [prettierPluginEstree as unknown as string, parserTypeScript], }) } + +/** + * Creates a transformer that converts: + * import 'Module' + * TO: + * window.__embrGlobals.scripting.globals.periscope.import('Module') + */ +const importTransformer: ts.TransformerFactory = (context) => { + return (sourceFile) => { + const visitor = (node: ts.Node): ts.Node => { + if ( + ts.isImportDeclaration(node) && + ts.isStringLiteral(node.moduleSpecifier) + ) { + const moduleName = node.moduleSpecifier.text + + // Base call: window.__embrGlobals.scripting.globals.periscope.import('moduleName') + const callExpression = ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('window'), + '__embrGlobals' + ), + 'scripting' + ), + 'globals' + ), + 'periscope' + ), + 'getClientResource' + ), + undefined, + [ts.factory.createStringLiteral(moduleName)] + ) + + const awaitedCall = ts.factory.createAwaitExpression(callExpression) + const clause = node.importClause + + // Case 1: import 'module' (Side effect only) + if (!clause) { + return ts.factory.createExpressionStatement(awaitedCall) + } + + const elements: ts.BindingElement[] = [] + + // Case 2: import defaultName from 'module' + if (clause.name) { + elements.push( + ts.factory.createBindingElement(undefined, undefined, clause.name) + ) + } + + // Case 3: import { a, b as c } from 'module' + if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { + clause.namedBindings.elements.forEach((specifier) => { + elements.push( + ts.factory.createBindingElement( + undefined, + specifier.propertyName, // The "remote" name (e.g., 'b' in 'b as c') + specifier.name // The "local" name (e.g., 'c') + ) + ) + }) + } + + // Create: const { ... } = window...import('module') + return ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + ts.factory.createObjectBindingPattern(elements), + undefined, + undefined, + awaitedCall + ), + ], + ts.NodeFlags.Const + ) + ) + } + return ts.visitEachChild(node, visitor, context) + } + return ts.visitNode(sourceFile, visitor) as ts.SourceFile + } +} diff --git a/modules/periscope/web/src/client.ts b/modules/periscope/web/src/client.ts index 9ff8303e..b6a6201a 100644 --- a/modules/periscope/web/src/client.ts +++ b/modules/periscope/web/src/client.ts @@ -36,4 +36,4 @@ const components = [ components.forEach((c) => ComponentRegistry.register(c)) -waitForClientStore((clientStore) => installExtensions(clientStore)) +waitForClientStore(async (clientStore) => await installExtensions(clientStore)) diff --git a/modules/periscope/web/src/extensions/client-resources/index.ts b/modules/periscope/web/src/extensions/client-resources/index.ts index a252f0be..2d28ea68 100644 --- a/modules/periscope/web/src/extensions/client-resources/index.ts +++ b/modules/periscope/web/src/extensions/client-resources/index.ts @@ -1,6 +1,6 @@ import { ClientStore } from '@inductiveautomation/perspective-client' import { merge } from 'lodash' -import { getEmbrGlobals } from '@embr-js/perspective-client/src/globals' +import { getEmbrGlobals } from '@embr-js/perspective-client' export type JsResource = { path: string @@ -16,6 +16,7 @@ export type ResourceManifestResponse = { } let loadedResourceManifest: ResourceManifest | undefined let manifestPromise: Promise | undefined +const modules = new Map() export async function getResourceManifest(clientStore: ClientStore) { if (loadedResourceManifest) { @@ -25,7 +26,7 @@ export async function getResourceManifest(clientStore: ClientStore) { if (manifestPromise) return manifestPromise const { projectName } = clientStore - const paths = buildPaths(projectName) + const paths = projectPaths(projectName) manifestPromise = (async () => { const response = await fetch(paths.manifest) @@ -47,24 +48,28 @@ export async function getClientResource( clientStore: ClientStore, path: string ) { + if (modules.has(path)) { + return modules.get(path) + } + const { projectName } = clientStore - const { jsBase } = buildPaths(projectName) + const { jsBase } = projectPaths(projectName) const manifest = await getResourceManifest(clientStore) const hash = manifest.js.find((resource) => resource.path == path)?.hash - if (!hash) { - return null - } + if (!hash) return null try { - return await import(`${jsBase}/${encodeURI(path)}.${hash}.js`) + const module = await import(`${jsBase}/${encodeURI(path)}.${hash}.js`) + modules.set(path, module) + return module } catch (err) { console.error(`Failed to load ClientResource '${path}':`, err) return null } } -function buildPaths(projectName: string) { +function projectPaths(projectName: string) { const base = '/data/embr-periscope/client-resources' return { base, @@ -74,37 +79,20 @@ function buildPaths(projectName: string) { } export function installClientResourceImport(clientStore: ClientStore) { + const throwError = (message: string) => { + throw new Error(message) + } + merge(getEmbrGlobals().scripting.globals, { periscope: { - import: async (path: string) => + module: (path: string) => + modules.get(path) ?? throwError(`Module ${path} not found.`), + getClientResource: async (path: string) => await getClientResource(clientStore, path), }, }) } -export function installClientResourceImportMap(clientStore: ClientStore) { - const { projectName } = clientStore - const paths = buildPaths(projectName) - - const importMap = { - scopes: { - [paths.jsBase + '/']: { - '@/': `${paths.jsBase}/`, - }, - }, - } - - document.querySelector('script[data-import-map="periscope"]')?.remove() - - const script = document.createElement('script') - script.type = 'importmap' - script.dataset.importMap = 'periscope' - script.textContent = JSON.stringify(importMap, null, 2) - void ( - document.currentScript?.after(script) ?? document.head.appendChild(script) - ) -} - export const PROTOCOL = { REFRESH: 'periscope-client-resource-refresh', } diff --git a/modules/periscope/web/src/extensions/index.ts b/modules/periscope/web/src/extensions/index.ts index 69e7a711..36812c0f 100644 --- a/modules/periscope/web/src/extensions/index.ts +++ b/modules/periscope/web/src/extensions/index.ts @@ -1,20 +1,24 @@ import { ClientStore } from '@inductiveautomation/perspective-client' import { - installClientResourceImportMap, installClientResourceListener, installClientResourceImport, getResourceManifest, + getClientResource, } from './client-resources' import { installRunJavaScript } from './runJavaScript' import { installToasts } from './toast' export * from './runJavaScript' -export function installExtensions(clientStore: ClientStore) { - getResourceManifest(clientStore).then(() => {}) +export async function installExtensions(clientStore: ClientStore) { installRunJavaScript(clientStore) installToasts(clientStore) - installClientResourceImportMap(clientStore) installClientResourceImport(clientStore) installClientResourceListener(clientStore) + await getResourceManifest(clientStore).then( + async (manifest) => + await Promise.all( + manifest.js.map((js) => getClientResource(clientStore, js.path)) + ) + ) } diff --git a/yarn.lock b/yarn.lock index d95918fc..4088c9a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,46 +19,11 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.9", "@babel/compat-data@^7.26.0": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02" integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g== -"@babel/compat-data@^7.27.2": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" - integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== - -"@babel/core@7.27.4": - version "7.27.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.4.tgz#cc1fc55d0ce140a1828d1dd2a2eba285adbfb3ce" - integrity sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.3" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-module-transforms" "^7.27.3" - "@babel/helpers" "^7.27.4" - "@babel/parser" "^7.27.4" - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.27.4" - "@babel/types" "^7.27.3" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - "@babel/core@^7.23.2": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" @@ -91,17 +56,6 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" -"@babel/generator@^7.27.3", "@babel/generator@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" - integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== - dependencies: - "@babel/parser" "^7.28.3" - "@babel/types" "^7.28.2" - "@jridgewell/gen-mapping" "^0.3.12" - "@jridgewell/trace-mapping" "^0.3.28" - jsesc "^3.0.2" - "@babel/helper-annotate-as-pure@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" @@ -109,13 +63,6 @@ dependencies: "@babel/types" "^7.25.9" -"@babel/helper-annotate-as-pure@^7.27.1": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" - integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== - dependencies: - "@babel/types" "^7.27.3" - "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" @@ -127,17 +74,6 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-compilation-targets@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== - dependencies: - "@babel/compat-data" "^7.27.2" - "@babel/helper-validator-option" "^7.27.1" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - "@babel/helper-create-class-features-plugin@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83" @@ -171,11 +107,6 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-globals@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" - integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== - "@babel/helper-member-expression-to-functions@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" @@ -192,14 +123,6 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - "@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" @@ -209,15 +132,6 @@ "@babel/helper-validator-identifier" "^7.25.9" "@babel/traverse" "^7.25.9" -"@babel/helper-module-transforms@^7.27.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" - integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.28.3" - "@babel/helper-optimise-call-expression@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" @@ -230,11 +144,6 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== -"@babel/helper-plugin-utils@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== - "@babel/helper-remap-async-to-generator@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92" @@ -266,31 +175,16 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== - "@babel/helper-validator-option@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== -"@babel/helper-validator-option@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" - integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - "@babel/helper-wrap-function@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0" @@ -308,21 +202,6 @@ "@babel/template" "^7.25.9" "@babel/types" "^7.26.0" -"@babel/helpers@^7.27.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" - integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== - dependencies: - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.4" - -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.6", "@babel/parser@^7.27.2", "@babel/parser@^7.27.4", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" - integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== - dependencies: - "@babel/types" "^7.28.4" - "@babel/parser@^7.25.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" @@ -411,13 +290,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/plugin-syntax-jsx@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-syntax-typescript@^7.25.9", "@babel/plugin-syntax-typescript@^7.3.3": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" @@ -730,39 +602,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/plugin-transform-react-display-name@^7.27.1": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz#6f20a7295fea7df42eb42fed8f896813f5b934de" - integrity sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-react-jsx-development@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz#47ff95940e20a3a70e68ad3d4fcb657b647f6c98" - integrity sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q== - dependencies: - "@babel/plugin-transform-react-jsx" "^7.27.1" - -"@babel/plugin-transform-react-jsx@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz#1023bc94b78b0a2d68c82b5e96aed573bcfb9db0" - integrity sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-syntax-jsx" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/plugin-transform-react-pure-annotations@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz#339f1ce355eae242e0649f232b1c68907c02e879" - integrity sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-regenerator@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b" @@ -960,18 +799,6 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.27.1.tgz#86ea0a5ca3984663f744be2fd26cb6747c3fd0ec" - integrity sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-validator-option" "^7.27.1" - "@babel/plugin-transform-react-display-name" "^7.27.1" - "@babel/plugin-transform-react-jsx" "^7.27.1" - "@babel/plugin-transform-react-jsx-development" "^7.27.1" - "@babel/plugin-transform-react-pure-annotations" "^7.27.1" - "@babel/preset-typescript@^7.22.5": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" @@ -1009,11 +836,6 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/standalone@7.27.4": - version "7.27.4" - resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.27.4.tgz#640fb78bebcf4bda10ef2ba75c12e6a20ff9ce53" - integrity sha512-vFUF+yFDDyR3+ZUOvYeLMLb6Vl1l6LCvp61/0kMzRyB3Z6ljPz5rxbmsFVBZcm6Mvu9QyaZuJQIXYZ/DA20MfQ== - "@babel/template@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" @@ -1023,15 +845,6 @@ "@babel/parser" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/template@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" - "@babel/traverse@^7.16.0", "@babel/traverse@^7.25.9": version "7.26.4" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" @@ -1045,27 +858,6 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.4", "@babel/traverse@^7.28.3": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" - integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.3" - "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.4" - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.4" - debug "^4.3.1" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.6", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" - integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/types@^7.25.4", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.4.4": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" @@ -1324,131 +1116,261 @@ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz#a1414903bb38027382f85f03dda6065056757727" integrity sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA== +"@esbuild/aix-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz#815b39267f9bffd3407ea6c376ac32946e24f8d2" + integrity sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg== + "@esbuild/android-arm64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz#c859994089e9767224269884061f89dae6fb51c6" integrity sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w== +"@esbuild/android-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz#19b882408829ad8e12b10aff2840711b2da361e8" + integrity sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg== + "@esbuild/android-arm@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.8.tgz#96a8f2ca91c6cd29ea90b1af79d83761c8ba0059" integrity sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw== +"@esbuild/android-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz#90be58de27915efa27b767fcbdb37a4470627d7b" + integrity sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA== + "@esbuild/android-x64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.8.tgz#a3a626c4fec4a024a9fa8c7679c39996e92916f0" integrity sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA== +"@esbuild/android-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz#d7dcc976f16e01a9aaa2f9b938fbec7389f895ac" + integrity sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ== + "@esbuild/darwin-arm64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz#a5e1252ca2983d566af1c0ea39aded65736fc66d" integrity sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw== +"@esbuild/darwin-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz#9f6cac72b3a8532298a6a4493ed639a8988e8abd" + integrity sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg== + "@esbuild/darwin-x64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz#5271b0df2bb12ce8df886704bfdd1c7cc01385d2" integrity sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg== +"@esbuild/darwin-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz#ac61d645faa37fd650340f1866b0812e1fb14d6a" + integrity sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg== + "@esbuild/freebsd-arm64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz#d0a0e7fdf19733b8bb1566b81df1aa0bb7e46ada" integrity sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA== +"@esbuild/freebsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz#b8625689d73cf1830fe58c39051acdc12474ea1b" + integrity sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w== + "@esbuild/freebsd-x64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz#2de8b2e0899d08f1cb1ef3128e159616e7e85343" integrity sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw== +"@esbuild/freebsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz#07be7dd3c9d42fe0eccd2ab9f9ded780bc53bead" + integrity sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA== + "@esbuild/linux-arm64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz#a4209efadc0c2975716458484a4e90c237c48ae9" integrity sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w== +"@esbuild/linux-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz#bf31918fe5c798586460d2b3d6c46ed2c01ca0b6" + integrity sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg== + "@esbuild/linux-arm@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz#ccd9e291c24cd8d9142d819d463e2e7200d25b19" integrity sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg== +"@esbuild/linux-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz#28493ee46abec1dc3f500223cd9f8d2df08f9d11" + integrity sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw== + "@esbuild/linux-ia32@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz#006ad1536d0c2b28fb3a1cf0b53bcb85aaf92c4d" integrity sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg== +"@esbuild/linux-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz#750752a8b30b43647402561eea764d0a41d0ee29" + integrity sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg== + "@esbuild/linux-loong64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz#127b3fbfb2c2e08b1397e985932f718f09a8f5c4" integrity sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ== +"@esbuild/linux-loong64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz#a5a92813a04e71198c50f05adfaf18fc1e95b9ed" + integrity sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA== + "@esbuild/linux-mips64el@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz#837d1449517791e3fa7d82675a2d06d9f56cb340" integrity sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw== +"@esbuild/linux-mips64el@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz#deb45d7fd2d2161eadf1fbc593637ed766d50bb1" + integrity sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw== + "@esbuild/linux-ppc64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz#aa2e3bd93ab8df084212f1895ca4b03c42d9e0fe" integrity sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ== +"@esbuild/linux-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz#6f39ae0b8c4d3d2d61a65b26df79f6e12a1c3d78" + integrity sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA== + "@esbuild/linux-riscv64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz#a340620e31093fef72767dd28ab04214b3442083" integrity sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg== +"@esbuild/linux-riscv64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz#4c5c19c3916612ec8e3915187030b9df0b955c1d" + integrity sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ== + "@esbuild/linux-s390x@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz#ddfed266c8c13f5efb3105a0cd47f6dcd0e79e71" integrity sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg== +"@esbuild/linux-s390x@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz#9ed17b3198fa08ad5ccaa9e74f6c0aff7ad0156d" + integrity sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw== + "@esbuild/linux-x64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz#9a4f78c75c051e8c060183ebb39a269ba936a2ac" integrity sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ== +"@esbuild/linux-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz#12383dcbf71b7cf6513e58b4b08d95a710bf52a5" + integrity sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA== + "@esbuild/netbsd-arm64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz#902c80e1d678047926387230bc037e63e00697d0" integrity sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw== +"@esbuild/netbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz#dd0cb2fa543205fcd931df44f4786bfcce6df7d7" + integrity sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA== + "@esbuild/netbsd-x64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz#2d9eb4692add2681ff05a14ce99de54fbed7079c" integrity sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg== +"@esbuild/netbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz#028ad1807a8e03e155153b2d025b506c3787354b" + integrity sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA== + "@esbuild/openbsd-arm64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz#89c3b998c6de739db38ab7fb71a8a76b3fa84a45" integrity sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ== +"@esbuild/openbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz#e3c16ff3490c9b59b969fffca87f350ffc0e2af5" + integrity sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw== + "@esbuild/openbsd-x64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz#2f01615cf472b0e48c077045cfd96b5c149365cc" integrity sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ== +"@esbuild/openbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz#c5a4693fcb03d1cbecbf8b422422468dfc0d2a8b" + integrity sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ== + "@esbuild/openharmony-arm64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz#a201f720cd2c3ebf9a6033fcc3feb069a54b509a" integrity sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg== +"@esbuild/openharmony-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz#082082444f12db564a0775a41e1991c0e125055e" + integrity sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g== + "@esbuild/sunos-x64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz#07046c977985a3334667f19e6ab3a01a80862afb" integrity sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w== +"@esbuild/sunos-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz#5ab036c53f929e8405c4e96e865a424160a1b537" + integrity sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA== + "@esbuild/win32-arm64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz#4a5470caf0d16127c05d4833d4934213c69392d1" integrity sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ== +"@esbuild/win32-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz#38de700ef4b960a0045370c171794526e589862e" + integrity sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA== + "@esbuild/win32-ia32@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz#3de3e8470b7b328d99dbc3e9ec1eace207e5bbc4" integrity sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg== +"@esbuild/win32-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz#451b93dc03ec5d4f38619e6cd64d9f9eff06f55c" + integrity sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q== + "@esbuild/win32-x64@0.25.8": version "0.25.8" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz#610d7ea539d2fcdbe39237b5cc175eb2c4451f9c" integrity sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw== +"@esbuild/win32-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17" + integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA== + "@eslint-community/eslint-utils@^4.2.0": version "4.4.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" @@ -1655,14 +1577,6 @@ dependencies: "@sinclair/typebox" "^0.34.0" -"@jridgewell/gen-mapping@^0.3.12": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" - integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - "@jridgewell/trace-mapping" "^0.3.24" - "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -1695,14 +1609,6 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jridgewell/trace-mapping@^0.3.28": - version "0.3.31" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" - integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@jridgewell/trace-mapping@^0.3.29": version "0.3.29" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz#a58d31eaadaf92c6695680b2e1d464a9b8fbf7fc" @@ -2270,51 +2176,6 @@ dependencies: tslib "^2.4.0" -"@types/babel__core@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*", "@types/babel__generator@^7.6.8": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" - integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__standalone@7.1.9": - version "7.1.9" - resolved "https://registry.yarnpkg.com/@types/babel__standalone/-/babel__standalone-7.1.9.tgz#51ae2d49beaee9a8be1cd6a2997af83a5077d03a" - integrity sha512-IcCNPLqpevUD7UpV8QB0uwQPOyoOKACFf0YtYWRHcmxcakaje4Q7dbG2+jMqxw/I8Zk0NHvEps66WwS7z/UaaA== - dependencies: - "@babel/parser" "^7.25.6" - "@babel/types" "^7.25.6" - "@types/babel__core" "^7.20.5" - "@types/babel__generator" "^7.6.8" - "@types/babel__template" "^7.4.4" - "@types/babel__traverse" "^7.20.6" - -"@types/babel__template@*", "@types/babel__template@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.20.6": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" - integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== - dependencies: - "@babel/types" "^7.28.2" - "@types/chai@^5.2.2": version "5.2.2" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.2.tgz#6f14cea18180ffc4416bc0fd12be05fdd73bdd6b" @@ -4777,6 +4638,38 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +esbuild@0.27.3: + version "0.27.3" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.3.tgz#5859ca8e70a3af956b26895ce4954d7e73bd27a8" + integrity sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.27.3" + "@esbuild/android-arm" "0.27.3" + "@esbuild/android-arm64" "0.27.3" + "@esbuild/android-x64" "0.27.3" + "@esbuild/darwin-arm64" "0.27.3" + "@esbuild/darwin-x64" "0.27.3" + "@esbuild/freebsd-arm64" "0.27.3" + "@esbuild/freebsd-x64" "0.27.3" + "@esbuild/linux-arm" "0.27.3" + "@esbuild/linux-arm64" "0.27.3" + "@esbuild/linux-ia32" "0.27.3" + "@esbuild/linux-loong64" "0.27.3" + "@esbuild/linux-mips64el" "0.27.3" + "@esbuild/linux-ppc64" "0.27.3" + "@esbuild/linux-riscv64" "0.27.3" + "@esbuild/linux-s390x" "0.27.3" + "@esbuild/linux-x64" "0.27.3" + "@esbuild/netbsd-arm64" "0.27.3" + "@esbuild/netbsd-x64" "0.27.3" + "@esbuild/openbsd-arm64" "0.27.3" + "@esbuild/openbsd-x64" "0.27.3" + "@esbuild/openharmony-arm64" "0.27.3" + "@esbuild/sunos-x64" "0.27.3" + "@esbuild/win32-arm64" "0.27.3" + "@esbuild/win32-ia32" "0.27.3" + "@esbuild/win32-x64" "0.27.3" + esbuild@^0.25.0: version "0.25.8" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.8.tgz#482d42198b427c9c2f3a81b63d7663aecb1dda07" @@ -9400,7 +9293,7 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9418,15 +9311,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -9546,7 +9430,7 @@ stringstream@~0.0.4: resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9567,13 +9451,6 @@ strip-ansi@^4.0.0, strip-ansi@~4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -10053,11 +9930,6 @@ typescript@5.9.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== -typescript@5.9.3: - version "5.9.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" - integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== - ua-parser-js@^0.7.31: version "0.7.39" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.39.tgz#c71efb46ebeabc461c4612d22d54f88880fabe7e" @@ -10593,7 +10465,7 @@ worker-farm@~1.3.1: errno ">=0.1.1 <0.2.0-0" xtend ">=4.0.0 <4.1.0-0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -10610,15 +10482,6 @@ wrap-ansi@^2.0.0: string-width "^1.0.1" strip-ansi "^3.0.1" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 7b1100c2e2f9b7ca6e5267fe8800f8335f315cda Mon Sep 17 00:00:00 2001 From: Ben Musson Date: Fri, 6 Mar 2026 01:13:37 -0500 Subject: [PATCH 3/6] cleanup --- .../periscope/PeriscopeDesignerContext.kt | 4 ++-- .../typescript/TypeScriptResourceWorkspace.kt | 23 ++++++++----------- .../typescript/TypeScriptCompiler.kt | 4 ++-- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt index ac040bed..b15277dc 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt @@ -72,10 +72,10 @@ class PeriscopeDesignerContext(val context: DesignerContext) : } fun registerProjectResourceHandlers() { - handlers.forEach { project!!.addProjectResourceListener(it) } + handlers.forEach { project?.addProjectResourceListener(it) } } fun removeProjectResourceHandlers() { - handlers.forEach { project!!.removeProjectResourceListener(it) } + handlers.forEach { project?.removeProjectResourceListener(it) } } } diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt index 59cccadd..f83ee8cf 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt @@ -2,7 +2,6 @@ package com.mussonindustrial.ignition.embr.periscope.resources.typescript import com.inductiveautomation.ignition.common.BundleUtil import com.inductiveautomation.ignition.common.model.ApplicationScope -import com.inductiveautomation.ignition.common.project.Project import com.inductiveautomation.ignition.common.project.resource.ProjectResourceBuilder import com.inductiveautomation.ignition.common.project.resource.ResourcePath import com.inductiveautomation.ignition.designer.model.DesignerContext @@ -23,7 +22,7 @@ import javax.swing.JPopupMenu import org.json.JSONException class TypeScriptResourceWorkspace( - private val context: DesignerContext, + context: DesignerContext, private val parent: MutableNavTreeNode, ) : TabbedResourceWorkspace( @@ -40,9 +39,8 @@ class TypeScriptResourceWorkspace( ) { private val logger = this.getLogger() - - private fun getNewConstructor(project: Project): Consumer { - return Consumer { builder: ProjectResourceBuilder -> + private val resourceConstructor = + Consumer { builder: ProjectResourceBuilder -> try { builder.putData(TypeScriptResource.SOURCE_KEY, ByteArray(0)) builder.putData(TypeScriptResource.CLIENT_KEY, ByteArray(0)) @@ -50,7 +48,6 @@ class TypeScriptResourceWorkspace( logger.error("Error creating new TypeScript Module.", e) } } - } override fun getKey(): String { return TypeScriptResource.type.typeId @@ -66,7 +63,7 @@ class TypeScriptResourceWorkspace( override fun addNewResourceActions(folderNode: ResourceFolderNode, menu: JPopupMenu) { menu.add( - object : NewResourceAction(this, folderNode, getNewConstructor(context.project!!)) { + object : NewResourceAction(this, folderNode, resourceConstructor) { init { putValue(NAME, "New TypeScript Module") putValue( @@ -81,8 +78,6 @@ class TypeScriptResourceWorkspace( } override fun createWorkspaceHomeTab(): Optional { - val ws = this - return Optional.of( object : WorkspaceWelcomePanel(BundleUtil.i18n("periscope.typescript.nouns-long")) { override fun createPanels(): List { @@ -95,18 +90,18 @@ class TypeScriptResourceWorkspace( ResourceBuilderDelegate.build( BundleUtil.i18n("periscope.typescript.noun-long"), InteractiveSvgIcon(Meta::class.java, "images/svgicons/tsx.svg"), - getNewConstructor(context.project!!), + resourceConstructor, ) ), - ) { path: ResourcePath? -> - ws.open(path) + ) { path: ResourcePath -> + open(path) }, RecentlyModifiedTablePanel( context, TypeScriptResource.type, BundleUtil.i18n("periscope.typescript.nouns-long"), - ) { path: ResourcePath? -> - ws.open(path) + ) { path: ResourcePath -> + open(path) }, ) } diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt index b853495e..a1cde030 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt @@ -69,13 +69,13 @@ class TypeScriptCompiler(engine: Engine) { fun format(source: String, cursorPosition: Int): FormatResult? { val compiler = frame.executeJavaScript(compilerModule)!! val result = compiler.call("format", source, cursorPosition) - logger.info("Format Result $result") + logger.debug("Format Result $result") val future = CompletableFuture() result .then { it -> - logger.info("Format Result Resolution $it") + logger.debug("Format Result Resolution $it") val formatResult = it[0] as JsObject future.complete(FormatResult(formatResult)) } From 7a9509b7b84f905c2cf712b5b5c570ee5fc9d6ff Mon Sep 17 00:00:00 2001 From: Ben Musson Date: Fri, 6 Mar 2026 04:20:47 -0500 Subject: [PATCH 4/6] hoisting + default exports --- modules/periscope/ts-compiler/src/index.ts | 165 +++++++++++++++------ 1 file changed, 121 insertions(+), 44 deletions(-) diff --git a/modules/periscope/ts-compiler/src/index.ts b/modules/periscope/ts-compiler/src/index.ts index 2c3e5b7c..dfb64550 100644 --- a/modules/periscope/ts-compiler/src/index.ts +++ b/modules/periscope/ts-compiler/src/index.ts @@ -39,67 +39,137 @@ export async function format(source: string, cursorOffset: number) { */ const importTransformer: ts.TransformerFactory = (context) => { return (sourceFile) => { + const imports: { moduleName: string; clause: ts.ImportClause | null }[] = [] + const visitor = (node: ts.Node): ts.Node => { if ( ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) ) { - const moduleName = node.moduleSpecifier.text + imports.push({ + moduleName: node.moduleSpecifier.text, + clause: node.importClause ?? null, + }) + // Remove original import; replaced at top + return ts.factory.createEmptyStatement() + } + return ts.visitEachChild(node, visitor, context) + } + + const newSourceFile = ts.visitNode(sourceFile, visitor) as ts.SourceFile - // Base call: window.__embrGlobals.scripting.globals.periscope.import('moduleName') - const callExpression = ts.factory.createCallExpression( + if (imports.length === 0) return newSourceFile + + // Create Promise.all([...]) + const callExpressions = imports.map((imp) => + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( ts.factory.createPropertyAccessExpression( ts.factory.createPropertyAccessExpression( ts.factory.createPropertyAccessExpression( ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('window'), - '__embrGlobals' - ), - 'scripting' + ts.factory.createIdentifier('window'), + '__embrGlobals' ), - 'globals' + 'scripting' ), - 'periscope' + 'globals' ), - 'getClientResource' + 'periscope' ), - undefined, - [ts.factory.createStringLiteral(moduleName)] - ) + 'getClientResource' + ), + undefined, + [ts.factory.createStringLiteral(imp.moduleName)] + ) + ) + + const modulesIdentifier = ts.factory.createIdentifier('__modules') - const awaitedCall = ts.factory.createAwaitExpression(callExpression) - const clause = node.importClause + const modulesDeclaration = ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + modulesIdentifier, + undefined, + undefined, + ts.factory.createAwaitExpression( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('Promise'), + 'all' + ), + undefined, + [ts.factory.createArrayLiteralExpression(callExpressions)] + ) + ) + ), + ], + ts.NodeFlags.Const + ) + ) - // Case 1: import 'module' (Side effect only) - if (!clause) { - return ts.factory.createExpressionStatement(awaitedCall) - } + const statements: ts.Statement[] = [modulesDeclaration] - const elements: ts.BindingElement[] = [] + imports.forEach((imp, i) => { + const clause = imp.clause - // Case 2: import defaultName from 'module' - if (clause.name) { - elements.push( - ts.factory.createBindingElement(undefined, undefined, clause.name) + if (!clause) { + // Side-effect import: no statement needed + return + } + + if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) { + // const ns = __modules[i] + statements.push( + ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + clause.namedBindings.name, + undefined, + undefined, + ts.factory.createElementAccessExpression( + modulesIdentifier, + ts.factory.createNumericLiteral(i) + ) + ), + ], + ts.NodeFlags.Const + ) ) - } + ) + return + } - // Case 3: import { a, b as c } from 'module' - if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { - clause.namedBindings.elements.forEach((specifier) => { - elements.push( - ts.factory.createBindingElement( - undefined, - specifier.propertyName, // The "remote" name (e.g., 'b' in 'b as c') - specifier.name // The "local" name (e.g., 'c') - ) + const elements: ts.BindingElement[] = [] + + if (clause.name) { + elements.push( + ts.factory.createBindingElement( + undefined, + ts.factory.createIdentifier('default'), + clause.name + ) + ) + } + + if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { + clause.namedBindings.elements.forEach((specifier) => { + elements.push( + ts.factory.createBindingElement( + undefined, + specifier.propertyName, + specifier.name ) - }) - } + ) + }) + } - // Create: const { ... } = window...import('module') - return ts.factory.createVariableStatement( + statements.push( + ts.factory.createVariableStatement( undefined, ts.factory.createVariableDeclarationList( [ @@ -107,15 +177,22 @@ const importTransformer: ts.TransformerFactory = (context) => { ts.factory.createObjectBindingPattern(elements), undefined, undefined, - awaitedCall + ts.factory.createElementAccessExpression( + modulesIdentifier, + ts.factory.createNumericLiteral(i) + ) ), ], ts.NodeFlags.Const ) ) - } - return ts.visitEachChild(node, visitor, context) - } - return ts.visitNode(sourceFile, visitor) as ts.SourceFile + ) + }) + + // Prepend generated statements to the file + return ts.factory.updateSourceFile(newSourceFile, [ + ...statements, + ...newSourceFile.statements.filter((s) => !ts.isEmptyStatement(s)), + ]) } } From 87cc58ddac15f5dd29a667b36725fa8879e36ac3 Mon Sep 17 00:00:00 2001 From: Ben Musson Date: Fri, 6 Mar 2026 18:53:58 -0500 Subject: [PATCH 5/6] added module support and auto refreshing --- .../perspective-client/src/globals/globals.ts | 2 + .../variants/base.props.json | 11 +- .../periscope/PeriscopeDesignerContext.kt | 11 - .../embr/periscope/PeriscopeDesignerHook.kt | 6 - .../typescript/TypeScriptResourceEditor.kt | 2 +- .../typescript/TypeScriptResourceListener.kt | 48 --- .../typescript/TypeScriptCompiler.kt | 4 +- .../embr/periscope/PeriscopeGatewayContext.kt | 11 + .../embr/periscope/PeriscopeGatewayHook.kt | 6 + .../typescript/TypeScriptChangeListener.kt | 53 +++ .../ts-compiler/src/importTransformer.ts | 256 +++++++++++++ modules/periscope/ts-compiler/src/index.ts | 339 +++++++++--------- .../web/src/components/embedding/react.tsx | 3 +- .../index.ts => clientResource.ts} | 59 ++- modules/periscope/web/src/extensions/index.ts | 18 +- 15 files changed, 555 insertions(+), 274 deletions(-) delete mode 100644 modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceListener.kt create mode 100644 modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptChangeListener.kt create mode 100644 modules/periscope/ts-compiler/src/importTransformer.ts rename modules/periscope/web/src/extensions/{client-resources/index.ts => clientResource.ts} (67%) diff --git a/libraries/javascript/perspective-client/src/globals/globals.ts b/libraries/javascript/perspective-client/src/globals/globals.ts index 38c90ebb..d82f5d39 100644 --- a/libraries/javascript/perspective-client/src/globals/globals.ts +++ b/libraries/javascript/perspective-client/src/globals/globals.ts @@ -1,4 +1,5 @@ export type EmbrGlobals = { + modules: Record scripting: ScriptingProperties } @@ -14,6 +15,7 @@ declare global { function createDefaultGlobals(): EmbrGlobals { window.__embrGlobals = { + modules: {}, scripting: { globals: {}, }, diff --git a/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/variants/base.props.json b/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/variants/base.props.json index 53ba3fcc..1de2a9a1 100644 --- a/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/variants/base.props.json +++ b/modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/variants/base.props.json @@ -1,13 +1,6 @@ { - "component": "(props) =>
{props.text}
", - "props": { - "text": "Hello World!" - }, - "options": { - "babel": { - "presets": ["react"] - } - }, + "component": "", + "props": {}, "style": { "classes": "" } diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt index b15277dc..72bb9fc8 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt @@ -13,7 +13,6 @@ import com.mussonindustrial.embr.perspective.designer.component.removeComponent import com.mussonindustrial.ignition.embr.periscope.component.ComponentIdSuggestionSource import com.mussonindustrial.ignition.embr.periscope.component.TypeScriptResourceSuggestionSource import com.mussonindustrial.ignition.embr.periscope.component.embedding.* -import com.mussonindustrial.ignition.embr.periscope.resources.typescript.TypeScriptResourceListener import com.mussonindustrial.ignition.embr.periscope.resources.typescript.TypeScriptResourceWorkspace import com.teamdev.jxbrowser.engine.Engine @@ -55,8 +54,6 @@ class PeriscopeDesignerContext(val context: DesignerContext) : React.asDesignerComponent(), ) - private val handlers = listOf(TypeScriptResourceListener(this)) - private val workspaces = listOf(TypeScriptResourceWorkspace(context, perspectiveNavNode)) fun registerComponents() { @@ -70,12 +67,4 @@ class PeriscopeDesignerContext(val context: DesignerContext) : fun registerResourceWorkspaces() { workspaces.forEach { registerResourceWorkspace(it) } } - - fun registerProjectResourceHandlers() { - handlers.forEach { project?.addProjectResourceListener(it) } - } - - fun removeProjectResourceHandlers() { - handlers.forEach { project?.removeProjectResourceListener(it) } - } } diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerHook.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerHook.kt index 2a18c8d7..1a4a98f3 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerHook.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerHook.kt @@ -40,9 +40,6 @@ class PeriscopeDesignerHook : AbstractDesignerModuleHook() { logger.debug("Registering resource editors...") this.context.registerResourceWorkspaces() - - logger.debug("Registering project resource handlers...") - this.context.registerProjectResourceHandlers() } override fun shutdown() { @@ -56,8 +53,5 @@ class PeriscopeDesignerHook : AbstractDesignerModuleHook() { logger.debug("Removing components...") this.context.removeComponents() - - logger.debug("Removing project resource handlers...") - this.context.removeProjectResourceHandlers() } } diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceEditor.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceEditor.kt index 2e901240..74a02e03 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceEditor.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceEditor.kt @@ -77,7 +77,7 @@ class TypeScriptResourceEditor(workspace: TabbedResourceWorkspace?, path: Resour } override fun getObjectForSave(): TypeScriptResource { - val result = compiler.compile(textArea.text) + val result = compiler.compile(textArea.text, resourcePath.path.toString()) return TypeScriptResource( textArea.text ?: "", diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceListener.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceListener.kt deleted file mode 100644 index 033df727..00000000 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceListener.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.mussonindustrial.ignition.embr.periscope.resources.typescript - -import com.inductiveautomation.ignition.common.project.ChangeOperation -import com.inductiveautomation.ignition.common.project.ProjectResourceListener -import com.inductiveautomation.ignition.gateway.project.ResourceFilter -import com.inductiveautomation.perspective.designer.DesignerHook -import com.mussonindustrial.ignition.embr.periscope.PeriscopeDesignerContext -import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource - -class TypeScriptResourceListener(val context: PeriscopeDesignerContext) : ProjectResourceListener { - - val filter: ResourceFilter = - ResourceFilter.newBuilder().addResourceType(TypeScriptResource.type).build() - - override fun onBeforeChanges() { - super.onBeforeChanges() - } - - override fun onAfterChanges() { - DesignerHook.get(context.context).notifyProjectSaveDone() - } - - override fun manifestChanged( - projectName: String, - operation: List, - ) { - super.manifestChanged(projectName, operation) - } - - override fun resourcesCreated( - projectName: String, - resources: List, - ) {} - - override fun resourcesModified( - projectName: String, - resources: List, - ) {} - - override fun resourcesDeleted( - projectName: String, - resources: List, - ) {} - - override fun getResourceFilter(): ResourceFilter { - return filter - } -} diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt index a1cde030..7841314a 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/typescript/TypeScriptCompiler.kt @@ -30,9 +30,9 @@ class TypeScriptCompiler(engine: Engine) { } val frame = browser.mainFrame().get() - fun compile(input: String): CompilationResult? { + fun compile(input: String, modulePath: String): CompilationResult? { val compiler = frame.executeJavaScript(compilerModule)!! - val result = compiler.call("compile", input) + val result = compiler.call("compile", input, modulePath) return try { CompilationResult(result) diff --git a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayContext.kt b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayContext.kt index f5b05d1d..ea7de1e8 100644 --- a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayContext.kt +++ b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayContext.kt @@ -22,6 +22,7 @@ import com.mussonindustrial.embr.perspective.gateway.component.removeComponent import com.mussonindustrial.embr.perspective.gateway.reflect.ViewLoader import com.mussonindustrial.embr.servlets.ModuleServletManager import com.mussonindustrial.ignition.embr.periscope.component.embedding.* +import com.mussonindustrial.ignition.embr.periscope.resources.typescript.TypeScriptChangeListener import java.util.EnumSet import java.util.WeakHashMap @@ -49,6 +50,8 @@ class PeriscopeGatewayContext(private val context: GatewayContext) : PerspectiveContext.get(context) as GatewayHook.PerspectiveGatewayContext } + private val projectLifecycles = + listOf(TypeScriptChangeListener(perspectiveContext, context.projectManager)) private val viewLoaders = WeakHashMap() fun getViewLoader(pageModel: PageModel): ViewLoader { @@ -85,6 +88,14 @@ class PeriscopeGatewayContext(private val context: GatewayContext) : } } + fun startupProjectLifecycles() { + projectLifecycles.forEach { it.startup() } + } + + fun shutdownProjectLifecycles() { + projectLifecycles.forEach { it.shutdown() } + } + fun removeServlets() { servletManager.removeAllServlets() } diff --git a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayHook.kt b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayHook.kt index 04d5a395..f0f04b39 100644 --- a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayHook.kt +++ b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeGatewayHook.kt @@ -36,6 +36,9 @@ class PeriscopeGatewayHook : AbstractGatewayModuleHook() { logger.debug("Registering components...") context.registerComponents() + + logger.debug("Starting project lifecycles...") + context.startupProjectLifecycles() } override fun shutdown() { @@ -50,6 +53,9 @@ class PeriscopeGatewayHook : AbstractGatewayModuleHook() { logger.debug("Removing servlets...") context.removeServlets() + + logger.debug("Stopping project lifecycles...") + context.shutdownProjectLifecycles() } override fun getMountedResourceFolder(): Optional { diff --git a/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptChangeListener.kt b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptChangeListener.kt new file mode 100644 index 00000000..535c89a3 --- /dev/null +++ b/modules/periscope/gateway/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptChangeListener.kt @@ -0,0 +1,53 @@ +package com.mussonindustrial.ignition.embr.periscope.resources.typescript + +import com.inductiveautomation.ignition.common.gson.JsonObject +import com.inductiveautomation.ignition.common.project.RuntimeProject +import com.inductiveautomation.ignition.common.project.resource.ProjectResource +import com.inductiveautomation.ignition.common.project.resource.ProjectResourceId +import com.inductiveautomation.ignition.gateway.project.ProjectLifecycle +import com.inductiveautomation.ignition.gateway.project.ProjectLifecycleFactory +import com.inductiveautomation.ignition.gateway.project.ProjectManager +import com.inductiveautomation.ignition.gateway.project.ResourceFilter +import com.inductiveautomation.perspective.gateway.api.PerspectiveContext +import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource + +class TypeScriptChangeListener(val context: PerspectiveContext, projectManager: ProjectManager) : + ProjectLifecycleFactory(projectManager) { + val filter: ResourceFilter = + ResourceFilter.newBuilder().addResourceType(TypeScriptResource.Companion.type).build() + + override fun createProjectLifecycle(project: RuntimeProject): Notifier { + return Notifier(project) + } + + override fun getResourceFilter(): ResourceFilter { + return filter + } + + inner class Notifier(project: RuntimeProject) : ProjectLifecycle(project) { + + fun notifyClients() { + context.sessionMonitor.getClientSessionsForProject(project.name).forEach { + it.pages.forEach { page -> + page.send("periscope-client-resource-refresh", JsonObject()) + } + } + } + + override fun onStartup(resourceIds: List) {} + + override fun onShutdown(resourceIds: List) {} + + override fun onResourcesCreated(resources: List) { + notifyClients() + } + + override fun onResourcesModified(resources: List) { + notifyClients() + } + + override fun onResourcesDeleted(resources: List) { + notifyClients() + } + } +} diff --git a/modules/periscope/ts-compiler/src/importTransformer.ts b/modules/periscope/ts-compiler/src/importTransformer.ts new file mode 100644 index 00000000..d6361db5 --- /dev/null +++ b/modules/periscope/ts-compiler/src/importTransformer.ts @@ -0,0 +1,256 @@ +import ts from 'typescript' + +/** + * Resolve relative module paths. + */ +function resolveModulePath(base: string, spec: string): string { + if (!spec.startsWith('.')) return spec + + const baseParts = base.split('/') + baseParts.pop() + + for (const part of spec.split('/')) { + if (part === '.' || part === '') continue + if (part === '..') baseParts.pop() + else baseParts.push(part) + } + + return baseParts.join('/').replace(/\/+/g, '/') +} + +/** + * Runtime globals live under: + * window.__embrGlobals.scripting.globals. + */ +function isGlobalImport(spec: string) { + return !spec.startsWith('.') && !spec.includes('/') +} + +function createGlobalAccess(name: string): ts.Expression { + return ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('window'), + '__embrGlobals' + ), + 'modules' + ), + name + ) +} + +type ImportRecord = { + moduleName: string + clause: ts.ImportClause | null + isGlobal: boolean +} + +export function createImportTransformer( + modulePath: string +): ts.TransformerFactory { + return (context) => { + return (sourceFile) => { + const imports: ImportRecord[] = [] + + const visitor = (node: ts.Node): ts.Node => { + if ( + ts.isImportDeclaration(node) && + ts.isStringLiteral(node.moduleSpecifier) + ) { + const spec = node.moduleSpecifier.text + const global = isGlobalImport(spec) + + imports.push({ + moduleName: global ? spec : resolveModulePath(modulePath, spec), + clause: node.importClause ?? null, + isGlobal: global, + }) + + return ts.factory.createEmptyStatement() + } + + return ts.visitEachChild(node, visitor, context) + } + + const newSourceFile = ts.visitNode(sourceFile, visitor) as ts.SourceFile + + if (imports.length === 0) return newSourceFile + + const moduleImports = imports.filter((i) => !i.isGlobal) + const statements: ts.Statement[] = [] + + let modulesIdentifier: ts.Identifier | null = null + + /* + * Generate Promise.all() for module imports + */ + if (moduleImports.length > 0) { + modulesIdentifier = ts.factory.createIdentifier('__modules') + + const calls = moduleImports.map((imp) => + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('window'), + '__embrGlobals' + ), + 'getClientResource' + ), + undefined, + [ts.factory.createStringLiteral(imp.moduleName)] + ) + ) + + statements.push( + ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + modulesIdentifier, + undefined, + undefined, + ts.factory.createAwaitExpression( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('Promise'), + 'all' + ), + undefined, + [ts.factory.createArrayLiteralExpression(calls)] + ) + ) + ), + ], + ts.NodeFlags.Const + ) + ) + ) + } + + /* + * Generate bindings + */ + let moduleIndex = 0 + + for (const imp of imports) { + const clause = imp.clause + if (!clause) continue + + const sourceExpr = imp.isGlobal + ? createGlobalAccess(imp.moduleName) + : ts.factory.createElementAccessExpression( + modulesIdentifier!, + ts.factory.createNumericLiteral(moduleIndex++) + ) + + /* + * Namespace import + * + * import * as foo from 'bar' + */ + if ( + clause.namedBindings && + ts.isNamespaceImport(clause.namedBindings) + ) { + statements.push( + ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + clause.namedBindings.name, + undefined, + undefined, + sourceExpr + ), + ], + ts.NodeFlags.Const + ) + ) + ) + continue + } + + /* + * Default-only global import + * + * import foo from 'lodash' + * -> const foo = window.__embrGlobals.modules.lodash + */ + if ( + imp.isGlobal && + clause.name && + !(clause.namedBindings && ts.isNamedImports(clause.namedBindings)) + ) { + statements.push( + ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + clause.name, + undefined, + undefined, + sourceExpr + ), + ], + ts.NodeFlags.Const + ) + ) + ) + continue + } + + /* + * Named / default imports (normal destructuring) + */ + const elements: ts.BindingElement[] = [] + + if (clause.name) { + elements.push( + ts.factory.createBindingElement( + undefined, + ts.factory.createIdentifier('default'), + clause.name + ) + ) + } + + if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { + for (const spec of clause.namedBindings.elements) { + elements.push( + ts.factory.createBindingElement( + undefined, + spec.propertyName, + spec.name + ) + ) + } + } + + statements.push( + ts.factory.createVariableStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + ts.factory.createObjectBindingPattern(elements), + undefined, + undefined, + sourceExpr + ), + ], + ts.NodeFlags.Const + ) + ) + ) + } + + return ts.factory.updateSourceFile(newSourceFile, [ + ...statements, + ...newSourceFile.statements.filter((s) => !ts.isEmptyStatement(s)), + ]) + } + } +} diff --git a/modules/periscope/ts-compiler/src/index.ts b/modules/periscope/ts-compiler/src/index.ts index dfb64550..2f794cf5 100644 --- a/modules/periscope/ts-compiler/src/index.ts +++ b/modules/periscope/ts-compiler/src/index.ts @@ -3,8 +3,9 @@ import * as parserTypeScript from 'prettier/parser-typescript' import * as prettierPluginEstree from 'prettier/plugins/estree' import ts from 'typescript' +import { createImportTransformer } from './importTransformer' -export function compile(input: string) { +export function compile(input: string, modulePath: string) { return ts.transpileModule(input, { compilerOptions: { module: ts.ModuleKind.ESNext, @@ -14,7 +15,7 @@ export function compile(input: string) { jsx: ts.JsxEmit.React, }, transformers: { - before: [importTransformer], + before: [createImportTransformer(modulePath)], }, }) } @@ -30,169 +31,171 @@ export async function format(source: string, cursorOffset: number) { plugins: [prettierPluginEstree as unknown as string, parserTypeScript], }) } - -/** - * Creates a transformer that converts: - * import 'Module' - * TO: - * window.__embrGlobals.scripting.globals.periscope.import('Module') - */ -const importTransformer: ts.TransformerFactory = (context) => { - return (sourceFile) => { - const imports: { moduleName: string; clause: ts.ImportClause | null }[] = [] - - const visitor = (node: ts.Node): ts.Node => { - if ( - ts.isImportDeclaration(node) && - ts.isStringLiteral(node.moduleSpecifier) - ) { - imports.push({ - moduleName: node.moduleSpecifier.text, - clause: node.importClause ?? null, - }) - // Remove original import; replaced at top - return ts.factory.createEmptyStatement() - } - return ts.visitEachChild(node, visitor, context) - } - - const newSourceFile = ts.visitNode(sourceFile, visitor) as ts.SourceFile - - if (imports.length === 0) return newSourceFile - - // Create Promise.all([...]) - const callExpressions = imports.map((imp) => - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('window'), - '__embrGlobals' - ), - 'scripting' - ), - 'globals' - ), - 'periscope' - ), - 'getClientResource' - ), - undefined, - [ts.factory.createStringLiteral(imp.moduleName)] - ) - ) - - const modulesIdentifier = ts.factory.createIdentifier('__modules') - - const modulesDeclaration = ts.factory.createVariableStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - modulesIdentifier, - undefined, - undefined, - ts.factory.createAwaitExpression( - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('Promise'), - 'all' - ), - undefined, - [ts.factory.createArrayLiteralExpression(callExpressions)] - ) - ) - ), - ], - ts.NodeFlags.Const - ) - ) - - const statements: ts.Statement[] = [modulesDeclaration] - - imports.forEach((imp, i) => { - const clause = imp.clause - - if (!clause) { - // Side-effect import: no statement needed - return - } - - if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) { - // const ns = __modules[i] - statements.push( - ts.factory.createVariableStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - clause.namedBindings.name, - undefined, - undefined, - ts.factory.createElementAccessExpression( - modulesIdentifier, - ts.factory.createNumericLiteral(i) - ) - ), - ], - ts.NodeFlags.Const - ) - ) - ) - return - } - - const elements: ts.BindingElement[] = [] - - if (clause.name) { - elements.push( - ts.factory.createBindingElement( - undefined, - ts.factory.createIdentifier('default'), - clause.name - ) - ) - } - - if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { - clause.namedBindings.elements.forEach((specifier) => { - elements.push( - ts.factory.createBindingElement( - undefined, - specifier.propertyName, - specifier.name - ) - ) - }) - } - - statements.push( - ts.factory.createVariableStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - ts.factory.createObjectBindingPattern(elements), - undefined, - undefined, - ts.factory.createElementAccessExpression( - modulesIdentifier, - ts.factory.createNumericLiteral(i) - ) - ), - ], - ts.NodeFlags.Const - ) - ) - ) - }) - - // Prepend generated statements to the file - return ts.factory.updateSourceFile(newSourceFile, [ - ...statements, - ...newSourceFile.statements.filter((s) => !ts.isEmptyStatement(s)), - ]) - } -} +// +// /** +// * Creates a transformer that converts: +// * import 'Module' +// * TO: +// * window.__embrGlobals.scripting.globals.periscope.import('Module') +// */ +// function createImportTransformer( +// modulePath: string +// ): ts.TransformerFactory { +// return (context) => { +// return (sourceFile) => { +// const imports: { moduleName: string; clause: ts.ImportClause | null }[] = +// [] +// +// const visitor = (node: ts.Node): ts.Node => { +// if ( +// ts.isImportDeclaration(node) && +// ts.isStringLiteral(node.moduleSpecifier) +// ) { +// imports.push({ +// moduleName: resolveModulePath( +// modulePath, +// node.moduleSpecifier.text +// ), +// clause: node.importClause ?? null, +// }) +// // Remove original import; replaced at top +// return ts.factory.createEmptyStatement() +// } +// return ts.visitEachChild(node, visitor, context) +// } +// +// const newSourceFile = ts.visitNode(sourceFile, visitor) as ts.SourceFile +// +// if (imports.length === 0) return newSourceFile +// +// // Create Promise.all([...]) +// const callExpressions = imports.map((imp) => +// ts.factory.createCallExpression( +// ts.factory.createPropertyAccessExpression( +// ts.factory.createPropertyAccessExpression( +// ts.factory.createIdentifier('window'), +// '__embrGlobals' +// ), +// 'getClientResource' +// ), +// undefined, +// [ts.factory.createStringLiteral(imp.moduleName)] +// ) +// ) +// +// const modulesIdentifier = ts.factory.createIdentifier('__modules') +// +// const modulesDeclaration = ts.factory.createVariableStatement( +// undefined, +// ts.factory.createVariableDeclarationList( +// [ +// ts.factory.createVariableDeclaration( +// modulesIdentifier, +// undefined, +// undefined, +// ts.factory.createAwaitExpression( +// ts.factory.createCallExpression( +// ts.factory.createPropertyAccessExpression( +// ts.factory.createIdentifier('Promise'), +// 'all' +// ), +// undefined, +// [ts.factory.createArrayLiteralExpression(callExpressions)] +// ) +// ) +// ), +// ], +// ts.NodeFlags.Const +// ) +// ) +// +// const statements: ts.Statement[] = [modulesDeclaration] +// +// imports.forEach((imp, i) => { +// const clause = imp.clause +// +// if (!clause) { +// // Side-effect import: no statement needed +// return +// } +// +// if ( +// clause.namedBindings && +// ts.isNamespaceImport(clause.namedBindings) +// ) { +// // const ns = __modules[i] +// statements.push( +// ts.factory.createVariableStatement( +// undefined, +// ts.factory.createVariableDeclarationList( +// [ +// ts.factory.createVariableDeclaration( +// clause.namedBindings.name, +// undefined, +// undefined, +// ts.factory.createElementAccessExpression( +// modulesIdentifier, +// ts.factory.createNumericLiteral(i) +// ) +// ), +// ], +// ts.NodeFlags.Const +// ) +// ) +// ) +// return +// } +// +// const elements: ts.BindingElement[] = [] +// +// if (clause.name) { +// elements.push( +// ts.factory.createBindingElement( +// undefined, +// ts.factory.createIdentifier('default'), +// clause.name +// ) +// ) +// } +// +// if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { +// clause.namedBindings.elements.forEach((specifier) => { +// elements.push( +// ts.factory.createBindingElement( +// undefined, +// specifier.propertyName, +// specifier.name +// ) +// ) +// }) +// } +// +// statements.push( +// ts.factory.createVariableStatement( +// undefined, +// ts.factory.createVariableDeclarationList( +// [ +// ts.factory.createVariableDeclaration( +// ts.factory.createObjectBindingPattern(elements), +// undefined, +// undefined, +// ts.factory.createElementAccessExpression( +// modulesIdentifier, +// ts.factory.createNumericLiteral(i) +// ) +// ), +// ], +// ts.NodeFlags.Const +// ) +// ) +// ) +// }) +// +// // Prepend generated statements to the file +// return ts.factory.updateSourceFile(newSourceFile, [ +// ...statements, +// ...newSourceFile.statements.filter((s) => !ts.isEmptyStatement(s)), +// ]) +// } +// } +// } diff --git a/modules/periscope/web/src/components/embedding/react.tsx b/modules/periscope/web/src/components/embedding/react.tsx index f1b56d9b..478c0556 100644 --- a/modules/periscope/web/src/components/embedding/react.tsx +++ b/modules/periscope/web/src/components/embedding/react.tsx @@ -27,8 +27,7 @@ import { useDeepCompareMemo, } from '@embr-js/perspective-client' import { transformProps } from '@embr-js/utils' - -import { getClientResource } from '../../extensions/client-resources' +import { getClientResource } from '../../extensions' const COMPONENT_TYPE = 'embr.periscope.embedding.react' diff --git a/modules/periscope/web/src/extensions/client-resources/index.ts b/modules/periscope/web/src/extensions/clientResource.ts similarity index 67% rename from modules/periscope/web/src/extensions/client-resources/index.ts rename to modules/periscope/web/src/extensions/clientResource.ts index 2d28ea68..63178032 100644 --- a/modules/periscope/web/src/extensions/client-resources/index.ts +++ b/modules/periscope/web/src/extensions/clientResource.ts @@ -1,7 +1,16 @@ +import Lodash from 'lodash' +import PerspectiveClient from '@inductiveautomation/perspective-client' +import PerspectiveComponents from '@inductiveautomation/perspective-components' +import React from 'react' + import { ClientStore } from '@inductiveautomation/perspective-client' import { merge } from 'lodash' import { getEmbrGlobals } from '@embr-js/perspective-client' +const PROTOCOL = { + REFRESH: 'periscope-client-resource-refresh', +} + export type JsResource = { path: string hash: string @@ -14,10 +23,20 @@ export type ResourceManifest = { export type ResourceManifestResponse = { data: ResourceManifest } + let loadedResourceManifest: ResourceManifest | undefined let manifestPromise: Promise | undefined const modules = new Map() +function projectPaths(projectName: string) { + const base = '/data/embr-periscope/client-resources' + return { + base, + jsBase: `${base}/${projectName}/js`, + manifest: `${base}/${projectName}/manifest.json`, + } +} + export async function getResourceManifest(clientStore: ClientStore) { if (loadedResourceManifest) { return loadedResourceManifest @@ -69,16 +88,16 @@ export async function getClientResource( } } -function projectPaths(projectName: string) { - const base = '/data/embr-periscope/client-resources' - return { - base, - jsBase: `${base}/${projectName}/js`, - manifest: `${base}/${projectName}/manifest.json`, - } +export async function refreshClientResources(clientStore: ClientStore) { + await getResourceManifest(clientStore).then( + async (manifest) => + await Promise.all( + manifest.js.map((js) => getClientResource(clientStore, js.path)) + ) + ) } -export function installClientResourceImport(clientStore: ClientStore) { +export function installClientResourceGlobals(clientStore: ClientStore) { const throwError = (message: string) => { throw new Error(message) } @@ -87,14 +106,22 @@ export function installClientResourceImport(clientStore: ClientStore) { periscope: { module: (path: string) => modules.get(path) ?? throwError(`Module ${path} not found.`), - getClientResource: async (path: string) => - await getClientResource(clientStore, path), }, }) -} -export const PROTOCOL = { - REFRESH: 'periscope-client-resource-refresh', + merge(getEmbrGlobals(), { + getClientResource: async (path: string) => + await getClientResource(clientStore, path), + }) + + merge(getEmbrGlobals().modules, { + lodash: Lodash, + '@inductiveautomation/perspective-client': PerspectiveClient, + '@inductiveautomation/perspective-components': PerspectiveComponents, + react: React, + periscope: getEmbrGlobals().scripting.globals.periscope, + perspective: getEmbrGlobals().scripting.globals.perspective, + }) } export function installClientResourceListener(clientStore: ClientStore) { @@ -102,3 +129,9 @@ export function installClientResourceListener(clientStore: ClientStore) { window.location.reload() }) } + +export async function installClientResources(clientStore: ClientStore) { + installClientResourceGlobals(clientStore) + installClientResourceListener(clientStore) + await refreshClientResources(clientStore) +} diff --git a/modules/periscope/web/src/extensions/index.ts b/modules/periscope/web/src/extensions/index.ts index 36812c0f..966c28d7 100644 --- a/modules/periscope/web/src/extensions/index.ts +++ b/modules/periscope/web/src/extensions/index.ts @@ -1,24 +1,14 @@ import { ClientStore } from '@inductiveautomation/perspective-client' -import { - installClientResourceListener, - installClientResourceImport, - getResourceManifest, - getClientResource, -} from './client-resources' import { installRunJavaScript } from './runJavaScript' import { installToasts } from './toast' +import { installClientResources } from './clientResource' +export * from './clientResource' export * from './runJavaScript' +export * from './toast' export async function installExtensions(clientStore: ClientStore) { installRunJavaScript(clientStore) installToasts(clientStore) - installClientResourceImport(clientStore) - installClientResourceListener(clientStore) - await getResourceManifest(clientStore).then( - async (manifest) => - await Promise.all( - manifest.js.map((js) => getClientResource(clientStore, js.path)) - ) - ) + await installClientResources(clientStore) } From 7bcf4c0015f9686ccc7b082f39b838fe1b397bc3 Mon Sep 17 00:00:00 2001 From: Ben Musson Date: Sat, 7 Mar 2026 01:52:39 -0500 Subject: [PATCH 6/6] add node menu items --- .../embr/periscope/icons/PeriscopeIcons.kt | 11 ++ .../navtree/model/HiddenActionResourceNode.kt | 9 + .../navtree/model/TypeScriptResourceFolder.kt | 13 +- .../navtree/model/TypeScriptResourceNode.kt | 78 +++++++- .../typescript/TypeScriptResourceWorkspace.kt | 26 +-- modules/periscope/ts-compiler/src/index.ts | 169 +----------------- 6 files changed, 115 insertions(+), 191 deletions(-) create mode 100644 modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/icons/PeriscopeIcons.kt diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/icons/PeriscopeIcons.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/icons/PeriscopeIcons.kt new file mode 100644 index 00000000..4dd0cfb6 --- /dev/null +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/icons/PeriscopeIcons.kt @@ -0,0 +1,11 @@ +package com.mussonindustrial.ignition.embr.periscope.icons + +import com.inductiveautomation.ignition.designer.navtree.icon.InteractiveSvgIcon +import com.mussonindustrial.ignition.embr.periscope.Meta + +object PeriscopeIcons { + private fun icon(name: String) = InteractiveSvgIcon(Meta::class.java, "images/svgicons/$name") + + val tsx = icon("tsx.svg") + val folderClosed = icon("folder-closed.svg") +} diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/HiddenActionResourceNode.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/HiddenActionResourceNode.kt index b16ff6f8..4a29ff57 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/HiddenActionResourceNode.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/HiddenActionResourceNode.kt @@ -5,6 +5,7 @@ import com.inductiveautomation.ignition.designer.model.DesignerContext import com.inductiveautomation.ignition.designer.navtree.model.AbstractNavTreeNode import com.inductiveautomation.ignition.designer.tabbedworkspace.ResourceNode import com.inductiveautomation.ignition.designer.tabbedworkspace.TabbedResourceWorkspace +import javax.swing.JMenuItem import javax.swing.JPopupMenu import javax.swing.tree.TreePath @@ -13,6 +14,14 @@ abstract class HiddenActionResourceNode( workspace: TabbedResourceWorkspace, resource: ProjectResource, ) : ResourceNode(context, workspace, resource) { + + fun JPopupMenu.item(text: String, action: (JMenuItem) -> Unit): JMenuItem { + val item = JMenuItem(text) + item.addActionListener { action(item) } + add(item) + return item + } + override fun initPopupMenu( menu: JPopupMenu, paths: Array, diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceFolder.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceFolder.kt index f61836b3..77bce725 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceFolder.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceFolder.kt @@ -7,6 +7,11 @@ import com.inductiveautomation.ignition.designer.tabbedworkspace.ResourceFolderN import com.inductiveautomation.ignition.designer.tabbedworkspace.TabbedResourceWorkspace class TypeScriptResourceFolder : ResourceFolderNode { + constructor( + context: DesignerContext, + workspace: TabbedResourceWorkspace, + ) : super(context, workspace) + constructor( context: DesignerContext, workspace: TabbedResourceWorkspace, @@ -14,11 +19,9 @@ class TypeScriptResourceFolder : ResourceFolderNode { ) : super(context, workspace, resource) override fun createChildNode(resource: ProjectResource): AbstractNavTreeNode? { - return if (workspace.descriptor.resourceType == resource.resourceType) { - if (resource.isFolder) TypeScriptResourceFolder(this.context, this.workspace, resource) - else TypeScriptResourceNode(this.context, this.workspace, resource) - } else { - null + return when (resource.isFolder) { + true -> TypeScriptResourceFolder(context, workspace, resource) + false -> TypeScriptResourceNode(context, workspace, resource) } } } diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceNode.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceNode.kt index b1962f31..2f5b4903 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceNode.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/navtree/model/TypeScriptResourceNode.kt @@ -4,15 +4,58 @@ import com.inductiveautomation.ignition.common.project.resource.ProjectResource import com.inductiveautomation.ignition.designer.model.DesignerContext import com.inductiveautomation.ignition.designer.navtree.model.AbstractNavTreeNode import com.inductiveautomation.ignition.designer.tabbedworkspace.TabbedResourceWorkspace -import javax.swing.JMenuItem +import com.mussonindustrial.embr.common.logging.getLogger +import com.mussonindustrial.ignition.embr.periscope.icons.PeriscopeIcons +import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource +import java.awt.Toolkit +import java.awt.datatransfer.DataFlavor +import java.awt.datatransfer.StringSelection +import java.awt.event.InputEvent +import java.awt.event.KeyEvent import javax.swing.JPopupMenu +import javax.swing.KeyStroke import javax.swing.tree.TreePath class TypeScriptResourceNode( context: DesignerContext, workspace: TabbedResourceWorkspace, - resource: ProjectResource, + val resource: ProjectResource, ) : HiddenActionResourceNode(context, workspace, resource) { + + private val logger = this.getLogger() + + override fun initPopupMenu( + menu: JPopupMenu, + paths: Array, + selection: List, + modifiers: Int, + ) { + + menu + .item("Recompile") { logger.info("TODO: Implement recompile from menu.") } + .apply { + icon = PeriscopeIcons.tsx + mnemonic = KeyEvent.VK_R + accelerator = KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK) + } + + menu + .item("Format") { logger.info("TODO: Implement format from menu.") } + .apply { + icon = PeriscopeIcons.tsx + mnemonic = KeyEvent.VK_F + accelerator = + KeyStroke.getKeyStroke( + KeyEvent.VK_F, + InputEvent.CTRL_DOWN_MASK or InputEvent.ALT_DOWN_MASK, + ) + } + + menu.addSeparator() + + super.initPopupMenu(menu, paths, selection, modifiers) + } + override fun addShiftClickMenuItems( menu: JPopupMenu, paths: Array, @@ -20,10 +63,33 @@ class TypeScriptResourceNode( modifiers: Int, ) { - val copyJavaScript = JMenuItem("Copy JavaScript") - val pasteJavaScript = JMenuItem("Paste JavaScript") menu.addSeparator() - menu.add(copyJavaScript) - menu.add(pasteJavaScript) + + menu + .item("Copy Source") { + val typescriptResource = TypeScriptResource.fromResource(resource) + Toolkit.getDefaultToolkit() + .systemClipboard + .setContents(StringSelection(typescriptResource.source), null) + } + .apply { icon = PeriscopeIcons.tsx } + + menu + .item("Paste Source") { + val clipboard = Toolkit.getDefaultToolkit().systemClipboard + val contents = clipboard.getContents(null) + + val text = + if (contents?.isDataFlavorSupported(DataFlavor.stringFlavor) == true) + contents.getTransferData(DataFlavor.stringFlavor) as String + else null + + if (text == null) return@item + + context.project?.createOrModify(resource.resourcePath) { builder -> + builder.putData(TypeScriptResource.SOURCE_KEY, text.toByteArray()) + } + } + .apply { icon = PeriscopeIcons.tsx } } } diff --git a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt index f83ee8cf..d601252f 100644 --- a/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt +++ b/modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt @@ -6,6 +6,7 @@ import com.inductiveautomation.ignition.common.project.resource.ProjectResourceB import com.inductiveautomation.ignition.common.project.resource.ResourcePath import com.inductiveautomation.ignition.designer.model.DesignerContext import com.inductiveautomation.ignition.designer.navtree.icon.InteractiveSvgIcon +import com.inductiveautomation.ignition.designer.navtree.model.AbstractNavTreeNode import com.inductiveautomation.ignition.designer.navtree.model.MutableNavTreeNode import com.inductiveautomation.ignition.designer.tabbedworkspace.* import com.inductiveautomation.ignition.designer.workspacewelcome.RecentlyModifiedTablePanel @@ -14,6 +15,8 @@ import com.inductiveautomation.ignition.designer.workspacewelcome.ResourceBuilde import com.inductiveautomation.ignition.designer.workspacewelcome.WorkspaceWelcomePanel import com.mussonindustrial.embr.common.logging.getLogger import com.mussonindustrial.ignition.embr.periscope.Meta +import com.mussonindustrial.ignition.embr.periscope.icons.PeriscopeIcons +import com.mussonindustrial.ignition.embr.periscope.navtree.model.TypeScriptResourceFolder import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource import java.util.* import java.util.function.Consumer @@ -31,8 +34,8 @@ class TypeScriptResourceWorkspace( .resourceType(TypeScriptResource.type) .rootFolderText("TypeScript") .nounKey("periscope.typescript.noun") - .rootIcon(InteractiveSvgIcon(Meta::class.java, "images/svgicons/folder-closed.svg")) - .icon(InteractiveSvgIcon(Meta::class.java, "images/svgicons/tsx.svg")) + .rootIcon(PeriscopeIcons.folderClosed) + .icon(PeriscopeIcons.tsx) .navTreeLocation(9999999) .scope(ApplicationScope.GATEWAY) .build(), @@ -57,6 +60,10 @@ class TypeScriptResourceWorkspace( return this.parent } + override fun createRootNavTreeNode(): AbstractNavTreeNode { + return TypeScriptResourceFolder(context, this) + } + override fun newResourceEditor(resourcePath: ResourcePath): ResourceEditor<*> { return TypeScriptResourceEditor(this, resourcePath) } @@ -66,10 +73,7 @@ class TypeScriptResourceWorkspace( object : NewResourceAction(this, folderNode, resourceConstructor) { init { putValue(NAME, "New TypeScript Module") - putValue( - SMALL_ICON, - InteractiveSvgIcon(Meta::class.java, "images/svgicons/tsx.svg"), - ) + putValue(SMALL_ICON, PeriscopeIcons.tsx) } override fun newResourceName() = "NewTypeScriptModule" @@ -93,16 +97,14 @@ class TypeScriptResourceWorkspace( resourceConstructor, ) ), - ) { path: ResourcePath -> - open(path) - }, + this@TypeScriptResourceWorkspace::open, + ), RecentlyModifiedTablePanel( context, TypeScriptResource.type, BundleUtil.i18n("periscope.typescript.nouns-long"), - ) { path: ResourcePath -> - open(path) - }, + this@TypeScriptResourceWorkspace::open, + ), ) } } diff --git a/modules/periscope/ts-compiler/src/index.ts b/modules/periscope/ts-compiler/src/index.ts index 2f794cf5..e8153268 100644 --- a/modules/periscope/ts-compiler/src/index.ts +++ b/modules/periscope/ts-compiler/src/index.ts @@ -28,174 +28,7 @@ export async function format(source: string, cursorOffset: number) { endOfLine: 'lf', cursorOffset, parser: 'typescript', + printWidth: 120, plugins: [prettierPluginEstree as unknown as string, parserTypeScript], }) } -// -// /** -// * Creates a transformer that converts: -// * import 'Module' -// * TO: -// * window.__embrGlobals.scripting.globals.periscope.import('Module') -// */ -// function createImportTransformer( -// modulePath: string -// ): ts.TransformerFactory { -// return (context) => { -// return (sourceFile) => { -// const imports: { moduleName: string; clause: ts.ImportClause | null }[] = -// [] -// -// const visitor = (node: ts.Node): ts.Node => { -// if ( -// ts.isImportDeclaration(node) && -// ts.isStringLiteral(node.moduleSpecifier) -// ) { -// imports.push({ -// moduleName: resolveModulePath( -// modulePath, -// node.moduleSpecifier.text -// ), -// clause: node.importClause ?? null, -// }) -// // Remove original import; replaced at top -// return ts.factory.createEmptyStatement() -// } -// return ts.visitEachChild(node, visitor, context) -// } -// -// const newSourceFile = ts.visitNode(sourceFile, visitor) as ts.SourceFile -// -// if (imports.length === 0) return newSourceFile -// -// // Create Promise.all([...]) -// const callExpressions = imports.map((imp) => -// ts.factory.createCallExpression( -// ts.factory.createPropertyAccessExpression( -// ts.factory.createPropertyAccessExpression( -// ts.factory.createIdentifier('window'), -// '__embrGlobals' -// ), -// 'getClientResource' -// ), -// undefined, -// [ts.factory.createStringLiteral(imp.moduleName)] -// ) -// ) -// -// const modulesIdentifier = ts.factory.createIdentifier('__modules') -// -// const modulesDeclaration = ts.factory.createVariableStatement( -// undefined, -// ts.factory.createVariableDeclarationList( -// [ -// ts.factory.createVariableDeclaration( -// modulesIdentifier, -// undefined, -// undefined, -// ts.factory.createAwaitExpression( -// ts.factory.createCallExpression( -// ts.factory.createPropertyAccessExpression( -// ts.factory.createIdentifier('Promise'), -// 'all' -// ), -// undefined, -// [ts.factory.createArrayLiteralExpression(callExpressions)] -// ) -// ) -// ), -// ], -// ts.NodeFlags.Const -// ) -// ) -// -// const statements: ts.Statement[] = [modulesDeclaration] -// -// imports.forEach((imp, i) => { -// const clause = imp.clause -// -// if (!clause) { -// // Side-effect import: no statement needed -// return -// } -// -// if ( -// clause.namedBindings && -// ts.isNamespaceImport(clause.namedBindings) -// ) { -// // const ns = __modules[i] -// statements.push( -// ts.factory.createVariableStatement( -// undefined, -// ts.factory.createVariableDeclarationList( -// [ -// ts.factory.createVariableDeclaration( -// clause.namedBindings.name, -// undefined, -// undefined, -// ts.factory.createElementAccessExpression( -// modulesIdentifier, -// ts.factory.createNumericLiteral(i) -// ) -// ), -// ], -// ts.NodeFlags.Const -// ) -// ) -// ) -// return -// } -// -// const elements: ts.BindingElement[] = [] -// -// if (clause.name) { -// elements.push( -// ts.factory.createBindingElement( -// undefined, -// ts.factory.createIdentifier('default'), -// clause.name -// ) -// ) -// } -// -// if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { -// clause.namedBindings.elements.forEach((specifier) => { -// elements.push( -// ts.factory.createBindingElement( -// undefined, -// specifier.propertyName, -// specifier.name -// ) -// ) -// }) -// } -// -// statements.push( -// ts.factory.createVariableStatement( -// undefined, -// ts.factory.createVariableDeclarationList( -// [ -// ts.factory.createVariableDeclaration( -// ts.factory.createObjectBindingPattern(elements), -// undefined, -// undefined, -// ts.factory.createElementAccessExpression( -// modulesIdentifier, -// ts.factory.createNumericLiteral(i) -// ) -// ), -// ], -// ts.NodeFlags.Const -// ) -// ) -// ) -// }) -// -// // Prepend generated statements to the file -// return ts.factory.updateSourceFile(newSourceFile, [ -// ...statements, -// ...newSourceFile.statements.filter((s) => !ts.isEmptyStatement(s)), -// ]) -// } -// } -// }