Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
],
"linked": [],
"access": "public",
"baseBranch": "main",
"baseBranch": "8.3/main",
"updateInternalDependencies": "patch",
"ignore": [],
"privatePackages": {
Expand Down
5 changes: 5 additions & 0 deletions .changeset/cute-eyes-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@embr-js/perspective-client': patch
---

Added `getChildStore` helper function. Allows traversing an `ElementStore` by a list of path components.
5 changes: 5 additions & 0 deletions .changeset/gentle-hornets-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@embr-jvm/perspective-gateway': minor
---

`ThreadContext` now includes a `ComponentModel`. This `ComponentModel` is loaded from the `self` context of the Jython frame.
6 changes: 6 additions & 0 deletions .changeset/pretty-poems-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@embr-modules/periscope-web': minor
'@embr-modules/periscope': minor
---

`runJavaScript` functions now expose `perspective.context.component`. This is the `ComponentModel` of the component that made the `runJavaScript` function call.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { AbstractUIElementStore } from '@inductiveautomation/perspective-client'

/**
* Get a child store given the address path.
* @param store
* @param path
*/
export function getChildStore(
store: AbstractUIElementStore | undefined,
path: number[]
): AbstractUIElementStore | undefined {
let current = store

for (const index of path) {
if (!current?.children || current.children[index] === undefined) {
// Path cannot be fully resolved
return undefined
}
current = current.children[index]
}

return current
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './getChildStore'
1 change: 1 addition & 0 deletions libraries/javascript/perspective-client/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './getChildStore'
export { default as getClientStore } from './getClientStore'
export { default as getDesignerStore } from './getDesignerStore'
export { default as waitForClientStore } from './waitForClientStore'
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
package com.mussonindustrial.embr.perspective.gateway.model

import com.inductiveautomation.perspective.gateway.api.PerspectiveElement
import com.inductiveautomation.perspective.gateway.model.ComponentModel
import com.inductiveautomation.perspective.gateway.model.PageModel
import com.inductiveautomation.perspective.gateway.model.ViewModel
import com.inductiveautomation.perspective.gateway.script.ComponentModelScriptWrapper
import com.inductiveautomation.perspective.gateway.session.InternalSession
import com.mussonindustrial.embr.common.reflect.getPrivateProperty
import com.mussonindustrial.embr.perspective.gateway.model.ThreadContext.Companion.get
import com.mussonindustrial.embr.perspective.gateway.model.ThreadContext.Companion.set
import java.lang.ref.WeakReference
import org.python.core.Py
import org.python.core.PyString

class ThreadContext(view: ViewModel?, page: PageModel?, session: InternalSession?) {
class ThreadContext(
session: InternalSession?,
page: PageModel?,
view: ViewModel?,
component: ComponentModel?,
) {
val view = WeakReference(view)
val page = WeakReference(page)
val session = WeakReference(session)
val component = WeakReference(component)

companion object {
fun get(): ThreadContext {
return ThreadContext(
ViewModel.VIEW.get(),
PageModel.PAGE.get(),
InternalSession.SESSION.get(),
PageModel.PAGE.get(),
ViewModel.VIEW.get(),
getComponentModel(),
)
}

Expand All @@ -27,6 +39,13 @@ class ThreadContext(view: ViewModel?, page: PageModel?, session: InternalSession
PageModel.PAGE.set(threadContext.page.get())
InternalSession.SESSION.set(threadContext.session.get())
}

fun getComponentModel(): ComponentModel? {
val wrapper =
Py.getFrame().locals.__getitem__(PyString("self")) as? ComponentModelScriptWrapper
?: return null
return wrapper.getPrivateProperty("componentModel") as? ComponentModel
}
}
}

Expand All @@ -43,8 +62,9 @@ fun withThreadContext(threadContext: ThreadContext, block: () -> Unit) {
val PerspectiveElement.threadContext: ThreadContext
get() {
return ThreadContext(
this.view as? ViewModel,
this.page as? PageModel,
this.session as? InternalSession,
this.page as? PageModel,
this.view as? ViewModel,
this as? ComponentModel,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ class JavaScriptRunMsg(
threadContext.page.get()?.let {
add("page", JsonObject().apply { addProperty("id", it.id) })
}
threadContext.component.get()?.let {
add(
"component",
JsonObject().apply {
addProperty("componentAddressPath", it.componentAddressPath)
},
)
}
},
)
}
Expand Down
55 changes: 51 additions & 4 deletions modules/periscope/web/src/extensions/runJavaScript.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { createScriptingGlobals } from '@embr-js/perspective-client'
import { toUserScript } from '@embr-js/utils'
import {
createScriptingGlobals,
getChildStore,
} from '@embr-js/perspective-client'
import { toUserScript, UserScriptParams } from '@embr-js/utils'
import { ClientStore } from '@inductiveautomation/perspective-client'

export const PROTOCOL = {
Expand All @@ -8,11 +11,41 @@ export const PROTOCOL = {
ERROR: 'periscope-js-error',
}

type RunJavaScriptPayload = {
function: string
args: UserScriptParams
id: string
context: RunJavaScriptContext
}

type RunJavaScriptContext = {
view?: {
id: string
mountPath: string
resourcePath: string
}
page?: {
id: string
}
component?: {
componentAddressPath: string
}
}

function getChildPath(componentAddressPath?: string) {
return componentAddressPath?.split(':').map(Number) ?? []
}

export function installRunJavaScript(clientStore: ClientStore) {
const thisArg = clientStore

clientStore.connection.handlers.set(PROTOCOL.RUN, (payload) => {
const { function: functionLiteral, args, id } = payload
const {
function: functionLiteral,
args,
id,
context,
} = payload as RunJavaScriptPayload

function resolveSuccess(data: unknown) {
clientStore.connection.send(PROTOCOL.RESOLVE, {
Expand Down Expand Up @@ -42,7 +75,21 @@ export function installRunJavaScript(clientStore: ClientStore) {
}

new Promise((resolve) => {
const globals = createScriptingGlobals({})
const view = clientStore.page.findView(
context.view?.resourcePath ?? '',
context.view?.mountPath ?? ''
)

const componentPath = getChildPath(
context.component?.componentAddressPath
)

const globals = createScriptingGlobals({
client: clientStore,
page: clientStore.page,
view,
component: getChildStore(view, componentPath),
})

const f = toUserScript(functionLiteral, thisArg, globals)
resolve(f.runNamed(args))
Expand Down
Loading