Icon resizing of viewPort dynamically, ability to specify custom fore and background colors via env vars (#32)
@@ -141,6 +141,8 @@ BONOB_NAVIDROME_URL | http://$(hostname):4533 | URL for navidrome
|
|||||||
BONOB_NAVIDROME_CUSTOM_CLIENTS | undefined | Comma delimeted mime types for custom navidrome clients when streaming. ie. "audio/flac,audio/ogg" would use client = 'bonob+audio/flac' for flacs, and 'bonob+audio/ogg' for oggs.
|
BONOB_NAVIDROME_CUSTOM_CLIENTS | undefined | Comma delimeted mime types for custom navidrome clients when streaming. ie. "audio/flac,audio/ogg" would use client = 'bonob+audio/flac' for flacs, and 'bonob+audio/ogg' for oggs.
|
||||||
BONOB_SCROBBLE_TRACKS | true | Whether to scrobble the playing of a track if it has been played for >30s
|
BONOB_SCROBBLE_TRACKS | true | Whether to scrobble the playing of a track if it has been played for >30s
|
||||||
BONOB_REPORT_NOW_PLAYING | true | Whether to report a track as now playing
|
BONOB_REPORT_NOW_PLAYING | true | Whether to report a track as now playing
|
||||||
|
BONOB_ICON_FOREGROUND_COLOR | undefined | Icon foreground color in sonos app, must be a valid [web color](https://www.december.com/html/spec/colorsvg.html)
|
||||||
|
BONOB_ICON_BACKGROUND_COLOR | undefined | Icon background color in sonos app, must be a valid [web color](https://www.december.com/html/spec/colorsvg.html)
|
||||||
|
|
||||||
## Initialising service within sonos app
|
## Initialising service within sonos app
|
||||||
|
|
||||||
|
|||||||
5
nodemon.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"watch": ["src/**/*.ts", "web/**/*.svg"],
|
||||||
|
"ignore": [],
|
||||||
|
"exec": "ts-node ./src/app.ts"
|
||||||
|
}
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
"eta": "^1.12.1",
|
"eta": "^1.12.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"fp-ts": "^2.9.5",
|
"fp-ts": "^2.9.5",
|
||||||
|
"libxmljs2": "^0.27.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"node-html-parser": "^2.1.0",
|
"node-html-parser": "^2.1.0",
|
||||||
"scale-that-svg": "^1.0.5",
|
|
||||||
"sharp": "^0.27.2",
|
"sharp": "^0.27.2",
|
||||||
"soap": "^0.37.0",
|
"soap": "^0.37.0",
|
||||||
"ts-md5": "^1.2.7",
|
"ts-md5": "^1.2.7",
|
||||||
@@ -50,8 +50,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -Rf build",
|
"clean": "rm -Rf build",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"dev": "BONOB_SONOS_SERVICE_NAME=bonobDev BONOB_SONOS_DEVICE_DISCOVERY=true nodemon ./src/app.ts",
|
"dev": "BONOB_SONOS_SERVICE_NAME=bonobDev BONOB_SONOS_DEVICE_DISCOVERY=true nodemon",
|
||||||
"devr": "BONOB_SONOS_SERVICE_NAME=bonobDev BONOB_SONOS_DEVICE_DISCOVERY=true BONOB_SONOS_AUTO_REGISTER=true nodemon ./src/app.ts",
|
"devr": "BONOB_SONOS_SERVICE_NAME=bonobDev BONOB_SONOS_DEVICE_DISCOVERY=true BONOB_SONOS_AUTO_REGISTER=true nodemon",
|
||||||
"register-dev": "ts-node ./src/register.ts http://$(hostname):4534",
|
"register-dev": "ts-node ./src/register.ts http://$(hostname):4534",
|
||||||
"test": "jest --testPathIgnorePatterns=build"
|
"test": "jest --testPathIgnorePatterns=build"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ const app = server(
|
|||||||
new InMemoryLinkCodes(),
|
new InMemoryLinkCodes(),
|
||||||
new InMemoryAccessTokens(sha256(config.secret)),
|
new InMemoryAccessTokens(sha256(config.secret)),
|
||||||
SystemClock,
|
SystemClock,
|
||||||
|
config.icons,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
|
||||||
|
export const isChristmas = (clock: Clock = SystemClock) => clock.now().month() == 11 && clock.now().date() == 25;
|
||||||
|
export const isHalloween = (clock: Clock = SystemClock) => clock.now().month() == 9 && clock.now().date() == 31
|
||||||
|
export const isHoli = (clock: Clock = SystemClock) => ["2022/03/18", "2023/03/07", "2024/03/25", "2025/03/14"].map(dayjs).find(it => it.isSame(clock.now())) != undefined
|
||||||
|
export const isCNY = (clock: Clock = SystemClock) => ["2022/02/01", "2023/01/22", "2024/02/10", "2025/02/29"].map(dayjs).find(it => it.isSame(clock.now())) != undefined
|
||||||
|
|
||||||
export interface Clock {
|
export interface Clock {
|
||||||
now(): Dayjs;
|
now(): Dayjs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,24 @@ export default function () {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const colorFrom = (envVar: string) => {
|
||||||
|
const value = process.env[envVar];
|
||||||
|
if (value && value != "") {
|
||||||
|
if (value.match(/^\w+$/)) return value;
|
||||||
|
else throw `Invalid color specified for ${envVar}`;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
port,
|
port,
|
||||||
bonobUrl: url(bonobUrl),
|
bonobUrl: url(bonobUrl),
|
||||||
secret: process.env["BONOB_SECRET"] || "bonob",
|
secret: process.env["BONOB_SECRET"] || "bonob",
|
||||||
|
icons: {
|
||||||
|
foregroundColor: colorFrom("BONOB_ICON_FOREGROUND_COLOR"),
|
||||||
|
backgroundColor: colorFrom("BONOB_ICON_BACKGROUND_COLOR"),
|
||||||
|
},
|
||||||
sonos: {
|
sonos: {
|
||||||
serviceName: process.env["BONOB_SONOS_SERVICE_NAME"] || "bonob",
|
serviceName: process.env["BONOB_SONOS_SERVICE_NAME"] || "bonob",
|
||||||
deviceDiscovery:
|
deviceDiscovery:
|
||||||
|
|||||||
154
src/icon.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import libxmljs, { Element, Attribute } from "libxmljs2";
|
||||||
|
import _ from "underscore";
|
||||||
|
import { Clock, isChristmas, isCNY, isHalloween, isHoli, SystemClock } from "./clock";
|
||||||
|
|
||||||
|
export type Transformation = {
|
||||||
|
viewPortIncreasePercent: number | undefined;
|
||||||
|
backgroundColor: string | undefined;
|
||||||
|
foregroundColor: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SVG_NS = {
|
||||||
|
svg: "http://www.w3.org/2000/svg",
|
||||||
|
};
|
||||||
|
|
||||||
|
class ViewBox {
|
||||||
|
minX: number;
|
||||||
|
minY: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
|
||||||
|
constructor(viewBox: string) {
|
||||||
|
const parts = viewBox.split(" ").map((it) => Number.parseInt(it));
|
||||||
|
this.minX = parts[0]!;
|
||||||
|
this.minY = parts[1]!;
|
||||||
|
this.width = parts[2]!;
|
||||||
|
this.height = parts[3]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public increasePercent = (percent: number) => {
|
||||||
|
const i = Math.floor(((percent / 100) * this.height) / 3);
|
||||||
|
return new ViewBox(
|
||||||
|
`${-i} ${-i} ${this.height + 2 * i} ${this.height + 2 * i}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
public toString = () =>
|
||||||
|
`${this.minX} ${this.minY} ${this.width} ${this.height}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Icon {
|
||||||
|
with(newTransformation: Partial<Transformation>): Icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ColorOverridingIcon implements Icon {
|
||||||
|
rule: () => Boolean;
|
||||||
|
newColors: () => Pick<Transformation, "backgroundColor" | "foregroundColor">;
|
||||||
|
icon: Icon;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
icon: Icon,
|
||||||
|
rule: () => Boolean,
|
||||||
|
newColors: () => Pick<Transformation, "backgroundColor" | "foregroundColor">
|
||||||
|
) {
|
||||||
|
this.icon = icon;
|
||||||
|
this.rule = rule;
|
||||||
|
this.newColors = newColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public with = (transformation: Partial<Transformation>) =>
|
||||||
|
this.rule()
|
||||||
|
? this.icon.with({ ...transformation, ...this.newColors() })
|
||||||
|
: this.icon.with(transformation);
|
||||||
|
|
||||||
|
public toString = () => this.with({}).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SvgIcon implements Icon {
|
||||||
|
private svg: string;
|
||||||
|
private transformation: Transformation;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
svg: string,
|
||||||
|
transformation: Transformation = {
|
||||||
|
viewPortIncreasePercent: undefined,
|
||||||
|
backgroundColor: undefined,
|
||||||
|
foregroundColor: undefined,
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
this.svg = svg;
|
||||||
|
this.transformation = transformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public with = (newTransformation: Partial<Transformation>) =>
|
||||||
|
new SvgIcon(this.svg, { ...this.transformation, ...newTransformation });
|
||||||
|
|
||||||
|
public toString = () => {
|
||||||
|
const xml = libxmljs.parseXmlString(this.svg, {
|
||||||
|
noblanks: true,
|
||||||
|
net: false,
|
||||||
|
});
|
||||||
|
const viewBoxAttr = xml.get("//svg:svg/@viewBox", SVG_NS) as Attribute;
|
||||||
|
let viewBox = new ViewBox(viewBoxAttr.value());
|
||||||
|
if (
|
||||||
|
this.transformation.viewPortIncreasePercent &&
|
||||||
|
this.transformation.viewPortIncreasePercent > 0
|
||||||
|
) {
|
||||||
|
viewBox = viewBox.increasePercent(
|
||||||
|
this.transformation.viewPortIncreasePercent
|
||||||
|
);
|
||||||
|
viewBoxAttr.value(viewBox.toString());
|
||||||
|
}
|
||||||
|
if (this.transformation.backgroundColor) {
|
||||||
|
(xml.get("//svg:svg/*[1]", SVG_NS) as Element).addPrevSibling(
|
||||||
|
new Element(xml, "rect").attr({
|
||||||
|
x: `${viewBox.minX}`,
|
||||||
|
y: `${viewBox.minY}`,
|
||||||
|
width: `${Math.abs(viewBox.minX) + viewBox.width}`,
|
||||||
|
height: `${Math.abs(viewBox.minY) + viewBox.height}`,
|
||||||
|
style: `fill:${this.transformation.backgroundColor}`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.transformation.foregroundColor) {
|
||||||
|
(xml.find("//svg:path", SVG_NS) as Element[]).forEach((path) =>
|
||||||
|
path.attr({ style: `fill:${this.transformation.foregroundColor}` })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return xml.toString();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HOLI_COLORS = ["#06bceb", "#9fc717", "#fbdc10", "#f00b9a", "#fa9705"]
|
||||||
|
|
||||||
|
export const makeFestive = (icon: Icon, clock: Clock = SystemClock): Icon => {
|
||||||
|
const wrap = (
|
||||||
|
icon: Icon,
|
||||||
|
rule: (clock: Clock) => boolean,
|
||||||
|
colors: Pick<Transformation, "backgroundColor" | "foregroundColor">
|
||||||
|
) =>
|
||||||
|
new ColorOverridingIcon(
|
||||||
|
icon,
|
||||||
|
() => rule(clock),
|
||||||
|
() => colors
|
||||||
|
);
|
||||||
|
|
||||||
|
const xmas = wrap(icon, isChristmas, {
|
||||||
|
backgroundColor: "green",
|
||||||
|
foregroundColor: "red",
|
||||||
|
});
|
||||||
|
const randomHoliColors = _.shuffle([...HOLI_COLORS]);
|
||||||
|
const holi = wrap(xmas, isHoli, {
|
||||||
|
backgroundColor: randomHoliColors.pop(),
|
||||||
|
foregroundColor: randomHoliColors.pop(),
|
||||||
|
});
|
||||||
|
const cny = wrap(holi, isCNY, {
|
||||||
|
backgroundColor: "red",
|
||||||
|
foregroundColor: "yellow",
|
||||||
|
});
|
||||||
|
const halloween = wrap(cny, isHalloween, {
|
||||||
|
backgroundColor: "orange",
|
||||||
|
foregroundColor: "black",
|
||||||
|
});
|
||||||
|
return halloween;
|
||||||
|
};
|
||||||
@@ -3,7 +3,6 @@ import express, { Express, Request } from "express";
|
|||||||
import * as Eta from "eta";
|
import * as Eta from "eta";
|
||||||
import morgan from "morgan";
|
import morgan from "morgan";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import scale from "scale-that-svg";
|
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
|
||||||
@@ -29,29 +28,10 @@ import { Clock, SystemClock } from "./clock";
|
|||||||
import { pipe } from "fp-ts/lib/function";
|
import { pipe } from "fp-ts/lib/function";
|
||||||
import { URLBuilder } from "./url_builder";
|
import { URLBuilder } from "./url_builder";
|
||||||
import makeI8N, { asLANGs, KEY, keys as i8nKeys, LANG } from "./i8n";
|
import makeI8N, { asLANGs, KEY, keys as i8nKeys, LANG } from "./i8n";
|
||||||
|
import { SvgIcon, Icon, makeFestive } from "./icon";
|
||||||
|
|
||||||
export const BONOB_ACCESS_TOKEN_HEADER = "bonob-access-token";
|
export const BONOB_ACCESS_TOKEN_HEADER = "bonob-access-token";
|
||||||
|
|
||||||
const icon = (name: string) =>
|
|
||||||
fs
|
|
||||||
.readFileSync(path.resolve(__dirname, "..", "web", "icons", name))
|
|
||||||
.toString();
|
|
||||||
|
|
||||||
export type Icon = { svg: string; size: number };
|
|
||||||
|
|
||||||
export const ICONS: Record<ICON, Icon> = {
|
|
||||||
artists: { svg: icon("navidrome-artists.svg"), size: 24 },
|
|
||||||
albums: { svg: icon("navidrome-all.svg"), size: 24 },
|
|
||||||
playlists: { svg: icon("navidrome-playlists.svg"), size: 24 },
|
|
||||||
genres: { svg: icon("Theatre-Mask-111172.svg"), size: 128 },
|
|
||||||
random: { svg: icon("navidrome-random.svg"), size: 24 },
|
|
||||||
starred: { svg: icon("navidrome-topRated.svg"), size: 24 },
|
|
||||||
recentlyAdded: { svg: icon("navidrome-recentlyAdded.svg"), size: 24 },
|
|
||||||
recentlyPlayed: { svg: icon("navidrome-recentlyPlayed.svg"), size: 24 },
|
|
||||||
mostPlayed: { svg: icon("navidrome-mostPlayed.svg"), size: 24 },
|
|
||||||
discover: { svg: icon("Binoculars-14310.svg"), size: 32 },
|
|
||||||
};
|
|
||||||
|
|
||||||
interface RangeFilter extends Transform {
|
interface RangeFilter extends Transform {
|
||||||
range: (length: number) => string;
|
range: (length: number) => string;
|
||||||
}
|
}
|
||||||
@@ -97,6 +77,10 @@ function server(
|
|||||||
linkCodes: LinkCodes = new InMemoryLinkCodes(),
|
linkCodes: LinkCodes = new InMemoryLinkCodes(),
|
||||||
accessTokens: AccessTokens = new AccessTokenPerAuthToken(),
|
accessTokens: AccessTokens = new AccessTokenPerAuthToken(),
|
||||||
clock: Clock = SystemClock,
|
clock: Clock = SystemClock,
|
||||||
|
iconColors: {
|
||||||
|
foregroundColor: string | undefined;
|
||||||
|
backgroundColor: string | undefined;
|
||||||
|
} = { foregroundColor: undefined, backgroundColor: undefined },
|
||||||
applyContextPath = true
|
applyContextPath = true
|
||||||
): Express {
|
): Express {
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -119,6 +103,27 @@ function server(
|
|||||||
return i8n(...asLANGs(req.headers["accept-language"]));
|
return i8n(...asLANGs(req.headers["accept-language"]));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const iconFrom = (name: string) =>
|
||||||
|
makeFestive(
|
||||||
|
new SvgIcon(
|
||||||
|
fs.readFileSync(path.resolve(__dirname, "..", "web", "icons", name)).toString()
|
||||||
|
).with({ viewPortIncreasePercent: 50, ...iconColors }),
|
||||||
|
clock
|
||||||
|
);
|
||||||
|
|
||||||
|
const ICONS: Record<ICON, Icon> = {
|
||||||
|
artists: iconFrom("navidrome-artists.svg"),
|
||||||
|
albums: iconFrom("navidrome-all.svg"),
|
||||||
|
playlists: iconFrom("navidrome-playlists.svg"),
|
||||||
|
genres: iconFrom("Theatre-Mask-111172.svg"),
|
||||||
|
random: iconFrom("navidrome-random.svg"),
|
||||||
|
starred: iconFrom("navidrome-topRated.svg"),
|
||||||
|
recentlyAdded: iconFrom("navidrome-recentlyAdded.svg"),
|
||||||
|
recentlyPlayed: iconFrom("navidrome-recentlyPlayed.svg"),
|
||||||
|
mostPlayed: iconFrom("navidrome-mostPlayed.svg"),
|
||||||
|
discover: iconFrom("Binoculars-14310.svg"),
|
||||||
|
};
|
||||||
|
|
||||||
app.get("/", (req, res) => {
|
app.get("/", (req, res) => {
|
||||||
const lang = langFor(req);
|
const lang = langFor(req);
|
||||||
Promise.all([sonos.devices(), sonos.services()]).then(
|
Promise.all([sonos.devices(), sonos.services()]).then(
|
||||||
@@ -391,20 +396,17 @@ function server(
|
|||||||
const spec =
|
const spec =
|
||||||
size == "legacy"
|
size == "legacy"
|
||||||
? {
|
? {
|
||||||
outputSize: 80,
|
|
||||||
mimeType: "image/png",
|
mimeType: "image/png",
|
||||||
responseFormatter: (svg: string): Promise<Buffer | string> =>
|
responseFormatter: (svg: string): Promise<Buffer | string> =>
|
||||||
sharp(Buffer.from(svg)).png().toBuffer(),
|
sharp(Buffer.from(svg)).resize(80).png().toBuffer(),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
outputSize: Number.parseInt(size),
|
|
||||||
mimeType: "image/svg+xml",
|
mimeType: "image/svg+xml",
|
||||||
responseFormatter: (svg: string): Promise<Buffer | string> =>
|
responseFormatter: (svg: string): Promise<Buffer | string> =>
|
||||||
Promise.resolve(svg),
|
Promise.resolve(svg),
|
||||||
};
|
};
|
||||||
|
|
||||||
return Promise.resolve(icon.svg)
|
return Promise.resolve(icon.toString())
|
||||||
.then((svg) => scale(svg, { scale: spec.outputSize / icon.size }))
|
|
||||||
.then(spec.responseFormatter)
|
.then(spec.responseFormatter)
|
||||||
.then((data) => res.status(200).type(spec.mimeType).send(data));
|
.then((data) => res.status(200).type(spec.mimeType).send(data));
|
||||||
}
|
}
|
||||||
@@ -437,9 +439,9 @@ function server(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e: Error) => {
|
.catch((e: Error) => {
|
||||||
logger.error(
|
logger.error(`Failed fetching image ${type}/${id}/size/${size}`, {
|
||||||
`Failed fetching image ${type}/${id}/size/${size}`, { cause: e }
|
cause: e,
|
||||||
);
|
});
|
||||||
return res.status(500).send();
|
return res.status(500).send();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
58
tests/clock.test.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
import { isChristmas, isCNY, isHalloween, isHoli } from "../src/clock";
|
||||||
|
|
||||||
|
describe("isChristmas", () => {
|
||||||
|
["2000/12/25", "2022/12/25", "2030/12/25"].forEach((date) => {
|
||||||
|
it(`should return true for ${date} regardless of year`, () => {
|
||||||
|
expect(isChristmas({ now: () => dayjs(date) })).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
["2000/12/24", "2000/12/26", "2021/01/01"].forEach((date) => {
|
||||||
|
it(`should return false for ${date} regardless of year`, () => {
|
||||||
|
expect(isChristmas({ now: () => dayjs(date) })).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isHalloween", () => {
|
||||||
|
["2000/10/31", "2022/10/31", "2030/10/31"].forEach((date) => {
|
||||||
|
it(`should return true for ${date} regardless of year`, () => {
|
||||||
|
expect(isHalloween({ now: () => dayjs(date) })).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
["2000/09/31", "2000/10/30", "2021/01/01"].forEach((date) => {
|
||||||
|
it(`should return false for ${date} regardless of year`, () => {
|
||||||
|
expect(isHalloween({ now: () => dayjs(date) })).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isHoli", () => {
|
||||||
|
["2022/03/18", "2023/03/07", "2024/03/25", "2025/03/14"].forEach((date) => {
|
||||||
|
it(`should return true for ${date} regardless of year`, () => {
|
||||||
|
expect(isHoli({ now: () => dayjs(date) })).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
["2000/09/31", "2000/10/30", "2021/01/01"].forEach((date) => {
|
||||||
|
it(`should return false for ${date} regardless of year`, () => {
|
||||||
|
expect(isHoli({ now: () => dayjs(date) })).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isCNY", () => {
|
||||||
|
["2022/02/01", "2023/01/22", "2024/02/10", "2025/02/29"].forEach((date) => {
|
||||||
|
it(`should return true for ${date} regardless of year`, () => {
|
||||||
|
expect(isCNY({ now: () => dayjs(date) })).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
["2000/09/31", "2000/10/30", "2021/01/01"].forEach((date) => {
|
||||||
|
it(`should return false for ${date} regardless of year`, () => {
|
||||||
|
expect(isCNY({ now: () => dayjs(date) })).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -107,6 +107,66 @@ describe("config", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("icons", () => {
|
||||||
|
describe("foregroundColor", () => {
|
||||||
|
describe("when BONOB_ICON_FOREGROUND_COLOR is not specified", () => {
|
||||||
|
it(`should default to undefined`, () => {
|
||||||
|
expect(config().icons.foregroundColor).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when BONOB_ICON_FOREGROUND_COLOR is ''", () => {
|
||||||
|
it(`should default to undefined`, () => {
|
||||||
|
process.env["BONOB_ICON_FOREGROUND_COLOR"] = "";
|
||||||
|
expect(config().icons.foregroundColor).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when BONOB_ICON_FOREGROUND_COLOR is specified", () => {
|
||||||
|
it(`should use it`, () => {
|
||||||
|
process.env["BONOB_ICON_FOREGROUND_COLOR"] = "pink";
|
||||||
|
expect(config().icons.foregroundColor).toEqual("pink");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when BONOB_ICON_FOREGROUND_COLOR is an invalid string", () => {
|
||||||
|
it(`should blow up`, () => {
|
||||||
|
process.env["BONOB_ICON_FOREGROUND_COLOR"] = "#dfasd";
|
||||||
|
expect(() => config()).toThrow("Invalid color specified for BONOB_ICON_FOREGROUND_COLOR")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("backgroundColor", () => {
|
||||||
|
describe("when BONOB_ICON_BACKGROUND_COLOR is not specified", () => {
|
||||||
|
it(`should default to undefined`, () => {
|
||||||
|
expect(config().icons.backgroundColor).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when BONOB_ICON_BACKGROUND_COLOR is ''", () => {
|
||||||
|
it(`should default to undefined`, () => {
|
||||||
|
process.env["BONOB_ICON_BACKGROUND_COLOR"] = "";
|
||||||
|
expect(config().icons.backgroundColor).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when BONOB_ICON_BACKGROUND_COLOR is specified", () => {
|
||||||
|
it(`should use it`, () => {
|
||||||
|
process.env["BONOB_ICON_BACKGROUND_COLOR"] = "blue";
|
||||||
|
expect(config().icons.backgroundColor).toEqual("blue");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when BONOB_ICON_BACKGROUND_COLOR is an invalid string", () => {
|
||||||
|
it(`should blow up`, () => {
|
||||||
|
process.env["BONOB_ICON_BACKGROUND_COLOR"] = "#red";
|
||||||
|
expect(() => config()).toThrow("Invalid color specified for BONOB_ICON_BACKGROUND_COLOR")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("secret", () => {
|
describe("secret", () => {
|
||||||
it("should default to bonob", () => {
|
it("should default to bonob", () => {
|
||||||
expect(config().secret).toEqual("bonob");
|
expect(config().secret).toEqual("bonob");
|
||||||
|
|||||||
429
tests/icon.test.ts
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
import libxmljs from "libxmljs2";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ColorOverridingIcon,
|
||||||
|
HOLI_COLORS,
|
||||||
|
Icon,
|
||||||
|
makeFestive,
|
||||||
|
SvgIcon,
|
||||||
|
Transformation,
|
||||||
|
} from "../src/icon";
|
||||||
|
|
||||||
|
describe("SvgIcon", () => {
|
||||||
|
const xmlTidy = (xml: string) =>
|
||||||
|
libxmljs.parseXmlString(xml, { noblanks: true, net: false }).toString();
|
||||||
|
|
||||||
|
const svgIcon24 = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="something1"/>
|
||||||
|
<path d="something2"/>
|
||||||
|
<path d="something3"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const svgIcon128 = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||||
|
<path d="something1"/>
|
||||||
|
<path d="something2"/>
|
||||||
|
<path d="something3"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe("with no transformation", () => {
|
||||||
|
it("should be the same", () => {
|
||||||
|
expect(new SvgIcon(svgIcon24).toString()).toEqual(xmlTidy(svgIcon24));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with a view port increase", () => {
|
||||||
|
describe("of 50%", () => {
|
||||||
|
describe("when the viewPort is of size 0 0 24 24", () => {
|
||||||
|
it("should resize the viewPort", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon24)
|
||||||
|
.with({ viewPortIncreasePercent: 50 })
|
||||||
|
.toString()
|
||||||
|
).toEqual(
|
||||||
|
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32">
|
||||||
|
<path d="something1"/>
|
||||||
|
<path d="something2"/>
|
||||||
|
<path d="something3"/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("when the viewPort is of size 0 0 128 128", () => {
|
||||||
|
it("should resize the viewPort", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon128)
|
||||||
|
.with({ viewPortIncreasePercent: 50 })
|
||||||
|
.toString()
|
||||||
|
).toEqual(
|
||||||
|
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-21 -21 170 170">
|
||||||
|
<path d="something1"/>
|
||||||
|
<path d="something2"/>
|
||||||
|
<path d="something3"/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("of 0%", () => {
|
||||||
|
it("should do nothing", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon24).with({ viewPortIncreasePercent: 0 }).toString()
|
||||||
|
).toEqual(xmlTidy(svgIcon24));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("background color", () => {
|
||||||
|
describe("with no viewPort increase", () => {
|
||||||
|
it("should add a rectangle the same size as the original viewPort", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon24).with({ backgroundColor: "red" }).toString()
|
||||||
|
).toEqual(
|
||||||
|
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<rect x="0" y="0" width="24" height="24" style="fill:red"/>
|
||||||
|
<path d="something1"/>
|
||||||
|
<path d="something2"/>
|
||||||
|
<path d="something3"/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with a viewPort increase", () => {
|
||||||
|
it("should add a rectangle the same size as the original viewPort", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon24)
|
||||||
|
.with({ backgroundColor: "pink", viewPortIncreasePercent: 50 })
|
||||||
|
.toString()
|
||||||
|
).toEqual(
|
||||||
|
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32">
|
||||||
|
<rect x="-4" y="-4" width="36" height="36" style="fill:pink"/>
|
||||||
|
<path d="something1"/>
|
||||||
|
<path d="something2"/>
|
||||||
|
<path d="something3"/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("of undefined", () => {
|
||||||
|
it("should not do anything", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon24)
|
||||||
|
.with({ backgroundColor: undefined })
|
||||||
|
.toString()
|
||||||
|
).toEqual(
|
||||||
|
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="something1"/>
|
||||||
|
<path d="something2"/>
|
||||||
|
<path d="something3"/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("multiple times", () => {
|
||||||
|
it("should use the most recent", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon24)
|
||||||
|
.with({ backgroundColor: "green" })
|
||||||
|
.with({ backgroundColor: "red" })
|
||||||
|
.toString()
|
||||||
|
).toEqual(
|
||||||
|
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<rect x="0" y="0" width="24" height="24" style="fill:red"/>
|
||||||
|
<path d="something1"/>
|
||||||
|
<path d="something2"/>
|
||||||
|
<path d="something3"/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("foreground color", () => {
|
||||||
|
describe("with no viewPort increase", () => {
|
||||||
|
it("should add a rectangle the same size as the original viewPort", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon24).with({ foregroundColor: "red" }).toString()
|
||||||
|
).toEqual(
|
||||||
|
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="something1" style="fill:red"/>
|
||||||
|
<path d="something2" style="fill:red"/>
|
||||||
|
<path d="something3" style="fill:red"/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with a viewPort increase", () => {
|
||||||
|
it("should add a rectangle the same size as the original viewPort", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon24)
|
||||||
|
.with({ foregroundColor: "pink", viewPortIncreasePercent: 50 })
|
||||||
|
.toString()
|
||||||
|
).toEqual(
|
||||||
|
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32">
|
||||||
|
<path d="something1" style="fill:pink"/>
|
||||||
|
<path d="something2" style="fill:pink"/>
|
||||||
|
<path d="something3" style="fill:pink"/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("of undefined", () => {
|
||||||
|
it("should not do anything", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon24)
|
||||||
|
.with({ foregroundColor: undefined })
|
||||||
|
.toString()
|
||||||
|
).toEqual(
|
||||||
|
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="something1"/>
|
||||||
|
<path d="something2"/>
|
||||||
|
<path d="something3"/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("mutliple times", () => {
|
||||||
|
it("should use the most recent", () => {
|
||||||
|
expect(
|
||||||
|
new SvgIcon(svgIcon24)
|
||||||
|
.with({ foregroundColor: "blue" })
|
||||||
|
.with({ foregroundColor: "red" })
|
||||||
|
.toString()
|
||||||
|
).toEqual(
|
||||||
|
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="something1" style="fill:red"/>
|
||||||
|
<path d="something2" style="fill:red"/>
|
||||||
|
<path d="something3" style="fill:red"/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class DummyIcon implements Icon {
|
||||||
|
transformation: Partial<Transformation>;
|
||||||
|
constructor(transformation: Partial<Transformation>) {
|
||||||
|
this.transformation = transformation;
|
||||||
|
}
|
||||||
|
public with = (newTransformation: Partial<Transformation>) =>
|
||||||
|
new DummyIcon({ ...this.transformation, ...newTransformation });
|
||||||
|
|
||||||
|
public toString = () => JSON.stringify(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("ColorOverridingIcon", () => {
|
||||||
|
describe("when the rule matches", () => {
|
||||||
|
const icon = new DummyIcon({
|
||||||
|
backgroundColor: "black",
|
||||||
|
foregroundColor: "black",
|
||||||
|
});
|
||||||
|
const overriding = new ColorOverridingIcon(
|
||||||
|
icon,
|
||||||
|
() => true,
|
||||||
|
() => ({ backgroundColor: "blue", foregroundColor: "red" })
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("with", () => {
|
||||||
|
it("should be the with of the underlieing icon with the overriden colors", () => {
|
||||||
|
const result = overriding.with({
|
||||||
|
viewPortIncreasePercent: 99,
|
||||||
|
backgroundColor: "shouldBeIgnored",
|
||||||
|
foregroundColor: "shouldBeIgnored",
|
||||||
|
}) as DummyIcon;
|
||||||
|
|
||||||
|
expect(result.transformation).toEqual({
|
||||||
|
viewPortIncreasePercent: 99,
|
||||||
|
backgroundColor: "blue",
|
||||||
|
foregroundColor: "red",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("toString", () => {
|
||||||
|
it("should be the toString of the underlieing icon with the overriden colors", () => {
|
||||||
|
expect(overriding.toString()).toEqual(
|
||||||
|
new DummyIcon({
|
||||||
|
backgroundColor: "blue",
|
||||||
|
foregroundColor: "red",
|
||||||
|
}).toString()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the rule doesnt match", () => {
|
||||||
|
const icon = new DummyIcon({
|
||||||
|
backgroundColor: "black",
|
||||||
|
foregroundColor: "black",
|
||||||
|
});
|
||||||
|
const overriding = new ColorOverridingIcon(
|
||||||
|
icon,
|
||||||
|
() => false,
|
||||||
|
() => ({ backgroundColor: "blue", foregroundColor: "red" })
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("with", () => {
|
||||||
|
it("should use the provided transformation", () => {
|
||||||
|
const result = overriding.with({
|
||||||
|
viewPortIncreasePercent: 88,
|
||||||
|
backgroundColor: "shouldBeUsed",
|
||||||
|
foregroundColor: "shouldBeUsed",
|
||||||
|
}) as DummyIcon;
|
||||||
|
|
||||||
|
expect(result.transformation).toEqual({
|
||||||
|
viewPortIncreasePercent: 88,
|
||||||
|
backgroundColor: "shouldBeUsed",
|
||||||
|
foregroundColor: "shouldBeUsed",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("toString", () => {
|
||||||
|
it("should be the toString of the unchanged icon", () => {
|
||||||
|
expect(overriding.toString()).toEqual(icon.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("makeFestive", () => {
|
||||||
|
const icon = new DummyIcon({
|
||||||
|
backgroundColor: "black",
|
||||||
|
foregroundColor: "black",
|
||||||
|
});
|
||||||
|
let now = dayjs();
|
||||||
|
|
||||||
|
const festiveIcon = makeFestive(icon, { now: () => now })
|
||||||
|
|
||||||
|
describe("on a non special day", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
now = dayjs("2022/10/12");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use the given colors", () => {
|
||||||
|
const result = festiveIcon.with({
|
||||||
|
viewPortIncreasePercent: 88,
|
||||||
|
backgroundColor: "shouldBeUsed",
|
||||||
|
foregroundColor: "shouldBeUsed",
|
||||||
|
}) as DummyIcon;
|
||||||
|
|
||||||
|
expect(result.transformation).toEqual({
|
||||||
|
viewPortIncreasePercent: 88,
|
||||||
|
backgroundColor: "shouldBeUsed",
|
||||||
|
foregroundColor: "shouldBeUsed",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("on christmas day", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
now = dayjs("2022/12/25");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use the given colors", () => {
|
||||||
|
const result = festiveIcon.with({
|
||||||
|
viewPortIncreasePercent: 25,
|
||||||
|
backgroundColor: "shouldNotBeUsed",
|
||||||
|
foregroundColor: "shouldNotBeUsed",
|
||||||
|
}) as DummyIcon;
|
||||||
|
|
||||||
|
expect(result.transformation).toEqual({
|
||||||
|
viewPortIncreasePercent: 25,
|
||||||
|
backgroundColor: "green",
|
||||||
|
foregroundColor: "red",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("on halloween", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
now = dayjs("2022/10/31");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use the given colors", () => {
|
||||||
|
const result = festiveIcon.with({
|
||||||
|
viewPortIncreasePercent: 12,
|
||||||
|
backgroundColor: "shouldNotBeUsed",
|
||||||
|
foregroundColor: "shouldNotBeUsed",
|
||||||
|
}) as DummyIcon;
|
||||||
|
|
||||||
|
expect(result.transformation).toEqual({
|
||||||
|
viewPortIncreasePercent: 12,
|
||||||
|
backgroundColor: "orange",
|
||||||
|
foregroundColor: "black",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("on cny", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
now = dayjs("2022/02/01");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use the given colors", () => {
|
||||||
|
const result = festiveIcon.with({
|
||||||
|
viewPortIncreasePercent: 12,
|
||||||
|
backgroundColor: "shouldNotBeUsed",
|
||||||
|
foregroundColor: "shouldNotBeUsed",
|
||||||
|
}) as DummyIcon;
|
||||||
|
|
||||||
|
expect(result.transformation).toEqual({
|
||||||
|
viewPortIncreasePercent: 12,
|
||||||
|
backgroundColor: "red",
|
||||||
|
foregroundColor: "yellow",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("on holi", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
now = dayjs("2022/03/18");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use the given colors", () => {
|
||||||
|
const result = festiveIcon.with({
|
||||||
|
viewPortIncreasePercent: 12,
|
||||||
|
backgroundColor: "shouldNotBeUsed",
|
||||||
|
foregroundColor: "shouldNotBeUsed",
|
||||||
|
}) as DummyIcon;
|
||||||
|
|
||||||
|
expect(result.transformation.viewPortIncreasePercent).toEqual(12);
|
||||||
|
expect(HOLI_COLORS.includes(result.transformation.backgroundColor!)).toEqual(true);
|
||||||
|
expect(HOLI_COLORS.includes(result.transformation.foregroundColor!)).toEqual(true);
|
||||||
|
expect(result.transformation.backgroundColor).not.toEqual(result.transformation.foregroundColor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,7 +6,6 @@ import Image from "image-js";
|
|||||||
import { MusicService } from "../src/music_service";
|
import { MusicService } from "../src/music_service";
|
||||||
import makeServer, {
|
import makeServer, {
|
||||||
BONOB_ACCESS_TOKEN_HEADER,
|
BONOB_ACCESS_TOKEN_HEADER,
|
||||||
ICONS,
|
|
||||||
RangeBytesFromFilter,
|
RangeBytesFromFilter,
|
||||||
rangeFilterFor,
|
rangeFilterFor,
|
||||||
} from "../src/server";
|
} from "../src/server";
|
||||||
@@ -21,9 +20,8 @@ import { Response } from "express";
|
|||||||
import { Transform } from "stream";
|
import { Transform } from "stream";
|
||||||
import url from "../src/url_builder";
|
import url from "../src/url_builder";
|
||||||
import i8n, { randomLang } from "../src/i8n";
|
import i8n, { randomLang } from "../src/i8n";
|
||||||
import {
|
import { SONOS_RECOMMENDED_IMAGE_SIZES } from "../src/smapi";
|
||||||
SONOS_RECOMMENDED_IMAGE_SIZES,
|
import { Clock, SystemClock } from "../src/clock";
|
||||||
} from "../src/smapi";
|
|
||||||
|
|
||||||
describe("rangeFilterFor", () => {
|
describe("rangeFilterFor", () => {
|
||||||
describe("invalid range header string", () => {
|
describe("invalid range header string", () => {
|
||||||
@@ -1264,14 +1262,23 @@ describe("server", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("/icon", () => {
|
describe("/icon", () => {
|
||||||
const server = makeServer(
|
const server = (
|
||||||
jest.fn() as unknown as Sonos,
|
clock: Clock = SystemClock,
|
||||||
aService(),
|
iconColors: {
|
||||||
url("http://localhost:1234"),
|
foregroundColor: string | undefined;
|
||||||
jest.fn() as unknown as MusicService,
|
backgroundColor: string | undefined;
|
||||||
new InMemoryLinkCodes(),
|
} = { foregroundColor: undefined, backgroundColor: undefined }
|
||||||
jest.fn() as unknown as AccessTokens
|
) =>
|
||||||
);
|
makeServer(
|
||||||
|
jest.fn() as unknown as Sonos,
|
||||||
|
aService(),
|
||||||
|
url("http://localhost:1234"),
|
||||||
|
jest.fn() as unknown as MusicService,
|
||||||
|
new InMemoryLinkCodes(),
|
||||||
|
jest.fn() as unknown as AccessTokens,
|
||||||
|
clock,
|
||||||
|
iconColors
|
||||||
|
);
|
||||||
|
|
||||||
describe("invalid icon names", () => {
|
describe("invalid icon names", () => {
|
||||||
[
|
[
|
||||||
@@ -1286,7 +1293,7 @@ describe("server", () => {
|
|||||||
].forEach((type) => {
|
].forEach((type) => {
|
||||||
describe(`trying to retrieve an icon with name ${type}`, () => {
|
describe(`trying to retrieve an icon with name ${type}`, () => {
|
||||||
it(`should fail`, async () => {
|
it(`should fail`, async () => {
|
||||||
const response = await request(server).get(
|
const response = await request(server()).get(
|
||||||
`/icon/${type}/size/legacy`
|
`/icon/${type}/size/legacy`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1300,7 +1307,7 @@ describe("server", () => {
|
|||||||
["-1", "0", "59", "foo"].forEach((size) => {
|
["-1", "0", "59", "foo"].forEach((size) => {
|
||||||
describe(`trying to retrieve an icon with size ${size}`, () => {
|
describe(`trying to retrieve an icon with size ${size}`, () => {
|
||||||
it(`should fail`, async () => {
|
it(`should fail`, async () => {
|
||||||
const response = await request(server).get(
|
const response = await request(server()).get(
|
||||||
`/icon/artists/size/${size}`
|
`/icon/artists/size/${size}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1311,11 +1318,22 @@ describe("server", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("fetching", () => {
|
describe("fetching", () => {
|
||||||
Object.keys(ICONS).forEach((type) => {
|
[
|
||||||
|
"artists",
|
||||||
|
"albums",
|
||||||
|
"playlists",
|
||||||
|
"genres",
|
||||||
|
"random",
|
||||||
|
"starred",
|
||||||
|
"recentlyAdded",
|
||||||
|
"recentlyPlayed",
|
||||||
|
"mostPlayed",
|
||||||
|
"discover",
|
||||||
|
].forEach((type) => {
|
||||||
describe(`type=${type}`, () => {
|
describe(`type=${type}`, () => {
|
||||||
describe(`legacy icon`, () => {
|
describe(`legacy icon`, () => {
|
||||||
it("should return the png image", async () => {
|
it("should return the png image", async () => {
|
||||||
const response = await request(server).get(
|
const response = await request(server()).get(
|
||||||
`/icon/${type}/size/legacy`
|
`/icon/${type}/size/legacy`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1330,7 +1348,7 @@ describe("server", () => {
|
|||||||
describe("svg icon", () => {
|
describe("svg icon", () => {
|
||||||
SONOS_RECOMMENDED_IMAGE_SIZES.forEach((size) => {
|
SONOS_RECOMMENDED_IMAGE_SIZES.forEach((size) => {
|
||||||
it(`should return an svg image for size = ${size}`, async () => {
|
it(`should return an svg image for size = ${size}`, async () => {
|
||||||
const response = await request(server).get(
|
const response = await request(server()).get(
|
||||||
`/icon/${type}/size/${size}`
|
`/icon/${type}/size/${size}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1339,12 +1357,33 @@ describe("server", () => {
|
|||||||
"image/svg+xml; charset=utf-8"
|
"image/svg+xml; charset=utf-8"
|
||||||
);
|
);
|
||||||
const svg = Buffer.from(response.body).toString();
|
const svg = Buffer.from(response.body).toString();
|
||||||
expect(svg).toContain(`viewBox="0 0 ${size} ${size}"`);
|
|
||||||
expect(svg).toContain(
|
expect(svg).toContain(
|
||||||
` xmlns="http://www.w3.org/2000/svg" `
|
` xmlns="http://www.w3.org/2000/svg" `
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should return icon colors as per config if overriden", async () => {
|
||||||
|
const response = await request(server(SystemClock, { foregroundColor: 'brightblue', backgroundColor: 'brightpink' })).get(
|
||||||
|
`/icon/${type}/size/180`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200);
|
||||||
|
const svg = Buffer.from(response.body).toString();
|
||||||
|
expect(svg).toContain(`fill:brightblue`);
|
||||||
|
expect(svg).toContain(`fill:brightpink`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a christmas icon on christmas day", async () => {
|
||||||
|
const response = await request(server({ now: () => dayjs("2022/12/25") })).get(
|
||||||
|
`/icon/${type}/size/180`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200);
|
||||||
|
const svg = Buffer.from(response.body).toString();
|
||||||
|
expect(svg).toContain(`fill:red`);
|
||||||
|
expect(svg).toContain(`fill:green`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2" d="M7 15A6 6 0 1 0 7 27 6 6 0 1 0 7 15zM25 15A6 6 0 1 0 25 27 6 6 0 1 0 25 15zM16 18A2 2 0 1 0 16 22 2 2 0 1 0 16 18z"/><path fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2" d="M30.5,18.6l-2.3-6.4C27.5,10.5,25.9,9.2,24,9v0c0-1.7-1.3-3-3-3s-3,1.3-3,3h-4c0-1.7-1.3-3-3-3S8,7.3,8,9v0c-1.9,0.2-3.5,1.4-4.2,3.3l-2.3,6.4"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<path stroke-miterlimit="10" stroke-width="2" d="M7 15A6 6 0 1 0 7 27 6 6 0 1 0 7 15zM25 15A6 6 0 1 0 25 27 6 6 0 1 0 25 15zM16 18A2 2 0 1 0 16 22 2 2 0 1 0 16 18z"/>
|
||||||
|
<path stroke-miterlimit="10" stroke-width="2" d="M30.5,18.6l-2.3-6.4C27.5,10.5,25.9,9.2,24,9v0c0-1.7-1.3-3-3-3s-3,1.3-3,3h-4c0-1.7-1.3-3-3-3S8,7.3,8,9v0c-1.9,0.2-3.5,1.4-4.2,3.3l-2.3,6.4"/>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 473 B After Width: | Height: | Size: 428 B |
@@ -1 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><path d="M64.9,98c-0.2,0.2-0.4,0.3-0.7,0.5l-0.1-0.1c-3.7-3-7-6.4-9.9-10l-12.6,4.6c-2.3-6.3-0.1-13.1,4.9-17l0,0c-3.8-8.2-5.9-17.3-5.9-26.7v-16c0.3-0.1,0.5-0.2,0.8-0.2c1.6-0.4,2.5-2.1,2.1-3.7c-0.4-1.6-2.1-2.5-3.7-2.1C26.2,30.9,14.2,38.6,5.1,49.4C4.2,50.5,4,52,4.5,53.3l6.9,19c5.8,16,18.4,28.4,34.6,33.9c2.6,0.9,5.3,1.3,8,1.3c5.1,0,10.2-1.6,14.5-4.7c1.3-1,1.6-2.8,0.7-4.2C68.1,97.4,66.2,97,64.9,98z M34.5,61.5L26,64.6c-1.6,0.6-3.3-0.2-3.8-1.8c-0.6-1.6,0.2-3.3,1.8-3.8l8.5-3.1c1.6-0.6,3.3,0.2,3.8,1.8S36,60.9,34.5,61.5z"/><path d="M118.8,25.9l-0.5-0.3c-21.2-12.2-47.5-12.2-68.8,0c0,0,0,0,0,0c-1.2,0.7-2,2-2,3.4v20.3c0,17.1,7.6,33,20.9,43.7c4.5,3.6,10,5.4,15.5,5.4s11-1.8,15.5-5.4c13.3-10.7,20.9-26.6,20.9-43.7V28.5C120.3,27.4,119.8,26.4,118.8,25.9z M84,79v5.9c0,3.2,1.8,5.7,4.2,7c-5.5,1.3-11.4,0.1-15.9-3.6c-11.8-9.6-18.6-23.8-18.6-39v-19c9.4-5.2,19.9-7.8,30.3-7.8V64h15C99,72.3,92.3,79,84,79z M103.1,47h-9c-1.7,0-3-1.3-3-3s1.3-3,3-3h9c1.7,0,3,1.3,3,3S104.7,47,103.1,47z"/><path d="M84 79l0-15H69C69 72.3 75.7 79 84 79zM76.1 44c0-1.7-1.3-3-3-3h-9c-1.7 0-3 1.3-3 3s1.3 3 3 3h9C74.7 47 76.1 45.7 76.1 44z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||||
|
<path d="M64.9,98c-0.2,0.2-0.4,0.3-0.7,0.5l-0.1-0.1c-3.7-3-7-6.4-9.9-10l-12.6,4.6c-2.3-6.3-0.1-13.1,4.9-17l0,0c-3.8-8.2-5.9-17.3-5.9-26.7v-16c0.3-0.1,0.5-0.2,0.8-0.2c1.6-0.4,2.5-2.1,2.1-3.7c-0.4-1.6-2.1-2.5-3.7-2.1C26.2,30.9,14.2,38.6,5.1,49.4C4.2,50.5,4,52,4.5,53.3l6.9,19c5.8,16,18.4,28.4,34.6,33.9c2.6,0.9,5.3,1.3,8,1.3c5.1,0,10.2-1.6,14.5-4.7c1.3-1,1.6-2.8,0.7-4.2C68.1,97.4,66.2,97,64.9,98z M34.5,61.5L26,64.6c-1.6,0.6-3.3-0.2-3.8-1.8c-0.6-1.6,0.2-3.3,1.8-3.8l8.5-3.1c1.6-0.6,3.3,0.2,3.8,1.8S36,60.9,34.5,61.5z"/>
|
||||||
|
<path d="M118.8,25.9l-0.5-0.3c-21.2-12.2-47.5-12.2-68.8,0c0,0,0,0,0,0c-1.2,0.7-2,2-2,3.4v20.3c0,17.1,7.6,33,20.9,43.7c4.5,3.6,10,5.4,15.5,5.4s11-1.8,15.5-5.4c13.3-10.7,20.9-26.6,20.9-43.7V28.5C120.3,27.4,119.8,26.4,118.8,25.9z M84,79v5.9c0,3.2,1.8,5.7,4.2,7c-5.5,1.3-11.4,0.1-15.9-3.6c-11.8-9.6-18.6-23.8-18.6-39v-19c9.4-5.2,19.9-7.8,30.3-7.8V64h15C99,72.3,92.3,79,84,79z M103.1,47h-9c-1.7,0-3-1.3-3-3s1.3-3,3-3h9c1.7,0,3,1.3,3,3S104.7,47,103.1,47z"/>
|
||||||
|
<path d="M84 79l0-15H69C69 72.3 75.7 79 84 79zM76.1 44c0-1.7-1.3-3-3-3h-9c-1.7 0-3 1.3-3 3s1.3 3 3 3h9C74.7 47 76.1 45.7 76.1 44z"/>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
33
web/icons/index.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
original<br>
|
||||||
|
<div style="width:100px; height:100px; border: 1px; border-style: solid;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" data-testid="icon">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" style="fill:lightgrey" />
|
||||||
|
<path style="fill:white" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-12.5c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5 4.5-2.01 4.5-4.5-2.01-4.5-4.5-4.5zm0 5.5c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
hack 1<br>
|
||||||
|
<div style="width:100px; height:100px; border: 1px; border-style: solid;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="-3 -3 30 30" aria-hidden="true" data-testid="icon">
|
||||||
|
<rect x="-3" y="-3" width="100%" height="100%" style="fill:lightgrey" />
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-12.5c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5 4.5-2.01 4.5-4.5-2.01-4.5-4.5-4.5zm0 5.5c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
hack 2<br>
|
||||||
|
<div style="width:100px; height:100px; border: 1px; border-style: solid;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="-2 -2 28 28" aria-hidden="true" data-testid="icon">
|
||||||
|
<rect x="-2" y="-2" width="100%" height="100%" style="fill:lightgrey" />
|
||||||
|
<path style="fill:white" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-12.5c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5 4.5-2.01 4.5-4.5-2.01-4.5-4.5-4.5zm0 5.5c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
hack 3<br>
|
||||||
|
<div style="width:100px; height:100px; border: 1px; border-style: solid;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="-4 -4 32 32" aria-hidden="true" data-testid="icon">
|
||||||
|
<rect x="-4" y="-4" width="100%" height="100%" style="fill:lightgrey" />
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-12.5c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5 4.5-2.01 4.5-4.5-2.01-4.5-4.5-4.5zm0 5.5c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" data-testid="icon">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-12.5c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5 4.5-2.01 4.5-4.5-2.01-4.5-4.5-4.5zm0 5.5c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"></path>
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-12.5c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5 4.5-2.01 4.5-4.5-2.01-4.5-4.5-4.5zm0 5.5c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 444 B After Width: | Height: | Size: 350 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" data-testid="icon">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"></path>
|
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 418 B After Width: | Height: | Size: 325 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" data-testid="icon">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"></path>
|
<path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 525 B After Width: | Height: | Size: 431 B |
@@ -1,4 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" role="img">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"></path>
|
<path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"></path>
|
||||||
<title>Most Played</title>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 247 B After Width: | Height: | Size: 151 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" data-testid="icon">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M22 6h-5v8.18c-.31-.11-.65-.18-1-.18-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3V8h3V6zm-7 0H3v2h12V6zm0 4H3v2h12v-2zm-4 4H3v2h8v-2zm4 3c0-.55.45-1 1-1s1 .45 1 1-.45 1-1 1-1-.45-1-1z"></path>
|
<path d="M22 6h-5v8.18c-.31-.11-.65-.18-1-.18-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3V8h3V6zm-7 0H3v2h12V6zm0 4H3v2h12v-2zm-4 4H3v2h8v-2zm4 3c0-.55.45-1 1-1s1 .45 1 1-.45 1-1 1-1-.45-1-1z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 266 B |
@@ -1,4 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" role="img">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20 17.96 7.46 20 9.5V4h-5.5zm.33 9.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04 2.04-3.13-3.13z"></path>
|
<path d="M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20 17.96 7.46 20 9.5V4h-5.5zm.33 9.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04 2.04-3.13-3.13z"></path>
|
||||||
<title>Random</title>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 352 B After Width: | Height: | Size: 261 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" data-testid="icon">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12zm-7-2h2v-3h3V9h-3V6h-2v3h-3v2h3z"></path>
|
<path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12zm-7-2h2v-3h3V9h-3V6h-2v3h-3v2h3z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 340 B After Width: | Height: | Size: 246 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" data-testid="icon">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12zM12 5.5v9l6-4.5z"></path>
|
<path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12zM12 5.5v9l6-4.5z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 230 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" data-testid="icon">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M12 3l.01 10.55c-.59-.34-1.27-.55-2-.55C7.79 13 6 14.79 6 17s1.79 4 4.01 4S14 19.21 14 17V7h4V3h-6zm-1.99 16c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path>
|
<path d="M12 3l.01 10.55c-.59-.34-1.27-.55-2-.55C7.79 13 6 14.79 6 17s1.79 4 4.01 4S14 19.21 14 17V7h4V3h-6zm-1.99 16c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 334 B After Width: | Height: | Size: 240 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" data-testid="activeIcon">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path>
|
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 176 B |
193
yarn.lock
@@ -664,6 +664,25 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@mapbox/node-pre-gyp@npm:^1.0.1":
|
||||||
|
version: 1.0.5
|
||||||
|
resolution: "@mapbox/node-pre-gyp@npm:1.0.5"
|
||||||
|
dependencies:
|
||||||
|
detect-libc: ^1.0.3
|
||||||
|
https-proxy-agent: ^5.0.0
|
||||||
|
make-dir: ^3.1.0
|
||||||
|
node-fetch: ^2.6.1
|
||||||
|
nopt: ^5.0.0
|
||||||
|
npmlog: ^4.1.2
|
||||||
|
rimraf: ^3.0.2
|
||||||
|
semver: ^7.3.4
|
||||||
|
tar: ^6.1.0
|
||||||
|
bin:
|
||||||
|
node-pre-gyp: bin/node-pre-gyp
|
||||||
|
checksum: c1f182a707f5782e47b77a76e9d6a073fb043999cf9ad965bc86732e88db27ad00926d1602918edb7105f05cde67871b84a178ee9844eb742319ade419636675
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@npmcli/move-file@npm:^1.0.1":
|
"@npmcli/move-file@npm:^1.0.1":
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
resolution: "@npmcli/move-file@npm:1.1.2"
|
resolution: "@npmcli/move-file@npm:1.1.2"
|
||||||
@@ -1511,6 +1530,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"bindings@npm:~1.5.0":
|
||||||
|
version: 1.5.0
|
||||||
|
resolution: "bindings@npm:1.5.0"
|
||||||
|
dependencies:
|
||||||
|
file-uri-to-path: 1.0.0
|
||||||
|
checksum: 65b6b48095717c2e6105a021a7da4ea435aa8d3d3cd085cb9e85bcb6e5773cf318c4745c3f7c504412855940b585bdf9b918236612a1c7a7942491de176f1ae7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"bl@npm:^4.0.3":
|
"bl@npm:^4.0.3":
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
resolution: "bl@npm:4.1.0"
|
resolution: "bl@npm:4.1.0"
|
||||||
@@ -1571,10 +1599,10 @@ __metadata:
|
|||||||
get-port: ^5.1.1
|
get-port: ^5.1.1
|
||||||
image-js: ^0.32.0
|
image-js: ^0.32.0
|
||||||
jest: ^26.6.3
|
jest: ^26.6.3
|
||||||
|
libxmljs2: ^0.27.0
|
||||||
morgan: ^1.10.0
|
morgan: ^1.10.0
|
||||||
node-html-parser: ^2.1.0
|
node-html-parser: ^2.1.0
|
||||||
nodemon: ^2.0.7
|
nodemon: ^2.0.7
|
||||||
scale-that-svg: ^1.0.5
|
|
||||||
sharp: ^0.27.2
|
sharp: ^0.27.2
|
||||||
soap: ^0.37.0
|
soap: ^0.37.0
|
||||||
supertest: ^6.1.3
|
supertest: ^6.1.3
|
||||||
@@ -1945,17 +1973,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"clean-deep@npm:3.0.2":
|
|
||||||
version: 3.0.2
|
|
||||||
resolution: "clean-deep@npm:3.0.2"
|
|
||||||
dependencies:
|
|
||||||
lodash.isempty: ^4.4.0
|
|
||||||
lodash.isplainobject: ^4.0.6
|
|
||||||
lodash.transform: ^4.6.0
|
|
||||||
checksum: e086672a5dd049bdee7d0fba7ba3bfd61cba5c4ec12de672c2d2954c657d0f113d42a46450a6bb0da70e682271bebbd961fd23359efe6a5f5321192822da4655
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"clean-stack@npm:^2.0.0":
|
"clean-stack@npm:^2.0.0":
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
resolution: "clean-stack@npm:2.2.0"
|
resolution: "clean-stack@npm:2.2.0"
|
||||||
@@ -2412,16 +2429,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"deep-rename-keys@npm:^0.2.1":
|
|
||||||
version: 0.2.1
|
|
||||||
resolution: "deep-rename-keys@npm:0.2.1"
|
|
||||||
dependencies:
|
|
||||||
kind-of: ^3.0.2
|
|
||||||
rename-keys: ^1.1.2
|
|
||||||
checksum: 34c838a7ee375e9579be2ba1de59a5ec5aef46bee96bb08cffdd8b6b3887d941bbfce0caccf3ecd7c5a74e6f3a70c7df0b469e732a97e5849a5149ddc1e2a062
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"deepmerge@npm:^4.2.2":
|
"deepmerge@npm:^4.2.2":
|
||||||
version: 4.2.2
|
version: 4.2.2
|
||||||
resolution: "deepmerge@npm:4.2.2"
|
resolution: "deepmerge@npm:4.2.2"
|
||||||
@@ -2578,13 +2585,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"element-to-path@npm:1.2.0":
|
|
||||||
version: 1.2.0
|
|
||||||
resolution: "element-to-path@npm:1.2.0"
|
|
||||||
checksum: 65c99a2ec3be262323ae94b55c32da643910e25560ca5fc72d5e1b5e0cef241d62827fec29080d7739c66c2395a54e5fb66d8b70eb55aac01fbcbf2606a0b8db
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"emittery@npm:^0.7.1":
|
"emittery@npm:^0.7.1":
|
||||||
version: 0.7.2
|
version: 0.7.2
|
||||||
resolution: "emittery@npm:0.7.2"
|
resolution: "emittery@npm:0.7.2"
|
||||||
@@ -2753,13 +2753,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"eventemitter3@npm:^2.0.0":
|
|
||||||
version: 2.0.3
|
|
||||||
resolution: "eventemitter3@npm:2.0.3"
|
|
||||||
checksum: dfbf4a07144afea0712d8e6a7f30ae91beb7c12c36c3d480818488aafa437d9a331327461f82c12dfd60a4fbad502efc97f684089cda02809988b84a23630752
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"exec-sh@npm:^0.3.2":
|
"exec-sh@npm:^0.3.2":
|
||||||
version: 0.3.6
|
version: 0.3.6
|
||||||
resolution: "exec-sh@npm:0.3.6"
|
resolution: "exec-sh@npm:0.3.6"
|
||||||
@@ -3040,6 +3033,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"file-uri-to-path@npm:1.0.0":
|
||||||
|
version: 1.0.0
|
||||||
|
resolution: "file-uri-to-path@npm:1.0.0"
|
||||||
|
checksum: b648580bdd893a008c92c7ecc96c3ee57a5e7b6c4c18a9a09b44fb5d36d79146f8e442578bc0e173dc027adf3987e254ba1dfd6e3ec998b7c282873010502144
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"fill-range@npm:^4.0.0":
|
"fill-range@npm:^4.0.0":
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
resolution: "fill-range@npm:4.0.0"
|
resolution: "fill-range@npm:4.0.0"
|
||||||
@@ -4116,7 +4116,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"is-plain-object@npm:^2.0.1, is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4":
|
"is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4":
|
||||||
version: 2.0.4
|
version: 2.0.4
|
||||||
resolution: "is-plain-object@npm:2.0.4"
|
resolution: "is-plain-object@npm:2.0.4"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4946,6 +4946,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"libxmljs2@npm:^0.27.0":
|
||||||
|
version: 0.27.0
|
||||||
|
resolution: "libxmljs2@npm:0.27.0"
|
||||||
|
dependencies:
|
||||||
|
"@mapbox/node-pre-gyp": ^1.0.1
|
||||||
|
bindings: ~1.5.0
|
||||||
|
nan: ~2.14.0
|
||||||
|
checksum: 8e575bf6b0a1740c09a455238f12783e37e98d54eeeea5c233777bf02431a7c74df063884a9de1dac689cdcdf48e1c1cf2bad07a869c2ee46b88b8afd5627b6d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"lines-and-columns@npm:^1.1.6":
|
"lines-and-columns@npm:^1.1.6":
|
||||||
version: 1.1.6
|
version: 1.1.6
|
||||||
resolution: "lines-and-columns@npm:1.1.6"
|
resolution: "lines-and-columns@npm:1.1.6"
|
||||||
@@ -4962,27 +4973,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"lodash.isempty@npm:^4.4.0":
|
|
||||||
version: 4.4.0
|
|
||||||
resolution: "lodash.isempty@npm:4.4.0"
|
|
||||||
checksum: a8118f23f7ed72a1dbd176bf27f297d1e71aa1926288449cb8f7cef99ba1bc7527eab52fe7899ab080fa1dc150aba6e4a6367bf49fa4e0b78da1ecc095f8d8c5
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"lodash.isplainobject@npm:^4.0.6":
|
|
||||||
version: 4.0.6
|
|
||||||
resolution: "lodash.isplainobject@npm:4.0.6"
|
|
||||||
checksum: 29c6351f281e0d9a1d58f1a4c8f4400924b4c79f18dfc4613624d7d54784df07efaff97c1ff2659f3e085ecf4fff493300adc4837553104cef2634110b0d5337
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"lodash.transform@npm:^4.6.0":
|
|
||||||
version: 4.6.0
|
|
||||||
resolution: "lodash.transform@npm:4.6.0"
|
|
||||||
checksum: f9d0f583409212e4e94c08c0de1c9e71679e26658d2645be16ee6db55ee2572db5a8395c76f471c00c7d18f3a86c781f7ac51238a7cfa29e9cca253aa0b97149
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"lodash@npm:4.x, lodash@npm:^4.17.19, lodash@npm:^4.17.5, lodash@npm:^4.7.0":
|
"lodash@npm:4.x, lodash@npm:^4.17.19, lodash@npm:^4.17.5, lodash@npm:^4.7.0":
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
resolution: "lodash@npm:4.17.21"
|
resolution: "lodash@npm:4.17.21"
|
||||||
@@ -5026,7 +5016,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"make-dir@npm:^3.0.0":
|
"make-dir@npm:^3.0.0, make-dir@npm:^3.1.0":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "make-dir@npm:3.1.0"
|
resolution: "make-dir@npm:3.1.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5605,6 +5595,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"nan@npm:~2.14.0":
|
||||||
|
version: 2.14.2
|
||||||
|
resolution: "nan@npm:2.14.2"
|
||||||
|
dependencies:
|
||||||
|
node-gyp: latest
|
||||||
|
checksum: 7a269139b66a7d37470effb7fb36a8de8cc3b5ffba6e40bb8e0545307911fe5ebf94797ec62f655ecde79c237d169899f8bd28256c66a32cbc8284faaf94c3f4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"nanomatch@npm:^1.2.9":
|
"nanomatch@npm:^1.2.9":
|
||||||
version: 1.2.13
|
version: 1.2.13
|
||||||
resolution: "nanomatch@npm:1.2.13"
|
resolution: "nanomatch@npm:1.2.13"
|
||||||
@@ -5926,16 +5925,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"omit-deep@npm:0.3.0":
|
|
||||||
version: 0.3.0
|
|
||||||
resolution: "omit-deep@npm:0.3.0"
|
|
||||||
dependencies:
|
|
||||||
is-plain-object: ^2.0.1
|
|
||||||
unset-value: ^0.1.1
|
|
||||||
checksum: ca603591af98f717ee4e4ae199778d386304f80072164fc1fb9c27abb011845faa27ffb32e7fa4a240698a4d54822526059af74f12f4f73315ecd7f03825d590
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"on-finished@npm:~2.3.0":
|
"on-finished@npm:~2.3.0":
|
||||||
version: 2.3.0
|
version: 2.3.0
|
||||||
resolution: "on-finished@npm:2.3.0"
|
resolution: "on-finished@npm:2.3.0"
|
||||||
@@ -6484,13 +6473,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"rename-keys@npm:^1.1.2":
|
|
||||||
version: 1.2.0
|
|
||||||
resolution: "rename-keys@npm:1.2.0"
|
|
||||||
checksum: 9d8e5ca3d1ae3fe6c0d7319a3fd80ded6ca34651e85bff27604982dcc750aed28d1a621374224a9c9072083769f5eab1fd86d1d5a53f54f96c7705c18267227b
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"repeat-element@npm:^1.1.2":
|
"repeat-element@npm:^1.1.2":
|
||||||
version: 1.1.4
|
version: 1.1.4
|
||||||
resolution: "repeat-element@npm:1.1.4"
|
resolution: "repeat-element@npm:1.1.4"
|
||||||
@@ -6741,17 +6723,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"scale-that-svg@npm:^1.0.5":
|
|
||||||
version: 1.0.5
|
|
||||||
resolution: "scale-that-svg@npm:1.0.5"
|
|
||||||
dependencies:
|
|
||||||
element-to-path: 1.2.0
|
|
||||||
svg-path-tools: 1.0.0
|
|
||||||
svgson: 3.0.0
|
|
||||||
checksum: ebad60633871e2a2d7a32aa68c0f390bc4c6d1d20149740ebaf8feae7086faa7e607d06a552ba8f34454d2954d081ec3324942c2aaaae066a2eec29c0c1eedbe
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"semver-diff@npm:^3.1.1":
|
"semver-diff@npm:^3.1.1":
|
||||||
version: 3.1.1
|
version: 3.1.1
|
||||||
resolution: "semver-diff@npm:3.1.1"
|
resolution: "semver-diff@npm:3.1.1"
|
||||||
@@ -7428,25 +7399,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"svg-path-tools@npm:1.0.0":
|
|
||||||
version: 1.0.0
|
|
||||||
resolution: "svg-path-tools@npm:1.0.0"
|
|
||||||
checksum: 8e44971bc160dbd459256a4ebcd05bbe80e2fed4824d6c58fd1a303da65982d0df62d3eec4198720f46077095ea1436537829b216d995e56ed8d50cb621029ea
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"svgson@npm:3.0.0":
|
|
||||||
version: 3.0.0
|
|
||||||
resolution: "svgson@npm:3.0.0"
|
|
||||||
dependencies:
|
|
||||||
clean-deep: 3.0.2
|
|
||||||
deep-rename-keys: ^0.2.1
|
|
||||||
omit-deep: 0.3.0
|
|
||||||
xml-reader: 2.4.3
|
|
||||||
checksum: 81d828a2f8af8b320d857b536bbae8d88e53e4543478e707230310f46dca08a4b2da1c00ac49e65fcbf79d39a673ccfbd9dfb8e412892a3584711cebc3ca2f30
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"symbol-tree@npm:^3.2.4":
|
"symbol-tree@npm:^3.2.4":
|
||||||
version: 3.2.4
|
version: 3.2.4
|
||||||
resolution: "symbol-tree@npm:3.2.4"
|
resolution: "symbol-tree@npm:3.2.4"
|
||||||
@@ -7912,16 +7864,6 @@ typescript@^4.1.3:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"unset-value@npm:^0.1.1":
|
|
||||||
version: 0.1.2
|
|
||||||
resolution: "unset-value@npm:0.1.2"
|
|
||||||
dependencies:
|
|
||||||
has-value: ^0.3.1
|
|
||||||
isobject: ^3.0.0
|
|
||||||
checksum: 56c7de1ee6b726002cc67b82954ec31b795836c2312d4d3d114a500eab5f632e1d3d6f5a164aff1ed90d7ffa94a009c452f6357f1f7d23bc444d489f622aeb9d
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"unset-value@npm:^1.0.0":
|
"unset-value@npm:^1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "unset-value@npm:1.0.0"
|
resolution: "unset-value@npm:1.0.0"
|
||||||
@@ -8290,15 +8232,6 @@ typescript@^4.1.3:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"xml-lexer@npm:^0.2.2":
|
|
||||||
version: 0.2.2
|
|
||||||
resolution: "xml-lexer@npm:0.2.2"
|
|
||||||
dependencies:
|
|
||||||
eventemitter3: ^2.0.0
|
|
||||||
checksum: ec9d3f8cbc61ed93b7fc1052d05b23cfe5bfe0064a1146f89bc3a9cfbb0c80c6c40d795cc253b745cdbba0607271d14fa57d496a7754fe350a06a8fceae23359
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"xml-name-validator@npm:^3.0.0":
|
"xml-name-validator@npm:^3.0.0":
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
resolution: "xml-name-validator@npm:3.0.0"
|
resolution: "xml-name-validator@npm:3.0.0"
|
||||||
@@ -8306,16 +8239,6 @@ typescript@^4.1.3:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"xml-reader@npm:2.4.3":
|
|
||||||
version: 2.4.3
|
|
||||||
resolution: "xml-reader@npm:2.4.3"
|
|
||||||
dependencies:
|
|
||||||
eventemitter3: ^2.0.0
|
|
||||||
xml-lexer: ^0.2.2
|
|
||||||
checksum: d4b4ca6eb2d61c17d2df2be73dd82a393ae88a4cd10c5152f9908bf3e3bafa5562ce4b63df31ef198bc9a7c8447d4c277c98622438da5b32abf55c2f15984bfa
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"xmlchars@npm:^2.2.0":
|
"xmlchars@npm:^2.2.0":
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
resolution: "xmlchars@npm:2.2.0"
|
resolution: "xmlchars@npm:2.2.0"
|
||||||
|
|||||||