mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-25 11:04:00 +00:00
remove registry, use tree lookups (#828)
* remove images from registry, use tree lookups * dont need registry for images anymore * remove registry * remove registry, apply groupdefs using lookups * change function name * simplify direct descendants * remove prev siblings methods, just check inside loop for node * comment on search * handle groupdefs we dont find * handle not finding specific id in first <elements> we find * abstract out findInLexicalScope * Add Js prefix for non-Maki objects
This commit is contained in:
parent
8ee3882aba
commit
8a2aef62f4
8 changed files with 205 additions and 82 deletions
|
|
@ -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 (
|
||||
<>
|
||||
<img
|
||||
|
|
@ -80,13 +83,12 @@ function Layout({
|
|||
);
|
||||
}
|
||||
|
||||
function Layer({ id, image, children, x, y }) {
|
||||
const data = React.useContext(SkinContext);
|
||||
function Layer({ node, id, image, children, x, y }) {
|
||||
if (image == null) {
|
||||
console.warn("Got an Layer without an image. Rendering null", id);
|
||||
return null;
|
||||
}
|
||||
const img = data[image.toLowerCase()];
|
||||
const img = node.js_imageLookup(image.toLowerCase());
|
||||
if (img == null) {
|
||||
console.warn("Unable to find image to render. Rendering null", image);
|
||||
return null;
|
||||
|
|
@ -124,7 +126,6 @@ function Layer({ id, image, children, x, y }) {
|
|||
}
|
||||
|
||||
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;
|
||||
if (imgId == null) {
|
||||
|
|
@ -132,7 +133,7 @@ function Button({ id, image, action, x, y, downImage, tooltip, node, children })
|
|||
return null;
|
||||
}
|
||||
// TODO: These seem to be switching too fast
|
||||
const img = data[imgId.toLowerCase()];
|
||||
const img = node.js_imageLookup(imgId);
|
||||
if (img == null) {
|
||||
console.warn("Unable to find image to render. Rendering null", image);
|
||||
return null;
|
||||
|
|
@ -228,7 +229,7 @@ function XmlNode({ node }) {
|
|||
function App() {
|
||||
const [data, setData] = React.useState(null);
|
||||
React.useEffect(() => {
|
||||
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 <h1>Loading...</h1>;
|
||||
}
|
||||
const { root, registry } = data;
|
||||
const root = data;
|
||||
|
||||
return (
|
||||
<SkinContext.Provider value={registry.images}>
|
||||
<XmlNode node={root} />
|
||||
</SkinContext.Provider>
|
||||
);
|
||||
return <XmlNode node={root} />;
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class GuiObject extends MakiObject {
|
|||
return this.findobject(id);
|
||||
}
|
||||
init(newRoot) {
|
||||
this.parent = newRoot;
|
||||
newRoot.js_addChild(this);
|
||||
return this;
|
||||
}
|
||||
|
|
|
|||
15
experiments/modern/src/runtime/JsElements.js
Normal file
15
experiments/modern/src/runtime/JsElements.js
Normal file
|
|
@ -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;
|
||||
15
experiments/modern/src/runtime/JsGammaSet.js
Normal file
15
experiments/modern/src/runtime/JsGammaSet.js
Normal file
|
|
@ -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;
|
||||
15
experiments/modern/src/runtime/JsGroupDef.js
Normal file
15
experiments/modern/src/runtime/JsGroupDef.js
Normal file
|
|
@ -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;
|
||||
|
|
@ -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()
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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 <Elements> nodes that are in node's lexical scope.
|
||||
// return the first child of an <Elements> 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 <GroupDef> 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue