71 lines
1.9 KiB
JavaScript
71 lines
1.9 KiB
JavaScript
|
// @ts-check
|
||
|
|
||
|
import { GifNodeType } from "./constants.js";
|
||
|
import crel, { fragment } from "../common/crel.js";
|
||
|
import getOwn from "../common/getOwn.js";
|
||
|
import { areBytesEqual } from "../common/bytes.js";
|
||
|
/** @typedef {import("../../types/gif.d.ts").GifNode} GifNode */
|
||
|
|
||
|
const textEncoder = new TextEncoder();
|
||
|
|
||
|
const VERSION_87 = textEncoder.encode("87a");
|
||
|
const VERSION_89 = textEncoder.encode("89a");
|
||
|
|
||
|
/**
|
||
|
* @param {(string | Node)[]} children
|
||
|
*/
|
||
|
const p = (...children) => crel("p", {}, ...children);
|
||
|
|
||
|
/**
|
||
|
* @typedef {object} NodeUi
|
||
|
* @prop {string} title
|
||
|
* @prop {HTMLElement | DocumentFragment} description
|
||
|
*/
|
||
|
|
||
|
/** @type {Record<string, (node: GifNode) => NodeUi>} */
|
||
|
const NODE_UI_FNS = {
|
||
|
[GifNodeType.root]: () => ({
|
||
|
title: "GIF file",
|
||
|
description: fragment(),
|
||
|
}),
|
||
|
|
||
|
// Header
|
||
|
|
||
|
[GifNodeType.header]: () => ({
|
||
|
title: "GIF header",
|
||
|
description: fragment(
|
||
|
p('GIFs start with a 6-byte header. This is typically the string "GIF87a" or "GIF89a", encoded as ASCII.'),
|
||
|
),
|
||
|
}),
|
||
|
[GifNodeType.headerSignature]: () => ({
|
||
|
title: "GIF signature",
|
||
|
description: p('GIFs start with the string "GIF", encoded as ASCII.'),
|
||
|
}),
|
||
|
[GifNodeType.headerVersion]: ({ bytes }) => {
|
||
|
/** @type {string} */ let end;
|
||
|
if (areBytesEqual(bytes, VERSION_87)) {
|
||
|
end = "87a";
|
||
|
} else if (areBytesEqual(bytes, VERSION_89)) {
|
||
|
end = "89a";
|
||
|
} else {
|
||
|
end = "something unexpected! This might not be a valid GIF file";
|
||
|
}
|
||
|
return {
|
||
|
title: "GIF version",
|
||
|
description: p(
|
||
|
`The version of the GIF format. This is typically "87a" or "89a". In this case, the version is ${end}.`,
|
||
|
),
|
||
|
};
|
||
|
},
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @param {GifNode} node
|
||
|
* @returns {NodeUi}
|
||
|
*/
|
||
|
export default (node) => {
|
||
|
const uiFn = getOwn(NODE_UI_FNS, node.type);
|
||
|
if (!uiFn) throw new Error("Found a node with no matching UI function");
|
||
|
return uiFn(node);
|
||
|
};
|