Skip to content

Commit 112b724

Browse files
committed
loading seems to work
1 parent 979611a commit 112b724

File tree

12 files changed

+297
-348
lines changed

12 files changed

+297
-348
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './components'
22
export * from './hooks'
3+
export * from './globals'
34
export * from './scripting'
45
export * from './transforms'
56
export * from './utils'

libraries/javascript/perspective-client/src/utils/waitForClientStore/waitForClientStore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import { getClientStore } from '../index'
77
export default function waitForClientStore(
88
callback: (clientStore: ClientStore) => void
99
) {
10-
setTimeout(function () {
10+
requestAnimationFrame(() => {
1111
const clientStore = getClientStore()
1212
if (clientStore) {
1313
callback(clientStore)
1414
} else {
1515
waitForClientStore(callback)
1616
}
17-
}, 100)
17+
})
1818
}

modules/periscope/common/src/main/resources/schemas/components/embr.periscope.embedding.react/props.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "string",
66
"description": "Component Path.",
77
"extension": {
8-
"suggestion-source": "embr-periscope-client-resource"
8+
"suggestion-source": "embr-periscope-typescript-resource"
99
}
1010
},
1111
"props": {

modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/PeriscopeDesignerContext.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import com.mussonindustrial.embr.designer.EmbrDesignerContextImpl
1010
import com.mussonindustrial.embr.perspective.designer.component.asDesignerComponent
1111
import com.mussonindustrial.embr.perspective.designer.component.registerComponent
1212
import com.mussonindustrial.embr.perspective.designer.component.removeComponent
13-
import com.mussonindustrial.ignition.embr.periscope.component.ClientResourceSuggestionSource
1413
import com.mussonindustrial.ignition.embr.periscope.component.ComponentIdSuggestionSource
14+
import com.mussonindustrial.ignition.embr.periscope.component.TypeScriptResourceSuggestionSource
1515
import com.mussonindustrial.ignition.embr.periscope.component.embedding.*
1616
import com.mussonindustrial.ignition.embr.periscope.resources.typescript.TypeScriptResourceListener
1717
import com.mussonindustrial.ignition.embr.periscope.resources.typescript.TypeScriptResourceWorkspace
@@ -39,8 +39,8 @@ class PeriscopeDesignerContext(val context: DesignerContext) :
3939
ComponentIdSuggestionSource(this@PeriscopeDesignerContext),
4040
)
4141
registerSuggestionSource(
42-
ClientResourceSuggestionSource.ID,
43-
ClientResourceSuggestionSource(this@PeriscopeDesignerContext),
42+
TypeScriptResourceSuggestionSource.ID,
43+
TypeScriptResourceSuggestionSource(this@PeriscopeDesignerContext),
4444
)
4545
}
4646
}

modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/ClientResourceSuggestionSource.kt renamed to modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/component/TypeScriptResourceSuggestionSource.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import com.mussonindustrial.ignition.embr.periscope.PeriscopeDesignerContext
99
import com.mussonindustrial.ignition.embr.periscope.resources.TypeScriptResource
1010
import java.util.concurrent.CompletableFuture
1111

12-
class ClientResourceSuggestionSource(private val context: PeriscopeDesignerContext) :
12+
class TypeScriptResourceSuggestionSource(private val context: PeriscopeDesignerContext) :
1313
SuggestionSource {
1414

1515
companion object {
16-
const val ID = "embr-periscope-client-resource"
16+
const val ID = "embr-periscope-typescript-resource"
1717
}
1818

1919
override fun getSuggestions(

modules/periscope/designer/src/main/kotlin/com/mussonindustrial/ignition/embr/periscope/resources/typescript/TypeScriptResourceWorkspace.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class TypeScriptResourceWorkspace(
7171
putValue(NAME, "New TypeScript Module")
7272
putValue(
7373
SMALL_ICON,
74-
InteractiveSvgIcon(Meta::class.java, "images/svgicons/theme.svg"),
74+
InteractiveSvgIcon(Meta::class.java, "images/svgicons/tsx.svg"),
7575
)
7676
}
7777

modules/periscope/gateway/src/main/resources/localization.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ script.invokeOnQueue.desc=Run a function on a Perspective execution queue, with
1515
script.invokeOnQueue.param.function=Python function to run. Should be a function reference that takes a single "scope" parameter.
1616
script.invokeOnQueue.param.delay=Optional. Time in milliseconds before running the function.
1717
script.invokeOnQueue.param.scope=Optional. Lifecycle scope. If specified, must be "session", "page", or "view", otherwise the default will be "view".
18+
1819
javascript-module.noun=JavaScript Module
1920
javascript-module.nouns=JavaScript Modules
2021
javascript-module.noun-long=JavaScript Module

modules/periscope/ts-compiler/src/index.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ export function compile(input: string) {
1313
sourceMap: false,
1414
jsx: ts.JsxEmit.React,
1515
},
16+
transformers: {
17+
before: [importTransformer],
18+
},
1619
})
1720
}
1821

@@ -27,3 +30,92 @@ export async function format(source: string, cursorOffset: number) {
2730
plugins: [prettierPluginEstree as unknown as string, parserTypeScript],
2831
})
2932
}
33+
34+
/**
35+
* Creates a transformer that converts:
36+
* import 'Module'
37+
* TO:
38+
* window.__embrGlobals.scripting.globals.periscope.import('Module')
39+
*/
40+
const importTransformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
41+
return (sourceFile) => {
42+
const visitor = (node: ts.Node): ts.Node => {
43+
if (
44+
ts.isImportDeclaration(node) &&
45+
ts.isStringLiteral(node.moduleSpecifier)
46+
) {
47+
const moduleName = node.moduleSpecifier.text
48+
49+
// Base call: window.__embrGlobals.scripting.globals.periscope.import('moduleName')
50+
const callExpression = ts.factory.createCallExpression(
51+
ts.factory.createPropertyAccessExpression(
52+
ts.factory.createPropertyAccessExpression(
53+
ts.factory.createPropertyAccessExpression(
54+
ts.factory.createPropertyAccessExpression(
55+
ts.factory.createPropertyAccessExpression(
56+
ts.factory.createIdentifier('window'),
57+
'__embrGlobals'
58+
),
59+
'scripting'
60+
),
61+
'globals'
62+
),
63+
'periscope'
64+
),
65+
'getClientResource'
66+
),
67+
undefined,
68+
[ts.factory.createStringLiteral(moduleName)]
69+
)
70+
71+
const awaitedCall = ts.factory.createAwaitExpression(callExpression)
72+
const clause = node.importClause
73+
74+
// Case 1: import 'module' (Side effect only)
75+
if (!clause) {
76+
return ts.factory.createExpressionStatement(awaitedCall)
77+
}
78+
79+
const elements: ts.BindingElement[] = []
80+
81+
// Case 2: import defaultName from 'module'
82+
if (clause.name) {
83+
elements.push(
84+
ts.factory.createBindingElement(undefined, undefined, clause.name)
85+
)
86+
}
87+
88+
// Case 3: import { a, b as c } from 'module'
89+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
90+
clause.namedBindings.elements.forEach((specifier) => {
91+
elements.push(
92+
ts.factory.createBindingElement(
93+
undefined,
94+
specifier.propertyName, // The "remote" name (e.g., 'b' in 'b as c')
95+
specifier.name // The "local" name (e.g., 'c')
96+
)
97+
)
98+
})
99+
}
100+
101+
// Create: const { ... } = window...import('module')
102+
return ts.factory.createVariableStatement(
103+
undefined,
104+
ts.factory.createVariableDeclarationList(
105+
[
106+
ts.factory.createVariableDeclaration(
107+
ts.factory.createObjectBindingPattern(elements),
108+
undefined,
109+
undefined,
110+
awaitedCall
111+
),
112+
],
113+
ts.NodeFlags.Const
114+
)
115+
)
116+
}
117+
return ts.visitEachChild(node, visitor, context)
118+
}
119+
return ts.visitNode(sourceFile, visitor) as ts.SourceFile
120+
}
121+
}

modules/periscope/web/src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ const components = [
3636

3737
components.forEach((c) => ComponentRegistry.register(c))
3838

39-
waitForClientStore((clientStore) => installExtensions(clientStore))
39+
waitForClientStore(async (clientStore) => await installExtensions(clientStore))

modules/periscope/web/src/extensions/client-resources/index.ts

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ClientStore } from '@inductiveautomation/perspective-client'
22
import { merge } from 'lodash'
3-
import { getEmbrGlobals } from '@embr-js/perspective-client/src/globals'
3+
import { getEmbrGlobals } from '@embr-js/perspective-client'
44

55
export type JsResource = {
66
path: string
@@ -16,6 +16,7 @@ export type ResourceManifestResponse = {
1616
}
1717
let loadedResourceManifest: ResourceManifest | undefined
1818
let manifestPromise: Promise<ResourceManifest> | undefined
19+
const modules = new Map<string, unknown>()
1920

2021
export async function getResourceManifest(clientStore: ClientStore) {
2122
if (loadedResourceManifest) {
@@ -25,7 +26,7 @@ export async function getResourceManifest(clientStore: ClientStore) {
2526
if (manifestPromise) return manifestPromise
2627

2728
const { projectName } = clientStore
28-
const paths = buildPaths(projectName)
29+
const paths = projectPaths(projectName)
2930

3031
manifestPromise = (async () => {
3132
const response = await fetch(paths.manifest)
@@ -47,24 +48,28 @@ export async function getClientResource(
4748
clientStore: ClientStore,
4849
path: string
4950
) {
51+
if (modules.has(path)) {
52+
return modules.get(path)
53+
}
54+
5055
const { projectName } = clientStore
51-
const { jsBase } = buildPaths(projectName)
56+
const { jsBase } = projectPaths(projectName)
5257

5358
const manifest = await getResourceManifest(clientStore)
5459
const hash = manifest.js.find((resource) => resource.path == path)?.hash
55-
if (!hash) {
56-
return null
57-
}
60+
if (!hash) return null
5861

5962
try {
60-
return await import(`${jsBase}/${encodeURI(path)}.${hash}.js`)
63+
const module = await import(`${jsBase}/${encodeURI(path)}.${hash}.js`)
64+
modules.set(path, module)
65+
return module
6166
} catch (err) {
6267
console.error(`Failed to load ClientResource '${path}':`, err)
6368
return null
6469
}
6570
}
6671

67-
function buildPaths(projectName: string) {
72+
function projectPaths(projectName: string) {
6873
const base = '/data/embr-periscope/client-resources'
6974
return {
7075
base,
@@ -74,37 +79,20 @@ function buildPaths(projectName: string) {
7479
}
7580

7681
export function installClientResourceImport(clientStore: ClientStore) {
82+
const throwError = (message: string) => {
83+
throw new Error(message)
84+
}
85+
7786
merge(getEmbrGlobals().scripting.globals, {
7887
periscope: {
79-
import: async (path: string) =>
88+
module: (path: string) =>
89+
modules.get(path) ?? throwError(`Module ${path} not found.`),
90+
getClientResource: async (path: string) =>
8091
await getClientResource(clientStore, path),
8192
},
8293
})
8394
}
8495

85-
export function installClientResourceImportMap(clientStore: ClientStore) {
86-
const { projectName } = clientStore
87-
const paths = buildPaths(projectName)
88-
89-
const importMap = {
90-
scopes: {
91-
[paths.jsBase + '/']: {
92-
'@/': `${paths.jsBase}/`,
93-
},
94-
},
95-
}
96-
97-
document.querySelector('script[data-import-map="periscope"]')?.remove()
98-
99-
const script = document.createElement('script')
100-
script.type = 'importmap'
101-
script.dataset.importMap = 'periscope'
102-
script.textContent = JSON.stringify(importMap, null, 2)
103-
void (
104-
document.currentScript?.after(script) ?? document.head.appendChild(script)
105-
)
106-
}
107-
10896
export const PROTOCOL = {
10997
REFRESH: 'periscope-client-resource-refresh',
11098
}

0 commit comments

Comments
 (0)