mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-24 02:27:37 +00:00
Initialize maki object tree (#823)
* make initialize tree out of maki objects * pass global flag to variables, only need hooks for global variables * implement new opcode * start using non generic makiobject * run scripts * fix running scripts * skip standardframe for now * switch to simple example for now * make WAL a group * move xml/init functions to be in GuiObject * handle creating backing XML nodes for objects created (new) in scripts * simpler null check * add js_ prefixes * only bind event handlers for hooks if they are present * uppercase for Classes * make null checking explicit * need to check undefined, not null * implement init for adding nodes to state tree * dont render groupdefs, dont execute scripts in groupdefs * implement getContainer * move find descendants to utils
This commit is contained in:
parent
1efded00d3
commit
769c7b9577
15 changed files with 295 additions and 70 deletions
|
|
@ -3,6 +3,9 @@ import JSZip from "jszip";
|
|||
import "./App.css";
|
||||
import * as Utils from "./utils";
|
||||
import initialize from "./initialize";
|
||||
const System = require("./runtime/System");
|
||||
const runtime = require("./runtime");
|
||||
const interpret = require("./maki-interpreter/interpreter");
|
||||
|
||||
// const runtime = require("./maki-interpreter/runtime");
|
||||
// const System = require("./maki-interpreter/runtime/System");
|
||||
|
|
@ -34,7 +37,8 @@ const IGNORE_IDS = new Set([
|
|||
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/CornerAmp_Redux.wal");
|
||||
const resp = await fetch(process.env.PUBLIC_URL + "/skins/simple.wal");
|
||||
const blob = await resp.blob();
|
||||
const zip = await JSZip.loadAsync(blob);
|
||||
const skinXml = await Utils.inlineIncludes(
|
||||
|
|
@ -42,11 +46,7 @@ async function getSkin() {
|
|||
zip
|
||||
);
|
||||
|
||||
const { nodes, registry } = await initialize(zip, skinXml);
|
||||
|
||||
// Gross hack returing a tuple here. We're just doing some crazy stuff to get
|
||||
// some data returned in the laziest way possible
|
||||
return [skinXml, nodes, registry];
|
||||
return await initialize(zip, skinXml);
|
||||
}
|
||||
|
||||
function Layout({
|
||||
|
|
@ -118,7 +118,7 @@ function Layer({ id, image, children, x, y }) {
|
|||
);
|
||||
}
|
||||
|
||||
function Button({ id, image, action, x, y, downImage, tooltip, children }) {
|
||||
function Button({ id, image, action, x, y, downImage, tooltip, node, children }) {
|
||||
const data = React.useContext(SkinContext);
|
||||
const [down, setDown] = React.useState(false);
|
||||
const imgId = down && downImage ? downImage : image;
|
||||
|
|
@ -133,6 +133,16 @@ function Button({ id, image, action, x, y, downImage, tooltip, children }) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const hooks = node.js_getActiveHooks();
|
||||
const eventHandlers = {};
|
||||
if (hooks.includes("onLeftClick")) {
|
||||
eventHandlers["onClick"] = e => {
|
||||
if (hooks.includes("onLeftClick")) {
|
||||
node.js_trigger("onLeftClick");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-node-type="button"
|
||||
|
|
@ -144,6 +154,7 @@ function Button({ id, image, action, x, y, downImage, tooltip, children }) {
|
|||
setDown(false);
|
||||
});
|
||||
}}
|
||||
{...eventHandlers}
|
||||
title={tooltip}
|
||||
style={{
|
||||
position: "absolute",
|
||||
|
|
@ -192,19 +203,19 @@ const NODE_NAME_TO_COMPONENT = {
|
|||
|
||||
// Given a skin XML node, pick which component to use, and render it.
|
||||
function XmlNode({ node }) {
|
||||
const attributes = node.attributes;
|
||||
const name = node.name;
|
||||
const attributes = node.xmlNode.attributes;
|
||||
const name = node.xmlNode.name;
|
||||
if (attributes && IGNORE_IDS.has(attributes.id)) {
|
||||
return null;
|
||||
}
|
||||
if (name == null) {
|
||||
// This is likely a comment
|
||||
if (name == null || name === "groupdef") {
|
||||
// name is null is likely a comment
|
||||
return null;
|
||||
}
|
||||
const Component = NODE_NAME_TO_COMPONENT[name];
|
||||
const childNodes = node.children || [];
|
||||
const children = childNodes.map((childNode, i) => (
|
||||
<XmlNode key={i} parent={node} node={childNode} />
|
||||
<XmlNode key={i} node={childNode} />
|
||||
));
|
||||
if (Component == null) {
|
||||
console.warn("Unknown node type", name);
|
||||
|
|
@ -213,23 +224,46 @@ function XmlNode({ node }) {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
return <Component {...attributes}>{children}</Component>;
|
||||
return <Component node={node} {...attributes}>{children}</Component>;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [data, setData] = React.useState(null);
|
||||
React.useEffect(() => {
|
||||
getSkin().then(setData);
|
||||
getSkin().then(async ({ root, registry }) => {
|
||||
// Execute scripts
|
||||
await Utils.asyncTreeFlatMap(root, async node => {
|
||||
switch (node.xmlNode.name) {
|
||||
case "groupdef": {
|
||||
// removes groupdefs from consideration (only run scripts when actually referenced by group)
|
||||
return {};
|
||||
}
|
||||
case "script": {
|
||||
// TODO: stop ignoring standardframe
|
||||
if (node.xmlNode.file.endsWith("standardframe.maki")) {
|
||||
break;
|
||||
}
|
||||
const scriptGroup = Utils.findParentNodeOfType(node, ["group", "WinampAbstractionLayer"]);
|
||||
const system = new System(scriptGroup);
|
||||
await interpret({ runtime, data: node.xmlNode.script, system, log: false });
|
||||
return node;
|
||||
}
|
||||
default: {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setData({ root, registry });
|
||||
});
|
||||
}, []);
|
||||
if (data == null) {
|
||||
return <h1>Loading...</h1>;
|
||||
}
|
||||
const [skinXml, nodes, registry] = data;
|
||||
const { root, registry } = data;
|
||||
return (
|
||||
<SkinContext.Provider value={registry.images}>
|
||||
<XmlNode
|
||||
node={nodes}
|
||||
/>
|
||||
<XmlNode node={root} />
|
||||
</SkinContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import * as Utils from "./utils";
|
||||
const MakiObject = require("./runtime/MakiObject");
|
||||
const WinampAbstractionLayer = require("./runtime/WinampAbstractionLayer");
|
||||
const Layout = require('./runtime/Layout');
|
||||
const Container = require('./runtime/Container');
|
||||
const Group = require("./runtime/Group");
|
||||
const Button = require("./runtime/Button");
|
||||
|
||||
function splitValues(str) {
|
||||
return str.split(",").map(parseFloat);
|
||||
|
|
@ -14,11 +20,6 @@ async function loadImage(imgUrl) {
|
|||
});
|
||||
}
|
||||
|
||||
let idCount = 0;
|
||||
function getId() {
|
||||
return '_' + idCount++;
|
||||
}
|
||||
|
||||
const schema = {
|
||||
groupdef: [
|
||||
"scripts",
|
||||
|
|
@ -115,14 +116,14 @@ const schema = {
|
|||
accelerators: ["accelerator"],
|
||||
};
|
||||
|
||||
const noop = (node) => node;
|
||||
const noop = (node, parent) => new MakiObject(node, parent);
|
||||
|
||||
const parsers = {
|
||||
groupdef: (node, parent, registry) => {
|
||||
const attributeId = node.attributes.id;
|
||||
registry.groupdefs[attributeId] = node;
|
||||
|
||||
return node;
|
||||
return new MakiObject(node, parent);
|
||||
},
|
||||
skininfo: noop,
|
||||
version: noop,
|
||||
|
|
@ -132,7 +133,7 @@ const parsers = {
|
|||
email: noop,
|
||||
homepage: noop,
|
||||
screenshot: noop,
|
||||
container: noop,
|
||||
container: (node, parent) => new Container(node, parent),
|
||||
scripts: noop,
|
||||
gammaset: (node, parent, registry) => {
|
||||
const gammaId = node.attributes.id;
|
||||
|
|
@ -140,29 +141,30 @@ const parsers = {
|
|||
registry.gammasets[gammaId] = {};
|
||||
}
|
||||
|
||||
return node;
|
||||
return new MakiObject(node, parent);
|
||||
},
|
||||
color: noop,
|
||||
layer: noop,
|
||||
layoutstatus: noop,
|
||||
hideobject: noop,
|
||||
button: 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) {
|
||||
return {
|
||||
const newNode = {
|
||||
...node,
|
||||
...groupdef,
|
||||
attributes: { ...node.attributes, ...groupdef.attributes },
|
||||
name: "group",
|
||||
};
|
||||
return new Group(newNode, parent);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
return new Group(node, parent);
|
||||
},
|
||||
layout: noop,
|
||||
layout: (node, parent) => new Layout(node, parent),
|
||||
sendparams: noop,
|
||||
elements: noop,
|
||||
bitmap: async (node, parent, registry, zip) => {
|
||||
|
|
@ -170,7 +172,7 @@ const parsers = {
|
|||
// TODO: Escape file for regex
|
||||
const img = Utils.getCaseInsensitveFile(zip, file);
|
||||
if (img === undefined) {
|
||||
return node;
|
||||
return new MakiObject(node, parent);
|
||||
}
|
||||
const imgBlob = await img.async("blob");
|
||||
const imgUrl = URL.createObjectURL(imgBlob);
|
||||
|
|
@ -183,23 +185,22 @@ const parsers = {
|
|||
}
|
||||
registry.images[id.toLowerCase()] = { file, gammagroup, h, w, x, y, imgUrl };
|
||||
|
||||
return node;
|
||||
return new MakiObject(node, parent);
|
||||
},
|
||||
eqvis: noop,
|
||||
slider: noop,
|
||||
gammagroup: (node, parent, registry) => {
|
||||
const gammaId = parent.attributes.id;
|
||||
const gammaId = parent.xmlNode.attributes.id;
|
||||
const attributeId = node.attributes.id;
|
||||
const attributeValues = splitValues(node.attributes.value);
|
||||
registry.gammasets[gammaId][attributeId] = attributeValues;
|
||||
|
||||
return node;
|
||||
return new MakiObject(node, parent);
|
||||
},
|
||||
truetypefont: noop,
|
||||
component: noop,
|
||||
text: noop,
|
||||
layer: noop,
|
||||
button: noop,
|
||||
togglebutton: noop,
|
||||
status: noop,
|
||||
slider: noop,
|
||||
|
|
@ -228,28 +229,29 @@ const parsers = {
|
|||
const script = await Utils.readUint8array(zip, file);
|
||||
registry.scripts.push({ parent, id, param, script });
|
||||
|
||||
return { ...node, script, param };
|
||||
const newNode = { ...node, script, param, file };
|
||||
return new MakiObject(newNode, parent);
|
||||
},
|
||||
};
|
||||
|
||||
async function parseChildren(node, registry, zip) {
|
||||
if (node.type === "comment") {
|
||||
if (node.xmlNode.type === "comment") {
|
||||
return;
|
||||
}
|
||||
if (node.name == null) {
|
||||
console.error(node);
|
||||
if (node.xmlNode.name == null) {
|
||||
console.error(node.xmlNode);
|
||||
throw new Error("Unknown node");
|
||||
}
|
||||
|
||||
const validChildren = new Set(schema[node.name.toLowerCase()]);
|
||||
const validChildren = new Set(schema[node.xmlNode.name.toLowerCase()]);
|
||||
const resolvedChildren = await Promise.all(
|
||||
node.children.map(async child => {
|
||||
node.xmlNode.children.map(async child => {
|
||||
if (child.type === "comment") {
|
||||
return;
|
||||
}
|
||||
if (child.type === "text") {
|
||||
// TODO: Handle text
|
||||
return { ...child, id: getId() };
|
||||
return new MakiObject({ ...child }, node);
|
||||
}
|
||||
if (child.name == null) {
|
||||
console.error(child);
|
||||
|
|
@ -257,12 +259,12 @@ async function parseChildren(node, registry, zip) {
|
|||
}
|
||||
const childName = child.name.toLowerCase();
|
||||
if (childName == null) {
|
||||
console.error(node);
|
||||
console.error(node.xmlNode);
|
||||
throw new Error("Unknown node");
|
||||
}
|
||||
|
||||
if (!validChildren.has(childName)) {
|
||||
throw new Error(`Invalid child of a ${node.name}: ${childName}`);
|
||||
throw new Error(`Invalid child of a ${node.xmlNode.name}: ${childName}`);
|
||||
}
|
||||
|
||||
const childParser = parsers[childName];
|
||||
|
|
@ -271,28 +273,23 @@ async function parseChildren(node, registry, zip) {
|
|||
return;
|
||||
}
|
||||
const parsedChild = await childParser(child, node, registry, zip);
|
||||
const returnNode = { ...parsedChild, id: getId() };
|
||||
if (parsedChild.children != null) {
|
||||
const parsedChildren = await parseChildren(parsedChild, registry, zip);
|
||||
returnNode.children = parsedChildren.children;
|
||||
if (parsedChild.xmlNode.children != null && parsedChild.xmlNode.children.length > 0) {
|
||||
await parseChildren(parsedChild, registry, zip);
|
||||
}
|
||||
return returnNode;
|
||||
return parsedChild;
|
||||
})
|
||||
);
|
||||
// remove comments other trimmed nodes
|
||||
const filteredChildren = resolvedChildren.filter(item => item !== undefined);
|
||||
|
||||
return {
|
||||
...node,
|
||||
children: filteredChildren
|
||||
};
|
||||
node.js_addChildren(filteredChildren);
|
||||
}
|
||||
|
||||
async function initialize(zip, skinXml) {
|
||||
const registry = { scripts: [], gammasets: {}, images: {}, groupdefs: {} };
|
||||
const nodes = await parseChildren(skinXml.children[0], registry, zip);
|
||||
nodes.id = getId();
|
||||
return { nodes, registry };
|
||||
const root = new WinampAbstractionLayer(skinXml.children[0], null);
|
||||
await parseChildren(root, registry, zip);
|
||||
return { root, registry };
|
||||
}
|
||||
|
||||
export default initialize;
|
||||
|
|
|
|||
|
|
@ -160,13 +160,13 @@ function readVariables({ makiFile, classes }) {
|
|||
if (klass == null) {
|
||||
throw new Error("Invalid type");
|
||||
}
|
||||
variables.push(new Variable({ type: klass, typeName: "OBJECT" }));
|
||||
variables.push(new Variable({ type: klass, typeName: "OBJECT", global: !!global }));
|
||||
} else if (subClass) {
|
||||
const variable = variables[typeOffset];
|
||||
if (variable == null) {
|
||||
throw new Error("Invalid type");
|
||||
}
|
||||
variables.push(new Variable({ type: variable, typeName: "SUBCLASS" }));
|
||||
variables.push(new Variable({ type: variable, typeName: "SUBCLASS", global: !!global }));
|
||||
} else {
|
||||
const typeName = PRIMITIVE_TYPES[typeOffset];
|
||||
if (typeName == null) {
|
||||
|
|
@ -195,7 +195,7 @@ function readVariables({ makiFile, classes }) {
|
|||
default:
|
||||
throw new Error("Invalid primitive type");
|
||||
}
|
||||
const variable = new Variable({ type: typeName, typeName });
|
||||
const variable = new Variable({ type: typeName, typeName, global: !!global });
|
||||
variable.setValue(value);
|
||||
variables.push(variable);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
const Emitter = require("../Emitter");
|
||||
|
||||
class Variable {
|
||||
constructor({ type, typeName }) {
|
||||
constructor({ type, typeName, global }) {
|
||||
this.type = type;
|
||||
this.typeName = typeName;
|
||||
this.global = global;
|
||||
this._emitter = new Emitter();
|
||||
this._unsubscribeFromValue = null;
|
||||
}
|
||||
|
|
@ -16,10 +17,11 @@ class Variable {
|
|||
if (this._unsubscribeFromValue != null) {
|
||||
this._unsubscribeFromValue();
|
||||
}
|
||||
if (this.typeName === "OBJECT") {
|
||||
if (this.global && this.typeName === "OBJECT" && value !== null) {
|
||||
this._unsubscribeFromValue = value.js_listenToAll((eventName, args) => {
|
||||
this._emitter.trigger(eventName, args);
|
||||
});
|
||||
value.js_updateHooks(this, Object.keys(this._emitter._hooks));
|
||||
}
|
||||
this._value = value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,6 +287,14 @@ function* interpret(start, program, stack = []) {
|
|||
twoArgOperator((b, a) => b >> a);
|
||||
break;
|
||||
}
|
||||
// new
|
||||
case 96: {
|
||||
const classesOffset = command.arg;
|
||||
const Klass = classes[classesOffset];
|
||||
const klassInst = new Klass();
|
||||
stack.push(klassInst);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unhandled opcode ${command.opcode}`);
|
||||
}
|
||||
|
|
|
|||
15
experiments/modern/src/runtime/Button.js
Normal file
15
experiments/modern/src/runtime/Button.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const GuiObject = require("./GuiObject");
|
||||
|
||||
class Button extends GuiObject {
|
||||
/**
|
||||
* getClassName()
|
||||
*
|
||||
* Returns the class name for the object.
|
||||
* @ret The class name.
|
||||
*/
|
||||
static getClassName() {
|
||||
return "Button";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Button;
|
||||
19
experiments/modern/src/runtime/Container.js
Normal file
19
experiments/modern/src/runtime/Container.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
const GuiObject = require("./GuiObject");
|
||||
const { findDescendantByTypeAndId } = require("../utils");
|
||||
|
||||
class Container extends GuiObject {
|
||||
/**
|
||||
* getClassName()
|
||||
*
|
||||
* Returns the class name for the object.
|
||||
* @ret The class name.
|
||||
*/
|
||||
static getClassName() {
|
||||
return "Container";
|
||||
}
|
||||
getLayout(id) {
|
||||
return findDescendantByTypeAndId(this, "layout", id);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Container;
|
||||
|
|
@ -10,11 +10,9 @@ class Group extends GuiObject {
|
|||
static getClassName() {
|
||||
return "Group";
|
||||
}
|
||||
findObject(id) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
setXmlParam(id, value) {
|
||||
throw new Error("Not implemented");
|
||||
getObject(id) {
|
||||
// Not sure this is correct, but it is my understanding this is just an alias
|
||||
return this.findObject(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const MakiObject = require("./MakiObject");
|
||||
const { findDescendantByTypeAndId } = require("../utils");
|
||||
|
||||
class GuiObject extends MakiObject {
|
||||
/**
|
||||
|
|
@ -10,6 +11,32 @@ class GuiObject extends MakiObject {
|
|||
static getClassName() {
|
||||
return "GuiObject";
|
||||
}
|
||||
findObject(id) {
|
||||
return findDescendantByTypeAndId(this, null, id);
|
||||
}
|
||||
init(newRoot) {
|
||||
newRoot.js_addChild(this);
|
||||
return this;
|
||||
}
|
||||
setXmlParam(param, value) {
|
||||
this.xmlNode.attributes[param] = value;
|
||||
return value;
|
||||
}
|
||||
// need to force all function names to lowercase in the interpreter
|
||||
// bad hack for now
|
||||
setXMLParam(param, value) {
|
||||
return this.setXmlParam(param, value);
|
||||
}
|
||||
getXmlParam(param) {
|
||||
const attributes = this.xmlNode.attributes;
|
||||
if (attributes !== undefined && attributes.hasOwnProperty(param)) {
|
||||
return attributes[param];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getParent() {
|
||||
return this.parent;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuiObject;
|
||||
|
|
|
|||
19
experiments/modern/src/runtime/Layout.js
Normal file
19
experiments/modern/src/runtime/Layout.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
const GuiObject = require("./GuiObject");
|
||||
const { findParentNodeOfType } = require("../utils");
|
||||
|
||||
class Layout extends GuiObject {
|
||||
/**
|
||||
* getClassName()
|
||||
*
|
||||
* Returns the class name for the object.
|
||||
* @ret The class name.
|
||||
*/
|
||||
static getClassName() {
|
||||
return "Layout";
|
||||
}
|
||||
getContainer() {
|
||||
return findParentNodeOfType(this, ["container"]);;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Layout;
|
||||
|
|
@ -1,8 +1,34 @@
|
|||
const Emitter = require("../Emitter");
|
||||
|
||||
class MakiObject {
|
||||
constructor() {
|
||||
constructor(node, parent) {
|
||||
this.xmlNode = node;
|
||||
this.parent = parent;
|
||||
this.children = [];
|
||||
this.hooks = {};
|
||||
this._emitter = new Emitter();
|
||||
|
||||
if (!this.xmlNode) {
|
||||
// When dynamically creating a new object with `new` we need to add an underlying "XML"
|
||||
// node that we can edit
|
||||
this.xmlNode = {
|
||||
children: [],
|
||||
attributes: {
|
||||
id: null,
|
||||
},
|
||||
// ugly but works for now
|
||||
name: this.constructor.name.toLowerCase(),
|
||||
type: 'element'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
js_addChild(child) {
|
||||
this.children.push(child);
|
||||
}
|
||||
|
||||
js_addChildren(children) {
|
||||
this.children = this.children.concat(children);
|
||||
}
|
||||
|
||||
js_trigger(eventName, ...args) {
|
||||
|
|
@ -17,6 +43,16 @@ class MakiObject {
|
|||
this._emitter.dispose();
|
||||
}
|
||||
|
||||
// updating hooks like this is probably totally wrong, but just hacking for now
|
||||
js_updateHooks (node, hooks) {
|
||||
this.hooks[node] = hooks;
|
||||
}
|
||||
|
||||
js_getActiveHooks () {
|
||||
const hookArrs = Object.values(this.hooks);
|
||||
return hookArrs.reduce((acc, val) => acc.concat(val), []);
|
||||
}
|
||||
|
||||
/**
|
||||
* getClassName()
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,7 +1,18 @@
|
|||
const Group = require("./Group");
|
||||
const MakiObject = require("./MakiObject");
|
||||
const { findDescendantByTypeAndId } = require("../utils");
|
||||
|
||||
class System extends MakiObject {
|
||||
constructor(scriptGroup = new Group()) {
|
||||
super(null, null);
|
||||
|
||||
this.scriptGroup = scriptGroup;
|
||||
this.root = scriptGroup;
|
||||
while(this.root.parent) {
|
||||
this.root = this.root.parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getClassName()
|
||||
*
|
||||
|
|
@ -17,7 +28,10 @@ class System extends MakiObject {
|
|||
}
|
||||
|
||||
getScriptGroup() {
|
||||
return new Group();
|
||||
return this.scriptGroup;
|
||||
}
|
||||
getContainer(id) {
|
||||
return findDescendantByTypeAndId(this.root, "container", id);
|
||||
}
|
||||
getRuntimeVersion() {
|
||||
return "5.666";
|
||||
|
|
|
|||
16
experiments/modern/src/runtime/WinampAbstractionLayer.js
Normal file
16
experiments/modern/src/runtime/WinampAbstractionLayer.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const Group = require("./Group");
|
||||
|
||||
// Needs to behavve like a group to work with top level scripts that call `getScriptGroup`
|
||||
class WinampAbstractionLayer extends Group {
|
||||
/**
|
||||
* getClassName()
|
||||
*
|
||||
* Returns the class name for the object.
|
||||
* @ret The class name.
|
||||
*/
|
||||
static getClassName() {
|
||||
return "WinampAbstractionLayer";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WinampAbstractionLayer;
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
const System = require("./System");
|
||||
const Button = require("./Button");
|
||||
const Group = require("./Group");
|
||||
const Layout = require('./Layout');
|
||||
const Container = require('./Container');
|
||||
const GuiObject = require("./GuiObject");
|
||||
const MakiObject = require("./MakiObject");
|
||||
|
||||
class PopupMenu {}
|
||||
class Container {}
|
||||
class List {}
|
||||
|
||||
const runtime = {
|
||||
|
|
@ -12,8 +14,10 @@ const runtime = {
|
|||
d6f50f6449b793fa66baf193983eaeef: System,
|
||||
"45be95e5419120725fbb5c93fd17f1f9": Group,
|
||||
"4ee3e1994becc636bc78cd97b028869c": GuiObject,
|
||||
"698eddcd4fec8f1e44f9129b45ff09f9": Button,
|
||||
f4787af44ef7b2bb4be7fb9c8da8bea9: PopupMenu,
|
||||
e90dc47b4ae7840d0b042cb0fcf775d2: Container,
|
||||
"60906d4e482e537e94cc04b072568861": Layout,
|
||||
b2023ab54ba1434d6359aebec6f30375: List,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -96,3 +96,39 @@ export async function inlineIncludes(xml, zip) {
|
|||
return includedFile.children;
|
||||
});
|
||||
}
|
||||
|
||||
// Operations on trees
|
||||
export function findParentNodeOfType(node, type) {
|
||||
let n = node;
|
||||
while(n.parent !== null) {
|
||||
n = n.parent;
|
||||
if ((!Array.isArray(type) && n.xmlNode.name === type) ||
|
||||
(Array.isArray(type) && type.includes(n.xmlNode.name))) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function findDescendantByTypeAndId(node, type, id) {
|
||||
if (node.children.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for(let i = 0; i < node.children.length; i++) {
|
||||
const child = node.children[i];
|
||||
if ((!type || child.xmlNode.name === type) &&
|
||||
(child.xmlNode.attributes !== undefined && child.xmlNode.attributes.id === id)) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
for(let i = 0; i < node.children.length; i++) {
|
||||
const child = node.children[i];
|
||||
const descendant = findDescendantByTypeAndId(child, type, id);
|
||||
if (descendant) {
|
||||
return descendant;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue