Skip to content

Instantly share code, notes, and snippets.

@pavi2410
Last active January 8, 2021 09:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pavi2410/18195e3e6096aa257aa0341524d0da9e to your computer and use it in GitHub Desktop.
Save pavi2410/18195e3e6096aa257aa0341524d0da9e to your computer and use it in GitHub Desktop.
[GSoC] Code for the App Inventor VCE SDK
let registry = {} // map of type to class
let mockInstances = {} // map of uuid to mockInstance
class MockComponentRegistry {
static register(type, mockClass) {
registry[type] = mockClass
console.log("<iframe>", "registering VCE", type, '<UUID = undefined>')
postMessage('registerMockComponent', [], type, '') // TODO needs discussion
}
}
window.addEventListener('message', event => {
let { action, args, type, uuid } = JSON.parse(event.data)
messageInterpreter(action, args, type, uuid)
}, false)
function messageInterpreter(action, args, type, uuid) {
console.log("<iframe>", "got message", action, args, type, uuid)
switch (action) {
case "instantiateComponent": {
let mockClass = registry[type]
mockInstances[uuid] = new mockClass(uuid)
break;
}
case "getName.callback": {
console.log('getName.cb', type, uuid, args)
break;
}
case "getPropertyValue.callback": {
console.log('getPropVal.cb', type, uuid, args)
break;
}
case 'onPropertyChange': {
mockInstances[uuid].onPropertyChange(args[0], args[1])
break;
}
}
}
function postMessage(action, args, type, uuid) {
let msg = JSON.stringify({
action,
args,
type,
uuid
})
window.top.postMessage(msg, '*')
}
function genUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
class MockVisibleExtension {
constructor(type, uuid) {
this.type = type
this.uuid = uuid
}
initComponent(element) {
this.$el = element
postMessage('initializeComponent', [element.outerHTML], this.type, this.uuid)
}
getName() {
postMessage('getName', [], this.type, this.uuid)
}
getPropertyValue(propName) {
postMessage('getPropertyValue', [propName], this.type, this.uuid)
}
changeProperty(propName, newValue) {
postMessage('changeProperty', [propName, newValue], this.type, this.uuid)
}
refresh(element) {
element = element || this.$el
postMessage('updateMockComponent', [element.outerHTML], this.type, this.uuid)
}
onPropertyChange() {
this.refresh(this.$el);
}
}
/////////////////////////////USER CODE////////////////////////////////////////
class MockSimpleLabel extends MockVisibleExtension {
static TYPE = "com.pavi2410.SimpleLabel"
constructor(uuid) {
super(MockSimpleLabel.TYPE, uuid)
this.label = document.createElement('span')
this.initComponent(this.label)
}
onCreateFromPalette() {
this.changeProperty("Text", this.getName() || "NA");
}
onPropertyChange(propertyName, newValue) {
switch (propertyName) {
case 'Text': {
this.label.innerText = newValue
break
}
}
super.onPropertyChange(propertyName, newValue)
}
}
MockComponentRegistry.register(MockSimpleLabel.TYPE, MockSimpleLabel)
///////////////////////////////////////////////////////////////////////////////
let registry = {} // map of type to class
let mockInstances = {} // map of uuid to mockInstance
class MockComponentRegistry {
static register(type, mockClass) {
registry[type] = mockClass
return postMessageAsync('registerMockComponent', [], type, '')
}
}
window.addEventListener('message', event => {
let { action, args, type, uuid } = event.data
function postResult(result) {
event.ports[0].postMessage({result})
}
function postError(error) {
event.ports[0].postMessage({error})
}
switch (action) {
case 'loadMockComponent': {
loadMockComponent(src)
postResult('done')
break
}
case 'instantiateComponent': {
let mockClass = registry[type]
mockInstances[uuid] = new mockClass(uuid)
postResult('done')
break
}
case 'onCreateFromPalette': {
if (!(uuid in mockInstances)) {
postError('err')
break
}
mockInstances[uuid].onCreateFromPalette()
postResult('done')
break
}
case 'onPropertyChange': {
mockInstances[uuid].onPropertyChange(args[0], args[1])
break
}
case 'getName.callback': {
// TODO
break
}
case 'getPropertyValue.callback': {
// TODO
break
}
}
}, false)
/**
* @ref https://advancedweb.hu/how-to-use-async-await-with-postmessage/
*/
function postMessageAsync(action, args, type, uuid) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = event => {
channel.port1.close()
let {result, error} = event.data
if (error) {
reject(error)
} else {
resolve(result)
}
}
window.top.postMessage({action, args, type, uuid}, [channel.port2])
})
}
function genUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
function loadMockComponent(src) {
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
document.body.appendChild(script);
}
class MockVisibleExtension {
constructor(type, uuid) {
this.type = type
this.uuid = uuid
}
initComponent(element) {
this.$el = element
return postMessageAsync('initializeComponent', [element.outerHTML], this.type, this.uuid)
}
getName() {
return postMessageAsync('getName', [], this.type, this.uuid)
}
getPropertyValue(propName) {
return postMessageAsync('getPropertyValue', [propName], this.type, this.uuid)
}
changeProperty(propName, newValue) {
return postMessageAsync('changeProperty', [propName, newValue], this.type, this.uuid)
}
refresh(element) {
element = element || this.$el
return postMessageAsync('updateMockComponent', [element.outerHTML], this.type, this.uuid)
}
onPropertyChange() {
this.refresh(this.$el)
}
}
/////////////////////////////USER CODE////////////////////////////////////////
class MockSimpleLabel extends MockVisibleExtension {
static TYPE = "com.pavi2410.SimpleLabel"
constructor(uuid) {
super(MockSimpleLabel.TYPE, uuid)
this.label = document.createElement('span')
this.initComponent(this.label).then(() => console.log('inited'))
}
onCreateFromPalette() {
this.getName().then(name => this.changeProperty("Text", name))
}
onPropertyChange(propertyName, newValue) {
switch (propertyName) {
case 'Text': {
this.label.innerText = newValue // mutate the local DOM
break
}
}
super.onPropertyChange(propertyName, newValue) // refreshes the DOM in the top window
}
}
MockComponentRegistry.register(MockSimpleLabel.TYPE, MockSimpleLabel).then(() => console.log('registered'))
///////////////////////////////////////////////////////////////////////////////
@pavi2410
Copy link
Author

pavi2410 commented Aug 18, 2020

Message protocol used for communication between iframes and GWT

{
  "action": "string",
  "args": [ "args to accompany the action" ],
  "type": "component type",
  "uuid": "uuid of each instance"
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment