[GSoC] Code for the App Inventor VCE SDK
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
/////////////////////////////////////////////////////////////////////////////// |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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')) | |
/////////////////////////////////////////////////////////////////////////////// |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Message protocol used for communication between iframes and GWT