mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 02:15:01 +00:00
Pull ani-cursor out into its own package (#1040)
* Pull ani-cursor out into its own package * Fix yarn.lock? * Ignore compiled tests * Was yarn.lock messed up? * Add ani-cursor to build
This commit is contained in:
parent
f6cf3a1897
commit
5abadecc4f
22 changed files with 1854 additions and 124 deletions
5
.github/workflows/nodejs.yml
vendored
5
.github/workflows/nodejs.yml
vendored
|
|
@ -24,10 +24,13 @@ jobs:
|
|||
yarn workspace webamp type-check
|
||||
- name: Build
|
||||
run: |
|
||||
yarn workspace ani-cursor build
|
||||
yarn workspace webamp build
|
||||
yarn workspace webamp build-library
|
||||
- name: Run Unit Tests
|
||||
run: yarn workspace webamp test
|
||||
run: |
|
||||
yarn workspace ani-cursor test
|
||||
yarn workspace webamp test
|
||||
- name: Run Integration Tests
|
||||
run: yarn workspace webamp integration-tests
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"examples/*"
|
||||
],
|
||||
"scripts": {
|
||||
"deploy": "yarn workspace webamp build && yarn workspace webamp build-library"
|
||||
"deploy": "yarn workspace ani-cursor build && yarn workspace webamp build && yarn workspace webamp build-library"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.0.5"
|
||||
|
|
|
|||
1
packages/ani-cursor/.gitignore
vendored
Normal file
1
packages/ani-cursor/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
dist/
|
||||
34
packages/ani-cursor/README.md
Normal file
34
packages/ani-cursor/README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
## Install
|
||||
|
||||
```bash
|
||||
yarn add ani-cursor
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
npm install ani-cursor
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```JavaScript
|
||||
import {convertAniBinaryToCSS} from 'ani-cursor';
|
||||
|
||||
async function applyCursor(selector, aniUrl) {
|
||||
const response = await fetch(aniUrl);
|
||||
const data = new Uint8Array(await response.arrayBuffer());
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.innerText = convertAniBinaryToCSS(selector, data);
|
||||
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
const h1 = document.createElement('h1');
|
||||
h1.id = 'pizza';
|
||||
h1.innerText = 'Pizza Time!';
|
||||
document.body.appendChild(h1);
|
||||
|
||||
applyCursor("#pizza", "https://archive.org/cors/tucows_169906_Pizza_cursor/pizza.ani");
|
||||
```
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Edge cases piano.ani 1`] = `
|
||||
"
|
||||
@keyframes ani-cursor-3 {
|
||||
0% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAA0ACQDoAgAAFgAAACgAAAAgAAAAQAAAAAEABA...), auto; }
|
||||
20% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAA0ACQDoAgAAFgAAACgAAAAgAAAAQAAAAAEABA...), auto; }
|
||||
40% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAA0ACQDoAgAAFgAAACgAAAAgAAAAQAAAAAEABA...), auto; }
|
||||
60% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAA0ACQDoAgAAFgAAACgAAAAgAAAAQAAAAAEABA...), auto; }
|
||||
80% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAA0ACQDoAgAAFgAAACgAAAAgAAAAQAAAAAEABA...), auto; }
|
||||
}
|
||||
#example:hover {
|
||||
animation: ani-cursor-3 1250ms step-end infinite;
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Green Dimension v2.wsz eqslid.cur 1`] = `
|
||||
"
|
||||
@keyframes ani-cursor-2 {
|
||||
0% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIA...), auto; }
|
||||
50% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIA...), auto; }
|
||||
}
|
||||
#example:hover {
|
||||
animation: ani-cursor-2 266.6666666666667ms step-end infinite;
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Super_Mario_Amp_2.wsz close.cur 1`] = `
|
||||
"
|
||||
@keyframes ani-cursor-1 {
|
||||
0% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACA...), auto; }
|
||||
7.246376811594203% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACA...), auto; }
|
||||
14.492753623188406% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACA...), auto; }
|
||||
21.73913043478261% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACA...), auto; }
|
||||
28.985507246376812%, 38.405797101449274%, 44.927536231884055% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACA...), auto; }
|
||||
34.05797101449276%, 42.028985507246375%, 47.10144927536232% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACA...), auto; }
|
||||
48.55072463768116% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACA...), auto; }
|
||||
49.275362318840585% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACA...), auto; }
|
||||
50.72463768115942%, 53.62318840579711%, 56.52173913043478% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACA...), auto; }
|
||||
52.17391304347826%, 55.072463768115945% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACA...), auto; }
|
||||
}
|
||||
#example:hover {
|
||||
animation: ani-cursor-1 2300ms step-end infinite;
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Super_Mario_Amp_2.wsz eqslid.cur 1`] = `
|
||||
"
|
||||
@keyframes ani-cursor-0 {
|
||||
0% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGA...), auto; }
|
||||
12.5% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGA...), auto; }
|
||||
25% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGA...), auto; }
|
||||
37.5% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGA...), auto; }
|
||||
50% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGA...), auto; }
|
||||
62.5% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGA...), auto; }
|
||||
75% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGA...), auto; }
|
||||
87.5% { cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGA...), auto; }
|
||||
}
|
||||
#example:hover {
|
||||
animation: ani-cursor-0 1333.3333333333335ms step-end infinite;
|
||||
}
|
||||
"
|
||||
`;
|
||||
39
packages/ani-cursor/__tests__/parser.test.ts
Normal file
39
packages/ani-cursor/__tests__/parser.test.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { convertAniBinaryToCSS } from "../";
|
||||
|
||||
const LONG_BASE_64 = /([A-Za-z0-9+/=]{50})[A-Za-z0-9+/=]+/g;
|
||||
|
||||
// Parse a `.ani` in our fixture directory and trim down the image data for use
|
||||
// in snapshot tests.
|
||||
function readPathCss(filePath: string): string {
|
||||
const buffer = fs.readFileSync(path.join(__dirname, "./fixtures/", filePath));
|
||||
|
||||
return convertAniBinaryToCSS("#example", buffer).replace(
|
||||
LONG_BASE_64,
|
||||
"$1..."
|
||||
);
|
||||
}
|
||||
|
||||
// https://skins.webamp.org/skin/6e30f9e9b8f5719469809785ae5e4a1f/Super_Mario_Amp_2.wsz/
|
||||
describe("Super_Mario_Amp_2.wsz", () => {
|
||||
test("eqslid.cur", async () => {
|
||||
expect(readPathCss("Super_Mario_Amp_2/eqslid.cur")).toMatchSnapshot();
|
||||
});
|
||||
test("close.cur", async () => {
|
||||
expect(readPathCss("Super_Mario_Amp_2/close.cur")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
// https://skins.webamp.org/skin/4308a2fc648033bf5fe7c4d56a5c8823/Green-Dimension-V2.wsz/
|
||||
describe("Green Dimension v2.wsz", () => {
|
||||
test("eqslid.cur", async () => {
|
||||
expect(readPathCss("Green Dimension v2/eqslid.cur")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge cases", () => {
|
||||
test("piano.ani", async () => {
|
||||
expect(readPathCss("piano.ani")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
6
packages/ani-cursor/babel.config.js
Normal file
6
packages/ani-cursor/babel.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
["@babel/preset-env", { targets: { node: "current" } }],
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
};
|
||||
|
|
@ -1,45 +1,26 @@
|
|||
import { parseAni } from "./aniParser";
|
||||
import { AniCursorImage } from "./types";
|
||||
import * as Utils from "./utils";
|
||||
import * as FileUtils from "./fileUtils";
|
||||
import { parseAni } from "./parser";
|
||||
|
||||
type AniCursorImage = {
|
||||
frames: {
|
||||
url: string;
|
||||
percents: number[];
|
||||
}[];
|
||||
duration: number;
|
||||
};
|
||||
|
||||
const JIFFIES_PER_MS = 1000 / 60;
|
||||
|
||||
export function readAni(contents: Uint8Array): AniCursorImage {
|
||||
const ani = parseAni(contents);
|
||||
const rate = ani.rate ?? ani.images.map(() => ani.metadata.iDispRate);
|
||||
const duration = Utils.sum(rate);
|
||||
|
||||
const frames = ani.images.map((image) => ({
|
||||
url: FileUtils.curUrlFromByteArray(image),
|
||||
percents: [] as number[],
|
||||
}));
|
||||
|
||||
let elapsed = 0;
|
||||
rate.forEach((r, i) => {
|
||||
const frameIdx = ani.seq ? ani.seq[i] : i;
|
||||
frames[frameIdx].percents.push((elapsed / duration) * 100);
|
||||
elapsed += r;
|
||||
});
|
||||
|
||||
return { duration: duration * JIFFIES_PER_MS, frames };
|
||||
}
|
||||
|
||||
// Generate CSS for an animated cursor.
|
||||
//
|
||||
// Based on https://css-tricks.com/forums/topic/animated-cursor/
|
||||
//
|
||||
// Browsers won't render animated cursor images specified via CSS. For `.ani`
|
||||
// images, we already have the frames as indiviual images, so we create a CSS
|
||||
// animation.
|
||||
//
|
||||
// This function returns CSS containing a set of keyframes with embedded Data
|
||||
// URIs as well as a CSS rule to the given selector.
|
||||
//
|
||||
// **Note:** This does not seem to work on Safari. I've filed an issue here:
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=219564
|
||||
export function aniCss(selector: string, ani: AniCursorImage): string {
|
||||
const animationName = `webamp-ani-cursor-${Utils.uniqueId()}`;
|
||||
export function convertAniBinaryToCSS(
|
||||
selector: string,
|
||||
aniBinary: Uint8Array
|
||||
): string {
|
||||
const ani = readAni(aniBinary);
|
||||
|
||||
const animationName = `ani-cursor-${uniqueId()}`;
|
||||
|
||||
const keyframes = ani.frames.map(({ url, percents }) => {
|
||||
const percent = percents.map((num) => `${num}%`).join(", ");
|
||||
|
|
@ -62,14 +43,51 @@ export function aniCss(selector: string, ani: AniCursorImage): string {
|
|||
// visible.
|
||||
const pseudoSelector = ":hover";
|
||||
|
||||
// prettier-ignore
|
||||
return `
|
||||
@keyframes ${animationName} {
|
||||
@keyframes ${animationName} {
|
||||
${keyframes.join("\n")}
|
||||
}
|
||||
${selector}${pseudoSelector} {
|
||||
animation: ${animationName} ${
|
||||
ani.duration
|
||||
}ms ${timingFunction} infinite;
|
||||
}
|
||||
`;
|
||||
}
|
||||
${selector}${pseudoSelector} {
|
||||
animation: ${animationName} ${ani.duration}ms ${timingFunction} infinite;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function readAni(contents: Uint8Array): AniCursorImage {
|
||||
const ani = parseAni(contents);
|
||||
const rate = ani.rate ?? ani.images.map(() => ani.metadata.iDispRate);
|
||||
const duration = sum(rate);
|
||||
|
||||
const frames = ani.images.map((image) => ({
|
||||
url: curUrlFromByteArray(image),
|
||||
percents: [] as number[],
|
||||
}));
|
||||
|
||||
let elapsed = 0;
|
||||
rate.forEach((r, i) => {
|
||||
const frameIdx = ani.seq ? ani.seq[i] : i;
|
||||
frames[frameIdx].percents.push((elapsed / duration) * 100);
|
||||
elapsed += r;
|
||||
});
|
||||
|
||||
return { duration: duration * JIFFIES_PER_MS, frames };
|
||||
}
|
||||
|
||||
/* Utility Functions */
|
||||
|
||||
let i = 0;
|
||||
const uniqueId = () => i++;
|
||||
|
||||
function base64FromDataArray(dataArray: Uint8Array): string {
|
||||
return window.btoa(String.fromCharCode(...dataArray));
|
||||
}
|
||||
|
||||
function curUrlFromByteArray(arr: Uint8Array) {
|
||||
const base64 = base64FromDataArray(arr);
|
||||
return `data:image/x-win-bitmap;base64,${base64}`;
|
||||
}
|
||||
|
||||
function sum(values: number[]): number {
|
||||
return values.reduce((total, value) => total + value, 0);
|
||||
}
|
||||
31
packages/ani-cursor/package.json
Normal file
31
packages/ani-cursor/package.json
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "ani-cursor",
|
||||
"version": "1.0.0",
|
||||
"description": "Render .ani cursors as CSS animations in the browser",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": "Jordan Eldredge <jordan@jordaneldredge.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest",
|
||||
"prepublish": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@babel/preset-env": "^7.12.7",
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"@types/jest": "^26.0.18",
|
||||
"babel-jest": "^26.6.3",
|
||||
"jest": "^26.6.3",
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"byte-data": "18.1.1",
|
||||
"riff-file": "^1.0.3"
|
||||
},
|
||||
"jest": {
|
||||
"modulePathIgnorePatterns": ["dist"]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ type Chunk = {
|
|||
};
|
||||
|
||||
// https://www.informit.com/articles/article.aspx?p=1189080&seqNum=3
|
||||
export type AniMetadata = {
|
||||
type AniMetadata = {
|
||||
cbSize: number; // Data structure size (in bytes)
|
||||
nFrames: number; // Number of images (also known as frames) stored in the file
|
||||
nSteps: number; // Number of frames to be displayed before the animation repeats
|
||||
|
|
@ -24,7 +24,7 @@ export type AniMetadata = {
|
|||
bfAttributes: number; // ANI attribute bit flags
|
||||
};
|
||||
|
||||
export type ParsedAni = {
|
||||
type ParsedAni = {
|
||||
rate: number[] | null;
|
||||
seq: number[] | null;
|
||||
images: Uint8Array[];
|
||||
33
packages/ani-cursor/tsconfig.json
Normal file
33
packages/ani-cursor/tsconfig.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||
"sourceMap": true /* Generates corresponding '.map' file. */,
|
||||
"outDir": "./dist" /* Redirect output structure to the directory. */,
|
||||
"downlevelIteration": true /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */,
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
"strictNullChecks": true /* Enable strict null checks. */,
|
||||
"strictFunctionTypes": true /* Enable strict checking of function types. */,
|
||||
"strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */,
|
||||
"strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
|
||||
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
|
||||
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true /* Report errors on unused locals. */,
|
||||
"noUnusedParameters": true /* Report errors on unused parameters. */,
|
||||
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
||||
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
|
||||
|
||||
/* Module Resolution Options */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
|
||||
"declaration": true,
|
||||
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -1,58 +0,0 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { parseAni } from "./aniParser";
|
||||
import { readAni, aniCss } from "./aniUtils";
|
||||
|
||||
// Parse a `.ani` in our fixture directory and trim down the image data for use
|
||||
// in snapshot tests.
|
||||
function parsePath(filePath: string) {
|
||||
const buffer = fs.readFileSync(
|
||||
path.join(__dirname, "./__tests__/fixtures/ani/", filePath)
|
||||
);
|
||||
|
||||
const ani = parseAni(buffer);
|
||||
const imgString = ani.images.map((image) => image.slice(0, 60).join(""));
|
||||
// @ts-ignore
|
||||
ani.images = `${imgString}...`;
|
||||
return ani;
|
||||
}
|
||||
|
||||
function readPathCss(filePath: string) {
|
||||
const buffer = fs.readFileSync(
|
||||
path.join(__dirname, "./__tests__/fixtures/ani/", filePath)
|
||||
);
|
||||
|
||||
const ani = readAni(buffer);
|
||||
ani.frames = ani.frames.map(({ url, percents }) => ({
|
||||
url: url.slice(0, 60),
|
||||
percents,
|
||||
}));
|
||||
return aniCss("#example", ani);
|
||||
}
|
||||
|
||||
// https://skins.webamp.org/skin/6e30f9e9b8f5719469809785ae5e4a1f/Super_Mario_Amp_2.wsz/
|
||||
describe("Super_Mario_Amp_2.wsz", () => {
|
||||
test("eqslid.cur", async () => {
|
||||
expect(parsePath("Super_Mario_Amp_2/eqslid.cur")).toMatchSnapshot();
|
||||
expect(readPathCss("Super_Mario_Amp_2/eqslid.cur")).toMatchSnapshot();
|
||||
});
|
||||
test("close.cur", async () => {
|
||||
expect(parsePath("Super_Mario_Amp_2/close.cur")).toMatchSnapshot();
|
||||
expect(readPathCss("Super_Mario_Amp_2/close.cur")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
// https://skins.webamp.org/skin/4308a2fc648033bf5fe7c4d56a5c8823/Green-Dimension-V2.wsz/
|
||||
describe("Green Dimension v2.wsz", () => {
|
||||
test("eqslid.cur", async () => {
|
||||
expect(parsePath("Green Dimension v2/eqslid.cur")).toMatchSnapshot();
|
||||
expect(readPathCss("Green Dimension v2/eqslid.cur")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge cases", () => {
|
||||
test("piano.ani", async () => {
|
||||
expect(parsePath("piano.ani")).toMatchSnapshot();
|
||||
expect(readPathCss("piano.ani")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -7,7 +7,7 @@ import { SkinImages } from "../types";
|
|||
import { createSelector } from "reselect";
|
||||
import Css from "./Css";
|
||||
import ClipPaths from "./ClipPaths";
|
||||
import { aniCss } from "../aniUtils";
|
||||
import { convertAniBinaryToCSS } from "ani-cursor";
|
||||
|
||||
const CSS_PREFIX = "#webamp";
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ const getCssRules = createSelector(
|
|||
case "cur":
|
||||
return `${selector} {cursor: url(${cursor.url}), auto}`;
|
||||
case "ani": {
|
||||
return aniCss(selector, cursor.ani);
|
||||
return convertAniBinaryToCSS(selector, cursor.aniData);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import SKIN_SPRITES, { Sprite } from "./skinSprites";
|
|||
import { DEFAULT_SKIN } from "./constants";
|
||||
import * as Utils from "./utils";
|
||||
import * as FileUtils from "./fileUtils";
|
||||
import * as AniUtils from "./aniUtils";
|
||||
|
||||
export const getFileExtension = (fileName: string): string | null => {
|
||||
const matches = /\.([a-z]{3,4})$/i.exec(fileName);
|
||||
|
|
@ -143,7 +142,7 @@ export async function getCursorFromFilename(
|
|||
const contents = file.contents as Uint8Array;
|
||||
if (arrayStartsWith(contents, RIFF_MAGIC)) {
|
||||
try {
|
||||
return { type: "ani", ani: AniUtils.readAni(contents) };
|
||||
return { type: "ani", aniData: contents };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -75,14 +75,6 @@ export type Band =
|
|||
|
||||
export type Slider = Band | "preamp";
|
||||
|
||||
export type AniCursorImage = {
|
||||
frames: {
|
||||
url: string;
|
||||
percents: number[];
|
||||
}[];
|
||||
duration: number;
|
||||
};
|
||||
|
||||
export type CursorImage =
|
||||
| {
|
||||
type: "cur";
|
||||
|
|
@ -90,7 +82,7 @@ export type CursorImage =
|
|||
}
|
||||
| {
|
||||
type: "ani";
|
||||
ani: AniCursorImage;
|
||||
aniData: Uint8Array;
|
||||
};
|
||||
|
||||
// TODO: Use a type to ensure these keys mirror the CURSORS constant in
|
||||
|
|
|
|||
|
|
@ -147,10 +147,9 @@
|
|||
"trailingComma": "es5"
|
||||
},
|
||||
"dependencies": {
|
||||
"byte-data": "18.1.1",
|
||||
"ani-cursor": "^1.0.0",
|
||||
"eslint-plugin-react-hooks": "^2.1.2",
|
||||
"fscreen": "^1.0.2",
|
||||
"redux-sentry-middleware": "^0.1.3",
|
||||
"riff-file": "^1.0.3"
|
||||
"redux-sentry-middleware": "^0.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue