Musson Industrial's Embr-Periscope Module

I'm guessing it's a classloader thing; only the gateway enforces classloader separation for modules. We probably never thought about/tested third party modules trying to use our schemas.

You might be able to, in some semi-hacky way, set the thread context classloader to Perspective's, but I'm not actually sure where you would do that and where you might want to undo it.

1 Like

:man_mage:

More testing required, but this seems to make the gateway happy (no more errors in the logs).

    override fun startup(activationState: LicenseState) {
        ...

        logger.debug("Registering components...")
        val originalClassLoader = Thread.currentThread().contextClassLoader
        Thread.currentThread().contextClassLoader = context.perspectiveContext.javaClass.classLoader

        ...
        componentRegistry.registerComponent(...)
        modelDelegateRegistry.register(...)
        ...

        Thread.currentThread().contextClassLoader = originalClassLoader
    }

Edit: The current hack. This has the benefit of also resolving my own reusable schemas.

class DelegatedClassLoader(parent: ClassLoader, private vararg val delegates: ClassLoader) :
    ClassLoader(parent) {

    override fun findClass(name: String): Class<*> {
        for (delegate in delegates) {
            try {
                return delegate.loadClass(name)
            } catch (_: ClassNotFoundException) {}
        }
        throw ClassNotFoundException(name)
    }

    override fun findResource(name: String): URL? =
        delegates.firstNotNullOfOrNull { it.getResource(name) }

    override fun findResources(name: String): Enumeration<URL> =
        Collections.enumeration(delegates.flatMap { it.getResources(name).toList() })
}

fun withContextClassLoaders(vararg delegates: ClassLoader, block: () -> Unit) {
    val originalContextClassLoader = Thread.currentThread().contextClassLoader
    Thread.currentThread().contextClassLoader =
        DelegatedClassLoader(originalContextClassLoader, *delegates)

    block()

    Thread.currentThread().contextClassLoader = originalContextClassLoader
}
    // GatewayHook
    override fun startup(activationState: LicenseState) {
        ...

        logger.debug("Registering components...")
        withContextClassLoaders(
            this.javaClass.classLoader,
            context.perspectiveContext.javaClass.classLoader,
        ) {
            componentRegistry.registerComponent(...)
            modelDelegateRegistry.register(...)
        }

        ...
    }

This fix is now included in 0.7.2.

2 Likes

Hey @bmusson, annoying js question again :grimacing:

Toastify: how would I be able to put words on new lines in the content? Usually I have to set the css white-space to pre*, but I have no idea where to start with this one..

Option 1: You can add classes to the toast.

system.perspective.runJavaScriptAsync('''() => {
	periscope.toast('My\\nContent\\nOn\\nDifferent\\nLines', {
		className: 'psc-MultiLineToast'
	})
}''')

Option 2: You can use inline styles.

system.perspective.runJavaScriptAsync('''() => {
	periscope.toast('My\\nContent\\nOn\\nDifferent\\nLines', {
		style: {
			whiteSpace: 'pre',
			pointerEvents: 'all'
		}
	})
}''')

Edit: If using inline styles, you have to add pointerEvents: 'all' for any interaction to work. This is because I'm using inline styles on the ToastContainer for the positioning and disabling interaction. This could be improved in a future version.

1 Like

Question regarding Portal:

Patch Changes

  • 3ea4d36: (FlexRepeaterPlus) Add instance key as an implicit parameter to the instance view.
  • 1bd2a1a: (FlexRepeaterPlus) Improve ViewModel caching.
    • Move ViewModel caching from the instance level to the component level, allowing the ViewModel reference to be retained for the lifetime of the component.
    • Previously, a ViewModel instance was only cached for the lifetime of its associated InstancePropsHandler, and not much care was taken to remember InstancePropsHandlers.This resolves a bug that would occur when simultaneously (in a single update to props.instances):
    1. Moving existing instances.
    2. Adding new instances.
    3. Changing the final size of the instances array.
  • 3b53009: (Toasts) Move pointerEvents setting from inline styles to CSS.
    • This makes it easier for users to use the style property of the toast function.
    • Users no longer need to add pointerEvents: 'all' to every inline style definition.
  • Updated dependencies [3ea4d36]
  • Updated dependencies [3b53009]
    • @embr-modules/periscope-web@0.7.3
4 Likes

Ok, I'm trying to use FlexRepeaterPlus, and I cannot get the instances to populate correctly....
I have a view with 3 input parameters, and I'd like to use two of them as common, and one as variable on each instance.

I'm feeding this transform a list of the instances I want:
	a={}
	counter=0
	value.sort(reverse=True)
	for i in value:	
		instance={"viewParams":{"Station":i},
  			"viewStyle": {"classes": ""},
  			"viewPosition": {},
  			"key": counter
			}
		a[counter]=instance
		
		counter=counter+1
	
	return a

My array looks correct if I setup a custom property with the transform,

{
  "0": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 180
    },
    "key": 0
  },
  "1": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 172
    },
    "key": 1
  },
  "2": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 171
    },
    "key": 2
  },
  "3": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 170
    },
    "key": 3
  },
  "4": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 160
    },
    "key": 4
  },
  "5": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 150
    },
    "key": 5
  },
  "6": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 130
    },
    "key": 6
  },
  "7": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 110
    },
    "key": 7
  },
  "8": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 100
    },
    "key": 8
  },
  "9": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 93
    },
    "key": 9
  },
  "10": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 91
    },
    "key": 10
  },
  "11": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 90
    },
    "key": 11
  },
  "12": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 81
    },
    "key": 12
  },
  "13": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 80
    },
    "key": 13
  },
  "14": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 70
    },
    "key": 14
  },
  "15": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 60
    },
    "key": 15
  },
  "16": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 50
    },
    "key": 16
  },
  "17": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 41
    },
    "key": 17
  },
  "18": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 21
    },
    "key": 18
  },
  "19": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 20
    },
    "key": 19
  },
  "20": {
    "viewStyle": {
      "classes": ""
    },
    "viewPosition": {},
    "viewParams": {
      "Station": 10
    },
    "key": 20
  }
}

But I cannot get the actual instances list to change, regardless of using a binding to the array or using the script push method as a startup event. I'm currently using periscope 0.7.4 (b2025050202)
Thanks!

Could you provide a project export that demonstrates the problem?

I can’t really follow your example.

Found it!
the a={} needed to be a=
I had the wrong keys due to how it looks if you copy the JSON to notepad...

I’m glad you figured it out.

TIL that open/closed brackets will render as a square. []

Today I learned that if you have rights to edit a post, clicking that square will actually check the checkbox :laughing:

3 Likes

oooo!

Cross linking this post: