diff --git a/experiments/modern/src/App.js b/experiments/modern/src/App.js index 7e87be3d..6ff49c53 100644 --- a/experiments/modern/src/App.js +++ b/experiments/modern/src/App.js @@ -7,8 +7,6 @@ const System = require("./runtime/System"); const runtime = require("./runtime"); const interpret = require("./maki-interpreter/interpreter"); -const SkinContext = React.createContext(null); - async function getSkin() { // const resp = await fetch(process.env.PUBLIC_URL + "/skins/CornerAmp_Redux.wal"); const resp = await fetch(process.env.PUBLIC_URL + "/skins/simple.wal"); @@ -43,6 +41,7 @@ function Container(props) { } function Layout({ + node, id, background, desktopalpha, @@ -54,13 +53,17 @@ function Layout({ droptarget, children, }) { - const data = React.useContext(SkinContext); if (background == null) { console.warn("Got a Layout without a background. Rendering null", id); return null; } - const image = data[background]; + const image = node.js_imageLookup(background); + if (image == null) { + console.warn("Unable to find image to render. Rendering null", background); + return null; + } + return ( <> { - getSkin().then(async ({ root, registry }) => { + getSkin().then(async root => { // Execute scripts await Utils.asyncTreeFlatMap(root, async node => { switch (node.xmlNode.name) { @@ -238,12 +239,17 @@ function App() { } case "script": { // TODO: stop ignoring standardframe - if (node.xmlNode.file.endsWith("standardframe.maki")) { + if (node.xmlNode.attributes.file.endsWith("standardframe.maki")) { break; } const scriptGroup = Utils.findParentNodeOfType(node, ["group", "WinampAbstractionLayer", "WasabiXML"]); const system = new System(scriptGroup); - await interpret({ runtime, data: node.xmlNode.script, system, log: false }); + await interpret({ + runtime, + data: node.js_annotations.script, + system, + log: false, + }); return node; } default: { @@ -252,19 +258,15 @@ function App() { } }); - setData({ root, registry }); + setData(root); }); }, []); if (data == null) { return

Loading...

; } - const { root, registry } = data; + const root = data; - return ( - - - - ); + return ; } export default App; diff --git a/experiments/modern/src/initialize.js b/experiments/modern/src/initialize.js index 6f81da2d..5130ab46 100644 --- a/experiments/modern/src/initialize.js +++ b/experiments/modern/src/initialize.js @@ -4,16 +4,15 @@ const WinampAbstractionLayer = require("./runtime/WinampAbstractionLayer"); const Layout = require('./runtime/Layout'); const Layer = require('./runtime/Layer'); const Container = require('./runtime/Container'); +const JsElements = require("./runtime/JsElements"); +const JsGammaSet = require("./runtime/JsGammaSet"); +const JsGroupDef = require("./runtime/JsGroupDef"); const Group = require("./runtime/Group"); const Button = require("./runtime/Button"); const ToggleButton = require("./runtime/ToggleButton"); const Text = require("./runtime/Text"); const Status = require("./runtime/Status"); -function splitValues(str) { - return str.split(",").map(parseFloat); -} - async function loadImage(imgUrl) { return await new Promise(resolve => { const img = new Image(); @@ -123,12 +122,7 @@ const schema = { const noop = (node, parent) => new MakiObject(node, parent); const parsers = { - groupdef: (node, parent, registry) => { - const attributeId = node.attributes.id; - registry.groupdefs[attributeId] = node; - - return new MakiObject(node, parent); - }, + groupdef: (node, parent) => new JsGroupDef(node, parent), skininfo: noop, version: noop, name: noop, @@ -139,39 +133,17 @@ const parsers = { screenshot: noop, container: (node, parent) => new Container(node, parent), scripts: noop, - gammaset: (node, parent, registry) => { - const gammaId = node.attributes.id; - if (!registry.gammasets.hasOwnProperty(gammaId)) { - registry.gammasets[gammaId] = {}; - } - - return new MakiObject(node, parent); - }, + gammaset: (node, parent) => new JsGammaSet(node, parent), color: noop, layer: (node, parent) => new Layer(node, parent), layoutstatus: noop, hideobject: noop, button: (node, parent) => new Button(node, parent), - group: (node, parent, registry) => { - if (!node.children || node.children.length === 0) { - const groupdef = registry.groupdefs[node.attributes.id]; - if (groupdef) { - const newNode = { - ...node, - ...groupdef, - attributes: { ...node.attributes, ...groupdef.attributes }, - name: "group", - }; - return new Group(newNode, parent); - } - } - - return new Group(node, parent); - }, + group: (node, parent) => new Group(node, parent), layout: (node, parent) => new Layout(node, parent), sendparams: noop, - elements: noop, - bitmap: async (node, parent, registry, zip) => { + elements: (node, parent) => new JsElements(node, parent), + bitmap: async (node, parent, zip) => { let { file, gammagroup, h, id, w, x, y } = node.attributes; // TODO: Escape file for regex const img = Utils.getCaseInsensitveFile(zip, file); @@ -187,20 +159,21 @@ const parsers = { x = x !== undefined ? x : 0; y = y !== undefined ? y : 0; } - registry.images[id.toLowerCase()] = { file, gammagroup, h, w, x, y, imgUrl }; - return new MakiObject(node, parent); + return new MakiObject(node, parent, { + id, + file, + gammagroup, + h, + w, + x, + y, + imgUrl, + }); }, eqvis: noop, slider: noop, - gammagroup: (node, parent, registry) => { - const gammaId = parent.xmlNode.attributes.id; - const attributeId = node.attributes.id; - const attributeValues = splitValues(node.attributes.value); - registry.gammasets[gammaId][attributeId] = attributeValues; - - return new MakiObject(node, parent); - }, + gammagroup: noop, truetypefont: noop, component: noop, text: (node, parent) => new Text(node, parent), @@ -226,17 +199,13 @@ const parsers = { menu: noop, albumart: noop, playlistplus: noop, - async script(node, parent, registry, zip) { - const { id, file, param } = node.attributes; - const script = await Utils.readUint8array(zip, file); - registry.scripts.push({ parent, id, param, script }); - - const newNode = { ...node, script, param, file }; - return new MakiObject(newNode, parent); + async script(node, parent, zip) { + const script = await Utils.readUint8array(zip, node.attributes.file); + return new MakiObject(node, parent, { script }); }, }; -async function parseChildren(node, registry, zip) { +async function parseChildren(node, zip) { if (node.xmlNode.type === "comment") { return; } @@ -274,9 +243,12 @@ async function parseChildren(node, registry, zip) { throw new Error(`Missing parser for ${childName}`); return; } - const parsedChild = await childParser(child, node, registry, zip); - if (parsedChild.xmlNode.children != null && parsedChild.xmlNode.children.length > 0) { - await parseChildren(parsedChild, registry, zip); + const parsedChild = await childParser(child, node, zip); + if ( + parsedChild.xmlNode.children != null && + parsedChild.xmlNode.children.length > 0 + ) { + await parseChildren(parsedChild, zip); } return parsedChild; }) @@ -287,11 +259,43 @@ async function parseChildren(node, registry, zip) { node.js_addChildren(filteredChildren); } +async function applyGroupDefs(root) { + await Utils.asyncTreeFlatMap(root, async node => { + switch (node.xmlNode.name) { + case "group": { + if (!node.children || node.children.length === 0) { + const groupdef = node.js_groupdefLookup(node.xmlNode.attributes.id); + if (!groupdef) { + console.warn( + "Unable to find groupdef. Rendering null", + node.xmlNode.attributes.id + ); + return {}; + } + node.children = groupdef.children; + // Do we need to copy the items instead of just changing the parent? + node.children.forEach(item => { + item.parent = node; + }); + node.xmlNode.attributes = { + ...node.xmlNode.attributes, + ...groupdef.xmlNode.attributes, + }; + } + return {}; + } + default: { + return node; + } + } + }); +} + async function initialize(zip, skinXml) { - const registry = { scripts: [], gammasets: {}, images: {}, groupdefs: {} }; const root = new WinampAbstractionLayer(skinXml.children[0], null); - await parseChildren(root, registry, zip); - return { root, registry }; + await parseChildren(root, zip); + await applyGroupDefs(root); + return root; } export default initialize; diff --git a/experiments/modern/src/runtime/GuiObject.js b/experiments/modern/src/runtime/GuiObject.js index 4784a2b2..f66fb228 100644 --- a/experiments/modern/src/runtime/GuiObject.js +++ b/experiments/modern/src/runtime/GuiObject.js @@ -24,6 +24,7 @@ class GuiObject extends MakiObject { return this.findobject(id); } init(newRoot) { + this.parent = newRoot; newRoot.js_addChild(this); return this; } diff --git a/experiments/modern/src/runtime/JsElements.js b/experiments/modern/src/runtime/JsElements.js new file mode 100644 index 00000000..d771eaa0 --- /dev/null +++ b/experiments/modern/src/runtime/JsElements.js @@ -0,0 +1,15 @@ +const MakiObject = require("./MakiObject"); + +class JsElements extends MakiObject { + /** + * getclassname() + * + * Returns the class name for the object. + * @ret The class name. + */ + static getclassname() { + return "Elements"; + } +} + +module.exports = JsElements; diff --git a/experiments/modern/src/runtime/JsGammaSet.js b/experiments/modern/src/runtime/JsGammaSet.js new file mode 100644 index 00000000..411c8302 --- /dev/null +++ b/experiments/modern/src/runtime/JsGammaSet.js @@ -0,0 +1,15 @@ +const MakiObject = require("./MakiObject"); + +class JsGammaSet extends MakiObject { + /** + * getclassname() + * + * Returns the class name for the object. + * @ret The class name. + */ + static getclassname() { + return "GammaSet"; + } +} + +module.exports = JsGammaSet; diff --git a/experiments/modern/src/runtime/JsGroupDef.js b/experiments/modern/src/runtime/JsGroupDef.js new file mode 100644 index 00000000..397483c9 --- /dev/null +++ b/experiments/modern/src/runtime/JsGroupDef.js @@ -0,0 +1,15 @@ +const MakiObject = require("./MakiObject"); + +class JsGroupDef extends MakiObject { + /** + * getclassname() + * + * Returns the class name for the object. + * @ret The class name. + */ + static getclassname() { + return "GroupDef"; + } +} + +module.exports = JsGroupDef; diff --git a/experiments/modern/src/runtime/MakiObject.js b/experiments/modern/src/runtime/MakiObject.js index 2734fe78..66eb4b75 100644 --- a/experiments/modern/src/runtime/MakiObject.js +++ b/experiments/modern/src/runtime/MakiObject.js @@ -1,9 +1,11 @@ const Emitter = require("../Emitter"); +const { findElementById, findGroupDefById } = require("../utils"); class MakiObject { - constructor(node, parent) { + constructor(node, parent, annotations = {}) { this.xmlNode = node; this.parent = parent; + this.js_annotations = annotations; this.children = []; this._emitter = new Emitter(); @@ -42,6 +44,24 @@ class MakiObject { this._emitter.dispose(); } + js_imageLookup(id) { + const element = findElementById(this, id); + if (element) { + return element.js_annotations; + } + + return null; + } + + js_groupdefLookup(id) { + const groupdef = findGroupDefById(this, id); + if (groupdef) { + return groupdef; + } + + return null; + } + /** * getclassname() * diff --git a/experiments/modern/src/utils.js b/experiments/modern/src/utils.js index 061db9f3..668f12a3 100644 --- a/experiments/modern/src/utils.js +++ b/experiments/modern/src/utils.js @@ -1,4 +1,6 @@ import { xml2js } from "xml-js"; +const JsElements = require("./runtime/JsElements"); +const JsGroupDef = require("./runtime/JsGroupDef"); export function getCaseInsensitveFile(zip, filename) { // TODO: Escape `file` for rejex characters @@ -104,7 +106,7 @@ export function unimplementedWarning(name) { // Operations on trees export function findParentNodeOfType(node, type) { let n = node; - while(n.parent !== null) { + while (n.parent) { n = n.parent; if ((!Array.isArray(type) && n.xmlNode.name === type) || (Array.isArray(type) && type.includes(n.xmlNode.name))) { @@ -129,7 +131,7 @@ export function findDescendantByTypeAndId(node, type, id) { } } - for(let i = 0; i < node.children.length; i++) { + for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; const descendant = findDescendantByTypeAndId(child, type, id); if (descendant) { @@ -139,3 +141,52 @@ export function findDescendantByTypeAndId(node, type, id) { return null; } + +function findDirectDescendantById(node, id) { + return node.children.find(item => item.xmlNode.attributes.id === id); +} + +// Search up the tree for a node that is in `node`'s lexical scope and pred returns node +function findInLexicalScope(node, pred) { + let currentNode = node; + while (currentNode.parent) { + let parent = currentNode.parent; + const children = parent.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child === currentNode) { + break; + } + + const item = pred(child); + if (item) { + return item; + } + } + currentNode = parent; + } + + return null; +} + +// Search up the tree for nodes that are in node's lexical scope. +// return the first child of an that matches id +export function findElementById(node, id) { + return findInLexicalScope(node, child => { + if (child instanceof JsElements) { + const element = findDirectDescendantById(child, id); + if (element) { + return element; + } + } + }); +} + +// Search up the tree for a node that is in node's lexical scope and matches id. +export function findGroupDefById(node, id) { + return findInLexicalScope(node, child => { + if (child instanceof JsGroupDef && child.xmlNode.attributes.id === id) { + return child; + } + }); +}