mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-22 01:29:22 +00:00
working on initialize and linking groupdefs (#820)
* move images to initialize * return nodes from initialize, lookup groupdefs * fix attributes when copying groupdefs * position groups with x/y * more parsers for classic modern skin * if img doesn't exist just return the node without adding to registry * dont need nested node * simplify ids * simplify splitValues * new variable with name
This commit is contained in:
parent
cf09ea75d7
commit
0d16df9c27
2 changed files with 222 additions and 134 deletions
|
|
@ -33,77 +33,20 @@ const IGNORE_IDS = new Set([
|
|||
|
||||
const SkinContext = React.createContext(null);
|
||||
|
||||
async function loadImage(imgUrl) {
|
||||
return await new Promise(resolve => {
|
||||
const img = new Image();
|
||||
img.addEventListener("load", function() {
|
||||
resolve(img);
|
||||
});
|
||||
img.src = imgUrl;
|
||||
});
|
||||
}
|
||||
|
||||
async function getSkin() {
|
||||
const resp = await fetch(process.env.PUBLIC_URL + "/skins/simple.wal");
|
||||
const resp = await fetch(process.env.PUBLIC_URL + "/skins/CornerAmp_Redux.wal");
|
||||
const blob = await resp.blob();
|
||||
const zip = null;
|
||||
|
||||
const registry = { scripts: [] };
|
||||
await initialize(registry, blob);
|
||||
console.log("registry: ", registry);
|
||||
throw new Error("done");
|
||||
const zip = await JSZip.loadAsync(blob);
|
||||
const skinXml = await Utils.inlineIncludes(
|
||||
await Utils.readXml(zip, "skin.xml"),
|
||||
zip
|
||||
);
|
||||
|
||||
// const system = new System();
|
||||
|
||||
const images = {};
|
||||
await Utils.asyncTreeFlatMap(skinXml, async node => {
|
||||
// TODO: This is probalby only valid if in an `<elements>` node
|
||||
switch (node.name) {
|
||||
case "bitmap": {
|
||||
let { file, gammagroup, h, id, w, x, y } = node.attributes;
|
||||
// TODO: Escape file for regex
|
||||
const img = Utils.getCaseInsensitveFile(zip, file);
|
||||
const imgBlob = await img.async("blob");
|
||||
const imgUrl = URL.createObjectURL(imgBlob);
|
||||
if (w === undefined || h === undefined) {
|
||||
const image = await loadImage(imgUrl);
|
||||
w = image.width;
|
||||
h = image.height;
|
||||
x = x !== undefined ? x : 0;
|
||||
y = y !== undefined ? y : 0;
|
||||
}
|
||||
images[id.toLowerCase()] = { file, gammagroup, h, w, x, y, imgUrl };
|
||||
break;
|
||||
}
|
||||
case "truetypefont": {
|
||||
//console.log(element);
|
||||
break;
|
||||
}
|
||||
case "script": {
|
||||
const { file, param } = node.attributes;
|
||||
if (!file.endsWith("standardframe.maki")) {
|
||||
break;
|
||||
}
|
||||
const scriptFile = Utils.getCaseInsensitveFile(zip, file);
|
||||
const data = await scriptFile.async("uint8array");
|
||||
// interpret({ data, system, runtime, log: true });
|
||||
console.log(data);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// console.error(`Unknown node ${node.name}`);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
});
|
||||
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, images];
|
||||
return [skinXml, nodes, registry];
|
||||
}
|
||||
|
||||
function Layout({
|
||||
|
|
@ -219,11 +162,24 @@ function Button({ id, image, action, x, y, downImage, tooltip, children }) {
|
|||
}
|
||||
|
||||
function ToggleButton(props) {
|
||||
return <Button {...props} />;
|
||||
return <Button data-node-type="togglebutton" {...props} />;
|
||||
}
|
||||
|
||||
function GroupDef(props) {
|
||||
return <div {...props} />;
|
||||
function Group(props) {
|
||||
const { id, children, x, y} = props;
|
||||
const style = {
|
||||
position: "absolute",
|
||||
};
|
||||
if (x !== undefined) {
|
||||
style.left = Number(x);
|
||||
}
|
||||
if (y !== undefined) {
|
||||
style.top = Number(y);
|
||||
}
|
||||
return <div
|
||||
data-node-type="group"
|
||||
data-node-id={id}
|
||||
style={style}>{children}</div>;
|
||||
}
|
||||
|
||||
const NODE_NAME_TO_COMPONENT = {
|
||||
|
|
@ -231,26 +187,27 @@ const NODE_NAME_TO_COMPONENT = {
|
|||
layer: Layer,
|
||||
button: Button,
|
||||
togglebutton: ToggleButton,
|
||||
groupef: GroupDef,
|
||||
group: Group,
|
||||
};
|
||||
|
||||
// Given a skin XML node, pick which component to use, and render it.
|
||||
function XmlNode({ node }) {
|
||||
const attributes = node.attributes;
|
||||
const name = node.name;
|
||||
if (attributes && IGNORE_IDS.has(attributes.id)) {
|
||||
return null;
|
||||
}
|
||||
if (node.name == null) {
|
||||
if (name == null) {
|
||||
// This is likely a comment
|
||||
return null;
|
||||
}
|
||||
const Component = NODE_NAME_TO_COMPONENT[node.name];
|
||||
const Component = NODE_NAME_TO_COMPONENT[name];
|
||||
const childNodes = node.children || [];
|
||||
const children = childNodes.map((childNode, i) => (
|
||||
<XmlNode key={i} node={childNode} />
|
||||
<XmlNode key={i} parent={node} node={childNode} />
|
||||
));
|
||||
if (Component == null) {
|
||||
console.warn("Unknown node type", node.name);
|
||||
console.warn("Unknown node type", name);
|
||||
if (childNodes.length) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
|
@ -267,16 +224,11 @@ function App() {
|
|||
if (data == null) {
|
||||
return <h1>Loading...</h1>;
|
||||
}
|
||||
const [skinXml, images] = data;
|
||||
const [skinXml, nodes, registry] = data;
|
||||
return (
|
||||
<SkinContext.Provider value={images}>
|
||||
<SkinContext.Provider value={registry.images}>
|
||||
<XmlNode
|
||||
node={
|
||||
// TODO: This is not quite right. Really we should only be rendering the
|
||||
// portion of the XML that is actually view code.
|
||||
// For now we just render the whole thing and ignore whatever we don't recognize
|
||||
skinXml.children[0]
|
||||
}
|
||||
node={nodes}
|
||||
/>
|
||||
</SkinContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,27 @@
|
|||
import * as Utils from "./utils";
|
||||
import JSZip from "jszip";
|
||||
|
||||
function splitValues(str) {
|
||||
return str.split(",").map(parseFloat);
|
||||
}
|
||||
|
||||
async function loadImage(imgUrl) {
|
||||
return await new Promise(resolve => {
|
||||
const img = new Image();
|
||||
img.addEventListener("load", function() {
|
||||
resolve(img);
|
||||
});
|
||||
img.src = imgUrl;
|
||||
});
|
||||
}
|
||||
|
||||
let idCount = 0;
|
||||
function getId() {
|
||||
return '_' + idCount++;
|
||||
}
|
||||
|
||||
const schema = {
|
||||
groupdef: [
|
||||
"scripts",
|
||||
"layer",
|
||||
"layoutstatus",
|
||||
"hideobject",
|
||||
|
|
@ -18,6 +37,39 @@ const schema = {
|
|||
"wasabi:button",
|
||||
"text",
|
||||
"vis",
|
||||
"grid",
|
||||
"rect",
|
||||
"animatedlayer",
|
||||
"nstatesbutton",
|
||||
"togglebutton",
|
||||
"songticker",
|
||||
"menu",
|
||||
"status",
|
||||
"albumart",
|
||||
"playlistplus",
|
||||
],
|
||||
group: [
|
||||
"button",
|
||||
"layer",
|
||||
"text",
|
||||
"vis",
|
||||
"group",
|
||||
"scripts",
|
||||
"layoutstatus",
|
||||
"hideobject",
|
||||
"wasabi:titlebar",
|
||||
"menu",
|
||||
"nstatesbutton",
|
||||
"status",
|
||||
"script",
|
||||
"songticker",
|
||||
"grid",
|
||||
"animatedlayer",
|
||||
"togglebutton",
|
||||
"slider",
|
||||
"rect",
|
||||
"eqvis",
|
||||
"playlistplus",
|
||||
],
|
||||
layout: [
|
||||
"wasabi:standardframe:status",
|
||||
|
|
@ -29,10 +81,16 @@ const schema = {
|
|||
"status",
|
||||
"slider",
|
||||
"group",
|
||||
"sendparams",
|
||||
"script",
|
||||
"grid",
|
||||
"vis",
|
||||
"rect",
|
||||
"component",
|
||||
],
|
||||
container: ["groupdef", "layout", "scripts"],
|
||||
scripts: ["script"],
|
||||
elements: ["color", "bitmap", "bitmapfont", "truetypefont"],
|
||||
elements: ["color", "bitmap", "bitmapfont", "truetypefont", "cursor", "elementalias"],
|
||||
skininfo: [
|
||||
"version",
|
||||
"name",
|
||||
|
|
@ -42,7 +100,7 @@ const schema = {
|
|||
"homepage",
|
||||
"screenshot",
|
||||
],
|
||||
wasabixml: ["skininfo", "scripts", "elements", "groupdef", "container"],
|
||||
wasabixml: ["skininfo", "scripts", "elements", "groupdef", "container", "gammaset"],
|
||||
// same as above, wa3 vs wa5
|
||||
winampabstractionlayer: [
|
||||
"skininfo",
|
||||
|
|
@ -50,59 +108,131 @@ const schema = {
|
|||
"elements",
|
||||
"groupdef",
|
||||
"container",
|
||||
"gammaset",
|
||||
"accelerators",
|
||||
],
|
||||
gammaset: ["gammagroup"],
|
||||
accelerators: ["accelerator"],
|
||||
};
|
||||
|
||||
const noop = (node) => node;
|
||||
|
||||
const parsers = {
|
||||
groupdef(node, parent, registry, zip) {},
|
||||
skininfo: () => {},
|
||||
version: () => {},
|
||||
name: () => {},
|
||||
comment: () => {},
|
||||
author: () => {},
|
||||
email: () => {},
|
||||
homepage: () => {},
|
||||
screenshot: () => {},
|
||||
container: () => {},
|
||||
scripts: () => {},
|
||||
gammaset: () => {},
|
||||
color: () => {},
|
||||
layer: () => {},
|
||||
layoutstatus: () => {},
|
||||
hideobject: () => {},
|
||||
button: () => {},
|
||||
group: () => {},
|
||||
layout: () => {},
|
||||
sendparams: () => {},
|
||||
elements: () => {},
|
||||
bitmap: () => {},
|
||||
eqvis: () => {},
|
||||
slider: () => {},
|
||||
gammagroup: () => {},
|
||||
truetypefont: () => {},
|
||||
component: () => {},
|
||||
text: () => {},
|
||||
layer: () => {},
|
||||
button: () => {},
|
||||
togglebutton: () => {},
|
||||
status: () => {},
|
||||
slider: () => {},
|
||||
bitmapfont: () => {},
|
||||
vis: () => {},
|
||||
"wasabi:titlebar": () => {},
|
||||
"colorthemes:list": () => {},
|
||||
"wasabi:standardframe:status": () => {},
|
||||
"wasabi:standardframe:nostatus": () => {},
|
||||
"wasabi:button": () => {},
|
||||
groupdef: (node, parent, registry) => {
|
||||
const attributeId = node.attributes.id;
|
||||
registry.groupdefs[attributeId] = node;
|
||||
|
||||
return node;
|
||||
},
|
||||
skininfo: noop,
|
||||
version: noop,
|
||||
name: noop,
|
||||
comment: noop,
|
||||
author: noop,
|
||||
email: noop,
|
||||
homepage: noop,
|
||||
screenshot: noop,
|
||||
container: noop,
|
||||
scripts: noop,
|
||||
gammaset: (node, parent, registry) => {
|
||||
const gammaId = node.attributes.id;
|
||||
if (!registry.gammasets.hasOwnProperty(gammaId)) {
|
||||
registry.gammasets[gammaId] = {};
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
color: noop,
|
||||
layer: noop,
|
||||
layoutstatus: noop,
|
||||
hideobject: noop,
|
||||
button: noop,
|
||||
group: (node, parent, registry) => {
|
||||
if (!node.children || node.children.length === 0) {
|
||||
const groupdef = registry.groupdefs[node.attributes.id];
|
||||
if (groupdef) {
|
||||
return {
|
||||
...node,
|
||||
...groupdef,
|
||||
attributes: { ...node.attributes, ...groupdef.attributes },
|
||||
name: "group",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
layout: noop,
|
||||
sendparams: noop,
|
||||
elements: noop,
|
||||
bitmap: async (node, parent, registry, zip) => {
|
||||
let { file, gammagroup, h, id, w, x, y } = node.attributes;
|
||||
// TODO: Escape file for regex
|
||||
const img = Utils.getCaseInsensitveFile(zip, file);
|
||||
if (img === undefined) {
|
||||
return node;
|
||||
}
|
||||
const imgBlob = await img.async("blob");
|
||||
const imgUrl = URL.createObjectURL(imgBlob);
|
||||
if (w === undefined || h === undefined) {
|
||||
const image = await loadImage(imgUrl);
|
||||
w = image.width;
|
||||
h = image.height;
|
||||
x = x !== undefined ? x : 0;
|
||||
y = y !== undefined ? y : 0;
|
||||
}
|
||||
registry.images[id.toLowerCase()] = { file, gammagroup, h, w, x, y, imgUrl };
|
||||
|
||||
return node;
|
||||
},
|
||||
eqvis: noop,
|
||||
slider: noop,
|
||||
gammagroup: (node, parent, registry) => {
|
||||
const gammaId = parent.attributes.id;
|
||||
const attributeId = node.attributes.id;
|
||||
const attributeValues = splitValues(node.attributes.value);
|
||||
registry.gammasets[gammaId][attributeId] = attributeValues;
|
||||
|
||||
return node;
|
||||
},
|
||||
truetypefont: noop,
|
||||
component: noop,
|
||||
text: noop,
|
||||
layer: noop,
|
||||
button: noop,
|
||||
togglebutton: noop,
|
||||
status: noop,
|
||||
slider: noop,
|
||||
bitmapfont: noop,
|
||||
vis: noop,
|
||||
"wasabi:titlebar": noop,
|
||||
"colorthemes:list": noop,
|
||||
"wasabi:standardframe:status": noop,
|
||||
"wasabi:standardframe:nostatus": noop,
|
||||
"wasabi:button": noop,
|
||||
accelerators: noop,
|
||||
accelerator: noop,
|
||||
cursor: noop,
|
||||
elementalias: noop,
|
||||
grid: noop,
|
||||
rect: noop,
|
||||
animatedlayer: noop,
|
||||
nstatesbutton: noop,
|
||||
songticker: noop,
|
||||
menu: noop,
|
||||
status: 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 });
|
||||
|
||||
return { ...node, script, param };
|
||||
},
|
||||
};
|
||||
|
||||
async function parseChildren(node, parsedParent, registry, zip) {
|
||||
async function parseChildren(node, registry, zip) {
|
||||
if (node.type === "comment") {
|
||||
return;
|
||||
}
|
||||
|
|
@ -112,14 +242,14 @@ async function parseChildren(node, parsedParent, registry, zip) {
|
|||
}
|
||||
|
||||
const validChildren = new Set(schema[node.name.toLowerCase()]);
|
||||
await Promise.all(
|
||||
const resolvedChildren = await Promise.all(
|
||||
node.children.map(async child => {
|
||||
if (child.type === "comment") {
|
||||
return;
|
||||
}
|
||||
if (child.type === "text") {
|
||||
// TODO: Handle text
|
||||
return;
|
||||
return { ...child, id: getId() };
|
||||
}
|
||||
if (child.name == null) {
|
||||
console.error(child);
|
||||
|
|
@ -132,7 +262,6 @@ async function parseChildren(node, parsedParent, registry, zip) {
|
|||
}
|
||||
|
||||
if (!validChildren.has(childName)) {
|
||||
debugger;
|
||||
throw new Error(`Invalid child of a ${node.name}: ${childName}`);
|
||||
}
|
||||
|
||||
|
|
@ -142,21 +271,28 @@ async function parseChildren(node, parsedParent, registry, zip) {
|
|||
return;
|
||||
}
|
||||
const parsedChild = await childParser(child, node, registry, zip);
|
||||
if (child.children != null) {
|
||||
await parseChildren(child, parsedChild, registry, zip);
|
||||
const returnNode = { ...parsedChild, id: getId() };
|
||||
if (parsedChild.children != null) {
|
||||
const parsedChildren = await parseChildren(parsedChild, registry, zip);
|
||||
returnNode.children = parsedChildren.children;
|
||||
}
|
||||
return returnNode;
|
||||
})
|
||||
);
|
||||
// remove comments other trimmed nodes
|
||||
const filteredChildren = resolvedChildren.filter(item => item !== undefined);
|
||||
|
||||
return {
|
||||
...node,
|
||||
children: filteredChildren
|
||||
};
|
||||
}
|
||||
|
||||
async function initialize(registry, skinBlob) {
|
||||
const zip = await JSZip.loadAsync(skinBlob);
|
||||
const skinXml = await Utils.inlineIncludes(
|
||||
await Utils.readXml(zip, "skin.xml"),
|
||||
zip
|
||||
);
|
||||
|
||||
await parseChildren(skinXml.children[0], null, registry, zip);
|
||||
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 };
|
||||
}
|
||||
|
||||
export default initialize;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue