Icons for root menu

This commit is contained in:
simojenki
2021-08-17 09:46:15 +10:00
parent 3545d9c653
commit 0ad1cd5c40
25 changed files with 1270 additions and 186 deletions

View File

@@ -159,6 +159,10 @@ BONOB_REPORT_NOW_PLAYING | true | Whether to report a track as now playing
- Implement the MusicService/MusicLibrary interface - Implement the MusicService/MusicLibrary interface
- Startup bonob with your new implementation. - Startup bonob with your new implementation.
## Credits
- Icons courtesy of: ![Navidrome](https://www.navidrome.org/), ![Vectornator](https://www.vectornator.io/), and @jicho
## TODO ## TODO
- Artist Radio - Artist Radio

View File

@@ -20,6 +20,7 @@
"fp-ts": "^2.9.5", "fp-ts": "^2.9.5",
"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",
@@ -36,6 +37,7 @@
"@types/supertest": "^2.0.10", "@types/supertest": "^2.0.10",
"chai": "^4.2.0", "chai": "^4.2.0",
"get-port": "^5.1.1", "get-port": "^5.1.1",
"image-js": "^0.32.0",
"jest": "^26.6.3", "jest": "^26.6.3",
"nodemon": "^2.0.7", "nodemon": "^2.0.7",
"supertest": "^6.1.3", "supertest": "^6.1.3",
@@ -49,6 +51,7 @@
"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 ./src/app.ts",
"devr": "BONOB_SONOS_SERVICE_NAME=bonobDev BONOB_SONOS_DEVICE_DISCOVERY=true BONOB_SONOS_AUTO_REGISTER=true nodemon ./src/app.ts",
"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"
} }

View File

@@ -2,6 +2,10 @@ import { option as O } from "fp-ts";
import express, { Express, Request } from "express"; 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 scale from "scale-that-svg";
import sharp from "sharp";
import fs from "fs";
import { PassThrough, Transform, TransformCallback } from "stream"; import { PassThrough, Transform, TransformCallback } from "stream";
@@ -13,7 +17,8 @@ import {
SONOS_RECOMMENDED_IMAGE_SIZES, SONOS_RECOMMENDED_IMAGE_SIZES,
LOGIN_ROUTE, LOGIN_ROUTE,
CREATE_REGISTRATION_ROUTE, CREATE_REGISTRATION_ROUTE,
REMOVE_REGISTRATION_ROUTE REMOVE_REGISTRATION_ROUTE,
ICON,
} from "./smapi"; } from "./smapi";
import { LinkCodes, InMemoryLinkCodes } from "./link_codes"; import { LinkCodes, InMemoryLinkCodes } from "./link_codes";
import { MusicService, isSuccess } from "./music_service"; import { MusicService, isSuccess } from "./music_service";
@@ -27,6 +32,26 @@ import makeI8N, { asLANGs, KEY, keys as i8nKeys, LANG } from "./i8n";
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;
} }
@@ -88,9 +113,11 @@ function server(
app.set("views", "./web/views"); app.set("views", "./web/views");
const langFor = (req: Request) => { const langFor = (req: Request) => {
logger.debug(`${req.path} (req[accept-language]=${req.headers["accept-language"]})`); logger.debug(
`${req.path} (req[accept-language]=${req.headers["accept-language"]})`
);
return i8n(...asLANGs(req.headers["accept-language"])); return i8n(...asLANGs(req.headers["accept-language"]));
} };
app.get("/", (req, res) => { app.get("/", (req, res) => {
const lang = langFor(req); const lang = langFor(req);
@@ -105,8 +132,12 @@ function server(
services, services,
bonobService: service, bonobService: service,
registeredBonobService, registeredBonobService,
createRegistrationRoute: bonobUrl.append({ pathname: CREATE_REGISTRATION_ROUTE }).pathname(), createRegistrationRoute: bonobUrl
removeRegistrationRoute: bonobUrl.append({ pathname: REMOVE_REGISTRATION_ROUTE }).pathname(), .append({ pathname: CREATE_REGISTRATION_ROUTE })
.pathname(),
removeRegistrationRoute: bonobUrl
.append({ pathname: REMOVE_REGISTRATION_ROUTE })
.pathname(),
}); });
} }
); );
@@ -116,8 +147,8 @@ function server(
return res.send({ return res.send({
service: { service: {
name: service.name, name: service.name,
sid: service.sid sid: service.sid,
} },
}); });
}); });
@@ -187,15 +218,19 @@ function server(
res.status(403).render("failure", { res.status(403).render("failure", {
lang, lang,
message: lang("loginFailed"), message: lang("loginFailed"),
cause: authResult.message cause: authResult.message,
}); });
} }
} }
}); });
app.get(STRINGS_ROUTE, (_, res) => { app.get(STRINGS_ROUTE, (_, res) => {
const stringNode = (id: string, value: string) => `<string stringId="${id}"><![CDATA[${value}]]></string>` const stringNode = (id: string, value: string) =>
const stringtableNode = (langName: string) => `<stringtable rev="1" xml:lang="${langName}">${i8nKeys().map(key => stringNode(key, i8n(langName as LANG)(key as KEY))).join("")}</stringtable>` `<string stringId="${id}"><![CDATA[${value}]]></string>`;
const stringtableNode = (langName: string) =>
`<stringtable rev="1" xml:lang="${langName}">${i8nKeys()
.map((key) => stringNode(key, i8n(langName as LANG)(key as KEY)))
.join("")}</stringtable>`;
res.type("application/xml").send(`<?xml version="1.0" encoding="utf-8" ?> res.type("application/xml").send(`<?xml version="1.0" encoding="utf-8" ?>
<stringtables xmlns="http://sonos.com/sonosapi"> <stringtables xmlns="http://sonos.com/sonosapi">
@@ -211,12 +246,23 @@ function server(
<Match> <Match>
<imageSizeMap> <imageSizeMap>
${SONOS_RECOMMENDED_IMAGE_SIZES.map( ${SONOS_RECOMMENDED_IMAGE_SIZES.map(
(size) => (size) =>
`<sizeEntry size="${size}" substitution="/art/size/${size}"/>` `<sizeEntry size="${size}" substitution="/size/${size}"/>`
).join("")} ).join("")}
</imageSizeMap> </imageSizeMap>
</Match> </Match>
</PresentationMap> </PresentationMap>
<PresentationMap type="BrowseIconSizeMap">
<Match>
<browseIconSizeMap>
<sizeEntry size="0" substitution="/size/legacy"/>
${SONOS_RECOMMENDED_IMAGE_SIZES.map(
(size) =>
`<sizeEntry size="${size}" substitution="/size/${size}"/>`
).join("")}
</browseIconSizeMap>
</Match>
</PresentationMap>
<PresentationMap type="Search"> <PresentationMap type="Search">
<Match> <Match>
<SearchCategories> <SearchCategories>
@@ -255,7 +301,8 @@ function server(
) )
.then(({ musicLibrary, stream }) => { .then(({ musicLibrary, stream }) => {
logger.info( logger.info(
`stream response from music service for ${id}, status=${stream.status `stream response from music service for ${id}, status=${
stream.status
}, headers=(${JSON.stringify(stream.headers)})` }, headers=(${JSON.stringify(stream.headers)})`
); );
@@ -328,28 +375,65 @@ function server(
} }
}); });
app.get("/:type/:id/art/size/:size", (req, res) => { app.get("/icon/:type/size/:size", (req, res) => {
const type = req.params["type"]!;
const size = req.params["size"]!;
if (!Object.keys(ICONS).includes(type)) {
return res.status(404).send();
} else if (
size != "legacy" &&
!SONOS_RECOMMENDED_IMAGE_SIZES.includes(size)
) {
return res.status(400).send();
} else {
const icon = (ICONS as any)[type]! as Icon;
const spec =
size == "legacy"
? {
outputSize: 80,
mimeType: "image/png",
responseFormatter: (svg: string): Promise<Buffer | string> =>
sharp(Buffer.from(svg)).png().toBuffer(),
}
: {
outputSize: Number.parseInt(size),
mimeType: "image/svg+xml",
responseFormatter: (svg: string): Promise<Buffer | string> =>
Promise.resolve(svg),
};
return Promise.resolve(icon.svg)
.then((svg) => scale(svg, { scale: spec.outputSize / icon.size }))
.then(spec.responseFormatter)
.then((data) => res.status(200).type(spec.mimeType).send(data));
}
});
app.get("/art/:type/:id/size/:size", (req, res) => {
const authToken = accessTokens.authTokenFor( const authToken = accessTokens.authTokenFor(
req.query[BONOB_ACCESS_TOKEN_HEADER] as string req.query[BONOB_ACCESS_TOKEN_HEADER] as string
); );
const type = req.params["type"]!; const type = req.params["type"]!;
const id = req.params["id"]!; const id = req.params["id"]!;
const size = Number.parseInt(req.params["size"]!); const size = req.params["size"]!;
if (!authToken) { if (!authToken) {
return res.status(401).send(); return res.status(401).send();
} else if (type != "artist" && type != "album") { } else if (type != "artist" && type != "album") {
return res.status(400).send(); return res.status(400).send();
} else if (!(size.match(/^\d+$/) && Number.parseInt(size) > 0)) {
return res.status(400).send();
} else { } else {
return musicService return musicService
.login(authToken) .login(authToken)
.then((it) => it.coverArt(id, type, size)) .then((it) => it.coverArt(id, type, Number.parseInt(size)))
.then((coverArt) => { .then((coverArt) => {
if (coverArt) { if (coverArt) {
res.status(200); res.status(200);
res.setHeader("content-type", coverArt.contentType); res.setHeader("content-type", coverArt.contentType);
res.send(coverArt.data); return res.send(coverArt.data);
} else { } else {
res.status(404).send(); return res.status(404).send();
} }
}) })
.catch((e: Error) => { .catch((e: Error) => {
@@ -357,7 +441,7 @@ function server(
`Failed fetching image ${type}/${id}/size/${size}: ${e.message}`, `Failed fetching image ${type}/${id}/size/${size}: ${e.message}`,
e e
); );
res.status(500).send(); return res.status(500).send();
}); });
} }
}); });

View File

@@ -46,6 +46,8 @@ export const SONOS_RECOMMENDED_IMAGE_SIZES = [
"1500", "1500",
]; ];
export type ICON = "artists" | "albums" | "playlists" | "genres" | "random" | "starred" | "recentlyAdded" | "recentlyPlayed" | "mostPlayed" | "discover"
const WSDL_FILE = path.resolve( const WSDL_FILE = path.resolve(
__dirname, __dirname,
"Sonoswsdl-1.19.4-20190411.142401-3.wsdl" "Sonoswsdl-1.19.4-20190411.142401-3.wsdl"
@@ -226,12 +228,15 @@ const playlist = (playlist: PlaylistSummary) => ({
}); });
export const defaultAlbumArtURI = (bonobUrl: URLBuilder, album: AlbumSummary) => export const defaultAlbumArtURI = (bonobUrl: URLBuilder, album: AlbumSummary) =>
bonobUrl.append({ pathname: `/album/${album.id}/art/size/180` }); bonobUrl.append({ pathname: `/art/album/${album.id}/size/180` });
export const iconArtURI = (bonobUrl: URLBuilder, icon: ICON) =>
bonobUrl.append({ pathname: `/icon/${icon}/size/legacy` });
export const defaultArtistArtURI = ( export const defaultArtistArtURI = (
bonobUrl: URLBuilder, bonobUrl: URLBuilder,
artist: ArtistSummary artist: ArtistSummary
) => bonobUrl.append({ pathname: `/artist/${artist.id}/art/size/180` }); ) => bonobUrl.append({ pathname: `/art/artist/${artist.id}/size/180` });
export const album = (bonobUrl: URLBuilder, album: AlbumSummary) => ({ export const album = (bonobUrl: URLBuilder, album: AlbumSummary) => ({
itemType: "album", itemType: "album",
@@ -558,19 +563,22 @@ function bindSmapiSoapServiceToExpress(
return getMetadataResult({ return getMetadataResult({
mediaCollection: [ mediaCollection: [
{ {
itemType: "container",
id: "artists", id: "artists",
title: lang("artists"), title: lang("artists"),
albumArtURI: iconArtURI(bonobUrl, "artists").href(),
itemType: "container",
}, },
{ {
itemType: "albumList",
id: "albums", id: "albums",
title: lang("albums"), title: lang("albums"),
albumArtURI: iconArtURI(bonobUrl, "albums").href(),
itemType: "albumList",
}, },
{ {
itemType: "playlist",
id: "playlists", id: "playlists",
title: lang("playlists"), title: lang("playlists"),
albumArtURI: iconArtURI(bonobUrl, "playlists").href(),
itemType: "playlist",
attributes: { attributes: {
readOnly: false, readOnly: false,
userContent: true, userContent: true,
@@ -578,34 +586,40 @@ function bindSmapiSoapServiceToExpress(
}, },
}, },
{ {
itemType: "container",
id: "genres", id: "genres",
title: lang("genres"), title: lang("genres"),
albumArtURI: iconArtURI(bonobUrl, "genres").href(),
itemType: "container",
}, },
{ {
itemType: "albumList",
id: "randomAlbums", id: "randomAlbums",
title: lang("random"), title: lang("random"),
albumArtURI: iconArtURI(bonobUrl, "random").href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "starredAlbums", id: "starredAlbums",
title: lang("starred"), title: lang("starred"),
albumArtURI: iconArtURI(bonobUrl, "starred").href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "recentlyAdded", id: "recentlyAdded",
title: lang("recentlyAdded"), title: lang("recentlyAdded"),
albumArtURI: iconArtURI(bonobUrl, "recentlyAdded").href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "recentlyPlayed", id: "recentlyPlayed",
title: lang("recentlyPlayed"), title: lang("recentlyPlayed"),
albumArtURI: iconArtURI(bonobUrl, "recentlyPlayed").href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "mostPlayed", id: "mostPlayed",
title: lang("mostPlayed"), title: lang("mostPlayed"),
albumArtURI: iconArtURI(bonobUrl, "mostPlayed").href(),
itemType: "albumList",
}, },
], ],
index: 0, index: 0,

View File

@@ -10,7 +10,7 @@ import { URLBuilder } from "./url_builder";
export const SONOS_LANG = ["en-US", "da-DK", "de-DE", "es-ES", "fr-FR", "it-IT", "ja-JP", "nb-NO", "nl-NL", "pt-BR", "sv-SE", "zh-CN"] export const SONOS_LANG = ["en-US", "da-DK", "de-DE", "es-ES", "fr-FR", "it-IT", "ja-JP", "nb-NO", "nl-NL", "pt-BR", "sv-SE", "zh-CN"]
export const PRESENTATION_AND_STRINGS_VERSION = "19"; export const PRESENTATION_AND_STRINGS_VERSION = "21";
// NOTE: manifest requires https for the URL, // NOTE: manifest requires https for the URL,
// otherwise you will get an error trying to register // otherwise you will get an error trying to register

View File

@@ -3717,7 +3717,6 @@ describe("Navidrome", () => {
const id = "idWithNoTracks"; const id = "idWithNoTracks";
const xml = similarSongsXml([]); const xml = similarSongsXml([]);
console.log(`xml = ${xml}`)
mockGET mockGET
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK))) .mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
.mockImplementationOnce(() => .mockImplementationOnce(() =>

View File

@@ -138,8 +138,6 @@ class SonosDriver {
return m![1]!; return m![1]!;
}); });
console.log(`posting to action ${action}`);
return request(this.server) return request(this.server)
.post(action) .post(action)
.type("form") .type("form")

View File

@@ -1,12 +1,16 @@
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import dayjs from "dayjs"; import dayjs from "dayjs";
import request from "supertest"; import request from "supertest";
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";
import { SONOS_DISABLED, Sonos, Device } from "../src/sonos"; import { SONOS_DISABLED, Sonos, Device } from "../src/sonos";
import { aDevice, aService } from "./builders"; import { aDevice, aService } from "./builders";
@@ -17,6 +21,9 @@ 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 {
SONOS_RECOMMENDED_IMAGE_SIZES,
} from "../src/smapi";
describe("rangeFilterFor", () => { describe("rangeFilterFor", () => {
describe("invalid range header string", () => { describe("invalid range header string", () => {
@@ -278,10 +285,16 @@ describe("server", () => {
.set("accept-language", acceptLanguage) .set("accept-language", acceptLanguage)
.send(); .send();
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.text).toMatch(`<input type="submit" value="${lang("register")}">`); expect(res.text).toMatch(
`<input type="submit" value="${lang("register")}">`
);
expect(res.text).toMatch(`<h3>${lang("expectedConfig")}</h3>`); expect(res.text).toMatch(`<h3>${lang("expectedConfig")}</h3>`);
expect(res.text).toMatch(`<h3>${lang("noExistingServiceRegistration")}</h3>`); expect(res.text).toMatch(
expect(res.text).not.toMatch(`<input type="submit" value="${lang("removeRegistration")}">`); `<h3>${lang("noExistingServiceRegistration")}</h3>`
);
expect(res.text).not.toMatch(
`<input type="submit" value="${lang("removeRegistration")}">`
);
}); });
}); });
}); });
@@ -317,10 +330,16 @@ describe("server", () => {
.set("accept-language", acceptLanguage) .set("accept-language", acceptLanguage)
.send(); .send();
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.text).toMatch(`<input type="submit" value="${lang("register")}">`); expect(res.text).toMatch(
`<input type="submit" value="${lang("register")}">`
);
expect(res.text).toMatch(`<h3>${lang("expectedConfig")}</h3>`); expect(res.text).toMatch(`<h3>${lang("expectedConfig")}</h3>`);
expect(res.text).toMatch(`<h3>${lang("existingServiceConfig")}</h3>`); expect(res.text).toMatch(
expect(res.text).toMatch(`<input type="submit" value="${lang("removeRegistration")}">`); `<h3>${lang("existingServiceConfig")}</h3>`
);
expect(res.text).toMatch(
`<input type="submit" value="${lang("removeRegistration")}">`
);
}); });
}); });
}); });
@@ -343,15 +362,16 @@ describe("server", () => {
it("should report some information about the service", async () => { it("should report some information about the service", async () => {
const res = await request(server) const res = await request(server)
.get(bonobUrl.append({ pathname: "/about" }).path()) .get(bonobUrl.append({ pathname: "/about" }).path())
.send(); .send();
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.body).toEqual({ expect(res.body).toEqual({
service: { service: {
name: theService.name, name: theService.name,
sid: theService.sid sid: theService.sid,
}}); },
});
}); });
}); });
@@ -415,7 +435,9 @@ describe("server", () => {
sonos.remove.mockResolvedValue(true); sonos.remove.mockResolvedValue(true);
const res = await request(server) const res = await request(server)
.post(bonobUrl.append({ pathname: "/registration/remove" }).path()) .post(
bonobUrl.append({ pathname: "/registration/remove" }).path()
)
.set("accept-language", acceptLanguage) .set("accept-language", acceptLanguage)
.send(); .send();
@@ -433,7 +455,9 @@ describe("server", () => {
sonos.remove.mockResolvedValue(false); sonos.remove.mockResolvedValue(false);
const res = await request(server) const res = await request(server)
.post(bonobUrl.append({ pathname: "/registration/remove" }).path()) .post(
bonobUrl.append({ pathname: "/registration/remove" }).path()
)
.set("accept-language", acceptLanguage) .set("accept-language", acceptLanguage)
.send(); .send();
@@ -454,7 +478,7 @@ describe("server", () => {
remove: jest.fn(), remove: jest.fn(),
}; };
const theService = aService({ const theService = aService({
name: serviceNameForLang name: serviceNameForLang,
}); });
const musicService = { const musicService = {
@@ -475,7 +499,6 @@ describe("server", () => {
now: jest.fn(), now: jest.fn(),
}; };
const server = makeServer( const server = makeServer(
sonos as unknown as Sonos, sonos as unknown as Sonos,
theService, theService,
@@ -496,10 +519,18 @@ describe("server", () => {
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.text).toMatch(`<title>${lang("login")}</title>`); expect(res.text).toMatch(`<title>${lang("login")}</title>`);
expect(res.text).toMatch(`<h1 class="login one-word-per-line">${lang("logInToBonob")}</h1>`); expect(res.text).toMatch(
expect(res.text).toMatch(`<label for="username">${lang("username")}:</label>`); `<h1 class="login one-word-per-line">${lang("logInToBonob")}</h1>`
expect(res.text).toMatch(`<label for="password">${lang("password")}:</label>`); );
expect(res.text).toMatch(`<input type="submit" value="${lang("login")}" id="submit">`); expect(res.text).toMatch(
`<label for="username">${lang("username")}:</label>`
);
expect(res.text).toMatch(
`<label for="password">${lang("password")}:</label>`
);
expect(res.text).toMatch(
`<input type="submit" value="${lang("login")}" id="submit">`
);
}); });
describe("when the credentials are valid", () => { describe("when the credentials are valid", () => {
@@ -1011,7 +1042,7 @@ describe("server", () => {
}); });
}); });
describe("art", () => { describe("/art", () => {
const musicService = { const musicService = {
login: jest.fn(), login: jest.fn(),
}; };
@@ -1040,7 +1071,7 @@ describe("server", () => {
describe("when there is no access-token", () => { describe("when there is no access-token", () => {
it("should return a 401", async () => { it("should return a 401", async () => {
const res = await request(server).get(`/album/123/art/size/180`); const res = await request(server).get(`/art/album/123/size/180`);
expect(res.status).toEqual(401); expect(res.status).toEqual(401);
}); });
@@ -1051,7 +1082,7 @@ describe("server", () => {
now = now.add(1, "day"); now = now.add(1, "day");
const res = await request(server).get( const res = await request(server).get(
`/album/123/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/art/album/123/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
); );
expect(res.status).toEqual(401); expect(res.status).toEqual(401);
@@ -1063,7 +1094,7 @@ describe("server", () => {
it("should return a 400", async () => { it("should return a 400", async () => {
const res = await request(server) const res = await request(server)
.get( .get(
`/foo/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/art/foo/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
@@ -1072,6 +1103,21 @@ describe("server", () => {
}); });
describe("artist art", () => { describe("artist art", () => {
["0", "-1", "foo"].forEach((size) => {
describe(`when the size is ${size}`, () => {
it(`should return a 400`, async () => {
musicService.login.mockResolvedValue(musicLibrary);
const res = await request(server)
.get(
`/art/artist/${albumId}/size/${size}?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(400);
});
});
});
describe("when there is some", () => { describe("when there is some", () => {
it("should return the image and a 200", async () => { it("should return the image and a 200", async () => {
const coverArt = { const coverArt = {
@@ -1086,7 +1132,7 @@ describe("server", () => {
const res = await request(server) const res = await request(server)
.get( .get(
`/artist/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
@@ -1112,7 +1158,7 @@ describe("server", () => {
const res = await request(server) const res = await request(server)
.get( .get(
`/artist/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
@@ -1128,7 +1174,7 @@ describe("server", () => {
const res = await request(server) const res = await request(server)
.get( .get(
`/artist/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
@@ -1138,6 +1184,21 @@ describe("server", () => {
}); });
describe("album art", () => { describe("album art", () => {
["0", "-1", "foo"].forEach((size) => {
describe(`when the size is ${size}`, () => {
it(`should return a 400`, async () => {
musicService.login.mockResolvedValue(musicLibrary);
const res = await request(server)
.get(
`/art/album/${albumId}/size/${size}?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(400);
});
});
});
describe("when there is some", () => { describe("when there is some", () => {
it("should return the image and a 200", async () => { it("should return the image and a 200", async () => {
const coverArt = { const coverArt = {
@@ -1151,7 +1212,7 @@ describe("server", () => {
const res = await request(server) const res = await request(server)
.get( .get(
`/album/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/art/album/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
@@ -1176,7 +1237,7 @@ describe("server", () => {
const res = await request(server) const res = await request(server)
.get( .get(
`/album/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/art/album/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
@@ -1191,7 +1252,7 @@ describe("server", () => {
const res = await request(server) const res = await request(server)
.get( .get(
`/album/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/art/album/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
@@ -1201,6 +1262,94 @@ describe("server", () => {
}); });
}); });
}); });
describe("/icon", () => {
const server = 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
);
describe("invalid icon names", () => {
[
"..%2F..%2Ffoo",
"%2Fetc%2Fpasswd",
".%2Fbob.js",
".",
"..",
"1",
"%23%24",
"notAValidIcon",
].forEach((type) => {
describe(`trying to retrieve an icon with name ${type}`, () => {
it(`should fail`, async () => {
const response = await request(server).get(
`/icon/${type}/size/legacy`
);
expect(response.status).toEqual(404);
});
});
});
});
describe("invalid size", () => {
["-1", "0", "59", "foo"].forEach((size) => {
describe(`trying to retrieve an icon with size ${size}`, () => {
it(`should fail`, async () => {
const response = await request(server).get(
`/icon/artists/size/${size}`
);
expect(response.status).toEqual(400);
});
});
});
});
describe("fetching", () => {
Object.keys(ICONS).forEach((type) => {
describe(`type=${type}`, () => {
describe(`legacy icon`, () => {
it("should return the png image", async () => {
const response = await request(server).get(
`/icon/${type}/size/legacy`
);
expect(response.status).toEqual(200);
expect(response.header["content-type"]).toEqual("image/png");
const image = await Image.load(response.body);
expect(image.width).toEqual(80);
expect(image.height).toEqual(80);
});
});
describe("svg icon", () => {
SONOS_RECOMMENDED_IMAGE_SIZES.forEach((size) => {
it(`should return an svg image for size = ${size}`, async () => {
const response = await request(server).get(
`/icon/${type}/size/${size}`
);
expect(response.status).toEqual(200);
expect(response.header["content-type"]).toEqual(
"image/svg+xml; charset=utf-8"
);
const svg = Buffer.from(response.body).toString();
expect(svg).toContain(`viewBox="0 0 ${size} ${size}"`);
expect(svg).toContain(
` xmlns="http://www.w3.org/2000/svg" `
);
});
});
});
});
});
});
});
}); });
}); });
}); });

View File

@@ -21,6 +21,7 @@ import {
defaultAlbumArtURI, defaultAlbumArtURI,
defaultArtistArtURI, defaultArtistArtURI,
searchResult, searchResult,
iconArtURI,
} from "../src/smapi"; } from "../src/smapi";
import { import {
@@ -54,83 +55,109 @@ describe("service config", () => {
const bonobWithContextPath = url("http://localhost:5678/some-context-path"); const bonobWithContextPath = url("http://localhost:5678/some-context-path");
[bonobWithNoContextPath, bonobWithContextPath].forEach((bonobUrl) => { [bonobWithNoContextPath, bonobWithContextPath].forEach((bonobUrl) => {
const server = makeServer( describe(bonobUrl.href(), () => {
SONOS_DISABLED, const server = makeServer(
aService({ name: "music land" }), SONOS_DISABLED,
bonobUrl, aService({ name: "music land" }),
new InMemoryMusicService() bonobUrl,
); new InMemoryMusicService()
);
const stringsUrl = bonobUrl.append({ pathname: STRINGS_ROUTE }); const stringsUrl = bonobUrl.append({ pathname: STRINGS_ROUTE });
const presentationUrl = bonobUrl.append({ const presentationUrl = bonobUrl.append({
pathname: PRESENTATION_MAP_ROUTE, pathname: PRESENTATION_MAP_ROUTE,
});
describe(`${stringsUrl}`, () => {
async function fetchStringsXml() {
const res = await request(server).get(stringsUrl.path()).send();
expect(res.status).toEqual(200);
// removing the sonos xml ns as makes xpath queries with xpath-ts painful
return parseXML(
res.text.replace('xmlns="http://sonos.com/sonosapi"', "")
);
}
it("should return xml for the strings", async () => {
const xml = await fetchStringsXml();
const sonosString = (id: string, lang: string) =>
xpath.select(
`string(/stringtables/stringtable[@xml:lang="${lang}"]/string[@stringId="${id}"])`,
xml
);
expect(sonosString("AppLinkMessage", "en-US")).toEqual(
"Linking sonos with music land"
);
expect(sonosString("AppLinkMessage", "nl-NL")).toEqual(
"Sonos koppelen aan music land"
);
// no fr-FR translation, so use en-US
expect(sonosString("AppLinkMessage", "fr-FR")).toEqual(
"Linking sonos with music land"
);
}); });
it("should return a section for all sonos supported languages", async () => { describe(STRINGS_ROUTE, () => {
const xml = await fetchStringsXml(); async function fetchStringsXml() {
SONOS_LANG.forEach(lang => { const res = await request(server).get(stringsUrl.path()).send();
expect(xpath.select(
`string(/stringtables/stringtable[@xml:lang="${lang}"]/string[@stringId="AppLinkMessage"])`,
xml
)).toBeDefined();
});
});
});
describe(`${presentationUrl}`, () => { expect(res.status).toEqual(200);
it("should have an ArtWorkSizeMap for all sizes recommended by sonos", async () => {
const res = await request(server).get(presentationUrl.path()).send();
expect(res.status).toEqual(200); // removing the sonos xml ns as makes xpath queries with xpath-ts painful
return parseXML(
res.text.replace('xmlns="http://sonos.com/sonosapi"', "")
);
}
// removing the sonos xml ns as makes xpath queries with xpath-ts painful it("should return xml for the strings", async () => {
const xml = parseXML( const xml = await fetchStringsXml();
res.text.replace('xmlns="http://sonos.com/sonosapi"', "")
);
const imageSizeMap = (size: string) => const sonosString = (id: string, lang: string) =>
xpath.select( xpath.select(
`string(/Presentation/PresentationMap[@type="ArtWorkSizeMap"]/Match/imageSizeMap/sizeEntry[@size="${size}"]/@substitution)`, `string(/stringtables/stringtable[@xml:lang="${lang}"]/string[@stringId="${id}"])`,
xml xml
);
expect(sonosString("AppLinkMessage", "en-US")).toEqual(
"Linking sonos with music land"
);
expect(sonosString("AppLinkMessage", "nl-NL")).toEqual(
"Sonos koppelen aan music land"
); );
SONOS_RECOMMENDED_IMAGE_SIZES.forEach((size) => { // no fr-FR translation, so use en-US
expect(imageSizeMap(size)).toEqual(`/art/size/${size}`); expect(sonosString("AppLinkMessage", "fr-FR")).toEqual(
"Linking sonos with music land"
);
}); });
it("should return a section for all sonos supported languages", async () => {
const xml = await fetchStringsXml();
SONOS_LANG.forEach((lang) => {
expect(
xpath.select(
`string(/stringtables/stringtable[@xml:lang="${lang}"]/string[@stringId="AppLinkMessage"])`,
xml
)
).toBeDefined();
});
});
});
describe(PRESENTATION_MAP_ROUTE, () => {
it("should have an ArtWorkSizeMap for all sizes recommended by sonos", async () => {
const res = await request(server).get(presentationUrl.path()).send();
expect(res.status).toEqual(200);
// removing the sonos xml ns as makes xpath queries with xpath-ts painful
const xml = parseXML(
res.text.replace('xmlns="http://sonos.com/sonosapi"', "")
);
const imageSizeMap = (size: string) =>
xpath.select(
`string(/Presentation/PresentationMap[@type="ArtWorkSizeMap"]/Match/imageSizeMap/sizeEntry[@size="${size}"]/@substitution)`,
xml
);
SONOS_RECOMMENDED_IMAGE_SIZES.forEach((size) => {
expect(imageSizeMap(size)).toEqual(`/size/${size}`);
});
});
it("should have an BrowseIconSizeMap for all sizes recommended by sonos", async () => {
const res = await request(server).get(presentationUrl.path()).send();
expect(res.status).toEqual(200);
// removing the sonos xml ns as makes xpath queries with xpath-ts painful
const xml = parseXML(
res.text.replace('xmlns="http://sonos.com/sonosapi"', "")
);
const imageSizeMap = (size: string) =>
xpath.select(
`string(/Presentation/PresentationMap[@type="BrowseIconSizeMap"]/Match/browseIconSizeMap/sizeEntry[@size="${size}"]/@substitution)`,
xml
);
SONOS_RECOMMENDED_IMAGE_SIZES.forEach((size) => {
expect(imageSizeMap(size)).toEqual(`/size/${size}`);
});
});
}); });
}); });
}); });
@@ -246,7 +273,7 @@ describe("track", () => {
albumId: someTrack.album.id, albumId: someTrack.album.id,
albumArtist: someTrack.artist.name, albumArtist: someTrack.artist.name,
albumArtistId: someTrack.artist.id, albumArtistId: someTrack.artist.id,
albumArtURI: `http://localhost:4567/foo/album/${someTrack.album.id}/art/size/180?access-token=1234`, albumArtURI: `http://localhost:4567/foo/art/album/${someTrack.album.id}/size/180?access-token=1234`,
artist: someTrack.artist.name, artist: someTrack.artist.name,
artistId: someTrack.artist.id, artistId: someTrack.artist.id,
duration: someTrack.duration, duration: someTrack.duration,
@@ -281,7 +308,7 @@ describe("defaultAlbumArtURI", () => {
const album = anAlbum(); const album = anAlbum();
expect(defaultAlbumArtURI(bonobUrl, album).href()).toEqual( expect(defaultAlbumArtURI(bonobUrl, album).href()).toEqual(
`http://localhost:1234/context-path/album/${album.id}/art/size/180?search=yes` `http://localhost:1234/context-path/art/album/${album.id}/size/180?search=yes`
); );
}); });
}); });
@@ -292,7 +319,7 @@ describe("defaultArtistArtURI", () => {
const artist = anArtist(); const artist = anArtist();
expect(defaultArtistArtURI(bonobUrl, artist).href()).toEqual( expect(defaultArtistArtURI(bonobUrl, artist).href()).toEqual(
`http://localhost:1234/something/artist/${artist.id}/art/size/180?s=123` `http://localhost:1234/something/art/artist/${artist.id}/size/180?s=123`
); );
}); });
}); });
@@ -450,7 +477,8 @@ describe("api", () => {
.catch((e: any) => { .catch((e: any) => {
expect(e.root.Envelope.Body.Fault).toEqual({ expect(e.root.Envelope.Body.Fault).toEqual({
faultcode: "Client.NOT_LINKED_RETRY", faultcode: "Client.NOT_LINKED_RETRY",
faultstring: "Link Code not found yet, sonos app will keep polling until you log in to bonob", faultstring:
"Link Code not found yet, sonos app will keep polling until you log in to bonob",
detail: { detail: {
ExceptionInfo: "NOT_LINKED_RETRY", ExceptionInfo: "NOT_LINKED_RETRY",
SonosError: "5", SonosError: "5",
@@ -707,46 +735,72 @@ describe("api", () => {
getMetadataResult({ getMetadataResult({
mediaCollection: [ mediaCollection: [
{ {
itemType: "container",
id: "artists", id: "artists",
title: "Artists", title: "Artists",
albumArtURI: iconArtURI(bonobUrl, "artists").href(),
itemType: "container",
},
{
id: "albums",
title: "Albums",
albumArtURI: iconArtURI(bonobUrl, "albums").href(),
itemType: "albumList",
}, },
{ itemType: "albumList", id: "albums", title: "Albums" },
{ {
itemType: "playlist",
id: "playlists", id: "playlists",
title: "Playlists", title: "Playlists",
albumArtURI: iconArtURI(bonobUrl, "playlists").href(),
itemType: "playlist",
attributes: { attributes: {
readOnly: "false", readOnly: "false",
renameable: "false", renameable: "false",
userContent: "true", userContent: "true",
}, },
}, },
{ itemType: "container", id: "genres", title: "Genres" },
{ {
itemType: "albumList", id: "genres",
title: "Genres",
albumArtURI: iconArtURI(bonobUrl, "genres").href(),
itemType: "container",
},
{
id: "randomAlbums", id: "randomAlbums",
title: "Random", title: "Random",
albumArtURI: iconArtURI(bonobUrl, "random").href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "starredAlbums", id: "starredAlbums",
title: "Starred", title: "Starred",
albumArtURI: iconArtURI(bonobUrl, "starred").href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "recentlyAdded", id: "recentlyAdded",
title: "Recently added", title: "Recently added",
albumArtURI: iconArtURI(
bonobUrl,
"recentlyAdded"
).href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "recentlyPlayed", id: "recentlyPlayed",
title: "Recently played", title: "Recently played",
albumArtURI: iconArtURI(
bonobUrl,
"recentlyPlayed"
).href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "mostPlayed", id: "mostPlayed",
title: "Most played", title: "Most played",
albumArtURI: iconArtURI(
bonobUrl,
"mostPlayed"
).href(),
itemType: "albumList",
}, },
], ],
index: 0, index: 0,
@@ -758,7 +812,7 @@ describe("api", () => {
describe("when an accept-language header is present with value nl-NL", () => { describe("when an accept-language header is present with value nl-NL", () => {
it("should return nl-NL", async () => { it("should return nl-NL", async () => {
ws.addHttpHeader("accept-language", "nl-NL, en-US;q=0.9") ws.addHttpHeader("accept-language", "nl-NL, en-US;q=0.9");
const root = await ws.getMetadataAsync({ const root = await ws.getMetadataAsync({
id: "root", id: "root",
index: 0, index: 0,
@@ -768,46 +822,72 @@ describe("api", () => {
getMetadataResult({ getMetadataResult({
mediaCollection: [ mediaCollection: [
{ {
itemType: "container",
id: "artists", id: "artists",
title: "Artiesten", title: "Artiesten",
albumArtURI: iconArtURI(bonobUrl, "artists").href(),
itemType: "container",
},
{
id: "albums",
title: "Albums",
albumArtURI: iconArtURI(bonobUrl, "albums").href(),
itemType: "albumList",
}, },
{ itemType: "albumList", id: "albums", title: "Albums" },
{ {
itemType: "playlist",
id: "playlists", id: "playlists",
title: "Afspeellijsten", title: "Afspeellijsten",
albumArtURI: iconArtURI(bonobUrl, "playlists").href(),
itemType: "playlist",
attributes: { attributes: {
readOnly: "false", readOnly: "false",
renameable: "false", renameable: "false",
userContent: "true", userContent: "true",
}, },
}, },
{ itemType: "container", id: "genres", title: "Genres" },
{ {
itemType: "albumList", id: "genres",
title: "Genres",
albumArtURI: iconArtURI(bonobUrl, "genres").href(),
itemType: "container",
},
{
id: "randomAlbums", id: "randomAlbums",
title: "Willekeurig", title: "Willekeurig",
albumArtURI: iconArtURI(bonobUrl, "random").href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "starredAlbums", id: "starredAlbums",
title: "Favorieten", title: "Favorieten",
albumArtURI: iconArtURI(bonobUrl, "starred").href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "recentlyAdded", id: "recentlyAdded",
title: "Onlangs toegevoegd", title: "Onlangs toegevoegd",
albumArtURI: iconArtURI(
bonobUrl,
"recentlyAdded"
).href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "recentlyPlayed", id: "recentlyPlayed",
title: "Onlangs afgespeeld", title: "Onlangs afgespeeld",
albumArtURI: iconArtURI(
bonobUrl,
"recentlyPlayed"
).href(),
itemType: "albumList",
}, },
{ {
itemType: "albumList",
id: "mostPlayed", id: "mostPlayed",
title: "Meest afgespeeld", title: "Meest afgespeeld",
albumArtURI: iconArtURI(
bonobUrl,
"mostPlayed"
).href(),
itemType: "albumList",
}, },
], ],
index: 0, index: 0,

View File

@@ -10,6 +10,7 @@
"strict": true, "strict": true,
"noImplicitAny": false, "noImplicitAny": false,
"typeRoots" : [ "typeRoots" : [
"../typings",
"../node_modules/@types" "../node_modules/@types"
] ]
}, },

View File

@@ -4,9 +4,11 @@
/* Basic Options */ /* Basic Options */
// "incremental": true, /* Enable incremental compilation */ // "incremental": true, /* Enable incremental compilation */
"target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "target": "ES2019" /* 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'. */ "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"lib": ["es2019"], /* Specify library files to be included in the compilation. */ "lib": [
"es2019"
] /* Specify library files to be included in the compilation. */,
// "allowJs": true, /* Allow javascript files to be compiled. */ // "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */ // "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
@@ -14,8 +16,8 @@
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */ // "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./build", /* Redirect output structure to the directory. */ "outDir": "./build" /* Redirect output structure to the directory. */,
"rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ "rootDir": "." /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
// "composite": true, /* Enable project compilation */ // "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */ // "removeComments": true, /* Do not emit comments to output. */
@@ -25,31 +27,35 @@
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */ /* Strict Type-Checking Options */
"strict": true, /* Enable all 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. */ "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
// "strictNullChecks": true, /* Enable strict null checks. */ // "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "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. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */ /* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */ "noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true, /* Report errors on unused parameters. */ "noUnusedParameters": true /* Report errors on unused parameters. */,
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
"noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ "noUncheckedIndexedAccess": true /* Include 'undefined' in index signature results */,
/* Module Resolution Options */ /* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */ "typeRoots": [
// "types": [], /* Type declaration files to be included in compilation. */ "./typings",
"node_modules/@types"
]
/* List of folders to include type definitions from. */,
// "types": ["src/customTypes/scale-that-svg.d.ts"], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
@@ -64,7 +70,7 @@
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */ /* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */ "skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
} }
} }

4
typings/scale-that-svg/index.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module "scale-that-svg" {
const noTypesYet: any;
export default noTypesYet;
}

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 473 B

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +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">
<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>

After

Width:  |  Height:  |  Size: 444 B

View File

@@ -0,0 +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">
<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>

After

Width:  |  Height:  |  Size: 418 B

View File

@@ -0,0 +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">
<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>

After

Width:  |  Height:  |  Size: 525 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" role="img">
<path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"></path>
<title>Most Played</title>
</svg>

After

Width:  |  Height:  |  Size: 247 B

View File

@@ -0,0 +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">
<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>

After

Width:  |  Height:  |  Size: 360 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" role="img">
<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>

After

Width:  |  Height:  |  Size: 352 B

View File

@@ -0,0 +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">
<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>

After

Width:  |  Height:  |  Size: 340 B

View File

@@ -0,0 +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">
<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>

After

Width:  |  Height:  |  Size: 324 B

View File

@@ -0,0 +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">
<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>

After

Width:  |  Height:  |  Size: 334 B

View File

@@ -0,0 +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">
<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>

After

Width:  |  Height:  |  Size: 276 B

710
yarn.lock
View File

@@ -362,6 +362,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/runtime@npm:^7.10.3":
version: 7.15.3
resolution: "@babel/runtime@npm:7.15.3"
dependencies:
regenerator-runtime: ^0.13.4
checksum: 2f0b8d2d4e36035ab1d84af0ec26aafa098536870f27c8e07de0a0e398f7a394fdea68a88165535ffb52ded6a68912bdc3450bdf91f229eb132e1c89470789f5
languageName: node
linkType: hard
"@babel/template@npm:^7.14.5, @babel/template@npm:^7.3.3": "@babel/template@npm:^7.14.5, @babel/template@npm:^7.3.3":
version: 7.14.5 version: 7.14.5
resolution: "@babel/template@npm:7.14.5" resolution: "@babel/template@npm:7.14.5"
@@ -704,6 +713,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@swiftcarrot/color-fns@npm:^3.2.0":
version: 3.2.0
resolution: "@swiftcarrot/color-fns@npm:3.2.0"
dependencies:
"@babel/runtime": ^7.10.3
checksum: 2d966d3db068d8f0489fa77ab28a985e44e07402c5811d3f9d36b45affa9e6ab7982503c4180c5aba70b5d1e973ccc6a3e3a801b2e8b39cd9eec3070a6cd10db
languageName: node
linkType: hard
"@szmarczak/http-timer@npm:^1.1.2": "@szmarczak/http-timer@npm:^1.1.2":
version: 1.1.2 version: 1.1.2
resolution: "@szmarczak/http-timer@npm:1.1.2" resolution: "@szmarczak/http-timer@npm:1.1.2"
@@ -914,6 +932,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/pako@npm:^1.0.1":
version: 1.0.2
resolution: "@types/pako@npm:1.0.2"
checksum: b94d5a82cfe339427549c3e22d77d9c651f15e49e00c7ec3dc274611ad6c8ed9f68231fb133c4c1733cd24506e51c101cd943991af68b429fdadb6d41357e830
languageName: node
linkType: hard
"@types/prettier@npm:^2.0.0": "@types/prettier@npm:^2.0.0":
version: 2.3.0 version: 2.3.0
resolution: "@types/prettier@npm:2.3.0" resolution: "@types/prettier@npm:2.3.0"
@@ -1497,6 +1522,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"blob-util@npm:^2.0.2":
version: 2.0.2
resolution: "blob-util@npm:2.0.2"
checksum: d543e6b92e4ca715ca33c78e89a07a2290d43e5b2bc897d7ec588c5c7bbf59df93e45225ac0c9258aa6ce4320358990f99c9288f1c48280f8ec5d7a2e088d19b
languageName: node
linkType: hard
"body-parser@npm:1.19.0": "body-parser@npm:1.19.0":
version: 1.19.0 version: 1.19.0
resolution: "body-parser@npm:1.19.0" resolution: "body-parser@npm:1.19.0"
@@ -1537,10 +1569,12 @@ __metadata:
express: ^4.17.1 express: ^4.17.1
fp-ts: ^2.9.5 fp-ts: ^2.9.5
get-port: ^5.1.1 get-port: ^5.1.1
image-js: ^0.32.0
jest: ^26.6.3 jest: ^26.6.3
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
@@ -1770,6 +1804,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"canny-edge-detector@npm:^1.0.0":
version: 1.0.0
resolution: "canny-edge-detector@npm:1.0.0"
checksum: a38e9117219ac5819072d5ae119f61a87f3aa32d4a3e7bc0821e82aa92ca35bdf99f227fb9b9049cf9b9ae31719c868087b904d3b2ecaab86eade47eafc35bac
languageName: node
linkType: hard
"capture-exit@npm:^2.0.0": "capture-exit@npm:^2.0.0":
version: 2.0.0 version: 2.0.0
resolution: "capture-exit@npm:2.0.0" resolution: "capture-exit@npm:2.0.0"
@@ -1904,6 +1945,17 @@ __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"
@@ -2360,6 +2412,16 @@ __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"
@@ -2516,6 +2578,13 @@ __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"
@@ -2684,6 +2753,13 @@ __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"
@@ -2860,6 +2936,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"fast-bmp@npm:^1.0.0":
version: 1.0.0
resolution: "fast-bmp@npm:1.0.0"
dependencies:
iobuffer: ^3.1.0
checksum: aa9a6242c51d8c4ce7e1059dc8dae3dc1e099b2f651a928fbdca9bfa6215288dbc5909f0b75d3b2688ab65bad87f12565aac21c25c7edb712c9554a76cd3c940
languageName: node
linkType: hard
"fast-deep-equal@npm:^3.1.1": "fast-deep-equal@npm:^3.1.1":
version: 3.1.3 version: 3.1.3
resolution: "fast-deep-equal@npm:3.1.3" resolution: "fast-deep-equal@npm:3.1.3"
@@ -2867,6 +2952,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"fast-jpeg@npm:^1.0.1":
version: 1.0.1
resolution: "fast-jpeg@npm:1.0.1"
dependencies:
iobuffer: ^2.1.0
tiff: ^2.0.0
checksum: 60b40f4f8f61989bcabac820e3b737edfed0a11a766eb034dee69f51abb795ffcdaa5487af40299fc2f2470aed130049de464e1624abf197f67e5f0c84b94f2a
languageName: node
linkType: hard
"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0": "fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0":
version: 2.1.0 version: 2.1.0
resolution: "fast-json-stable-stringify@npm:2.1.0" resolution: "fast-json-stable-stringify@npm:2.1.0"
@@ -2881,6 +2976,24 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"fast-list@npm:^1.0.3":
version: 1.0.3
resolution: "fast-list@npm:1.0.3"
checksum: 2bc01386f6c994c35f188cbb1bac5dcff1937c8910b8fdae079d2e7a28a9d6ebfb8b1a9247cdf5c6aa92778cd85e14dca442bcf56d860947cd16f138c9252605
languageName: node
linkType: hard
"fast-png@npm:^5.0.4":
version: 5.0.4
resolution: "fast-png@npm:5.0.4"
dependencies:
"@types/pako": ^1.0.1
iobuffer: ^5.0.2
pako: ^2.0.2
checksum: e7f2cce48821536619d3c38f537635da0090ef01c71229f3c30b5f32c3d9de14d4509b21682a22e4deaddfac8cc9b5f6a23f2e7883521913483cdba8b657d6c6
languageName: node
linkType: hard
"fast-safe-stringify@npm:^2.0.4, fast-safe-stringify@npm:^2.0.7": "fast-safe-stringify@npm:^2.0.4, fast-safe-stringify@npm:^2.0.7":
version: 2.0.7 version: 2.0.7
resolution: "fast-safe-stringify@npm:2.0.7" resolution: "fast-safe-stringify@npm:2.0.7"
@@ -2913,6 +3026,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"fft.js@npm:^4.0.3":
version: 4.0.4
resolution: "fft.js@npm:4.0.4"
checksum: 55e4f5ee0afc9ba9b3759c6fe303e0a2f2c1ebb9bc525914de9699b76f55e01da97d61ec30dedde0dfb0a8be3c0711d47af7aca53a1425f9875468254b704c7f
languageName: node
linkType: hard
"file-type@npm:^10.10.0":
version: 10.11.0
resolution: "file-type@npm:10.11.0"
checksum: cadd8cd187692dcde637a3ff53bb51c5d935633fc8085e7d25bfb3b4bf995e14a43f2baf71bdcb9d7235b3e725bd158b75d25911fa2f73e5812955382228c511
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"
@@ -3324,6 +3451,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"has-own@npm:^1.0.1":
version: 1.0.1
resolution: "has-own@npm:1.0.1"
checksum: 3893dd749c56fbda5c8a6277cf627d5ec8de2094fd8beb2cb17d492c1773e5826d092512714b94ffa11fb8c2f5c31106adf6dd60d53680c2846246addff94338
languageName: node
linkType: hard
"has-symbols@npm:^1.0.1": "has-symbols@npm:^1.0.1":
version: 1.0.2 version: 1.0.2
resolution: "has-symbols@npm:1.0.2" resolution: "has-symbols@npm:1.0.2"
@@ -3562,6 +3696,48 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"image-js@npm:^0.32.0":
version: 0.32.0
resolution: "image-js@npm:0.32.0"
dependencies:
"@swiftcarrot/color-fns": ^3.2.0
blob-util: ^2.0.2
canny-edge-detector: ^1.0.0
fast-bmp: ^1.0.0
fast-jpeg: ^1.0.1
fast-list: ^1.0.3
fast-png: ^5.0.4
has-own: ^1.0.1
image-type: ^4.1.0
is-array-type: ^1.0.0
is-integer: ^1.0.7
jpeg-js: ^0.4.3
js-priority-queue: ^0.1.5
js-quantities: ^1.7.6
median-quickselect: ^1.0.1
ml-convolution: 0.2.0
ml-disjoint-set: ^1.0.0
ml-matrix: ^6.8.0
ml-matrix-convolution: 0.4.3
ml-regression: ^5.0.0
monotone-chain-convex-hull: ^1.0.0
new-array: ^1.0.0
robust-point-in-polygon: ^1.0.3
tiff: ^5.0.0
web-worker-manager: ^0.2.0
checksum: 0c5f672a9c6c2dbcdc698317ce964cf21294901d5fe18d5b996d64c3fb2f16699a8c6957bd60e825c582b0d86c35ea7758f9870ca80f2b552275e44f48bdd622
languageName: node
linkType: hard
"image-type@npm:^4.1.0":
version: 4.1.0
resolution: "image-type@npm:4.1.0"
dependencies:
file-type: ^10.10.0
checksum: debb29d8de4f5f00617cde8935e5c63bbe2d5ce76d0b387e98066572474b00002af086fd0093553ef9aad9f6f6b76e22af4eaad80d7c420794c39576304be2e5
languageName: node
linkType: hard
"import-lazy@npm:^2.1.0": "import-lazy@npm:^2.1.0":
version: 2.1.0 version: 2.1.0
resolution: "import-lazy@npm:2.1.0" resolution: "import-lazy@npm:2.1.0"
@@ -3640,6 +3816,29 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"iobuffer@npm:^2.1.0":
version: 2.1.0
resolution: "iobuffer@npm:2.1.0"
checksum: a283788bbe642a440f729b7123abd873cf1f2a7204eb30d19752c0eb0775d9bfcc785dbabb52b586788092cce19e6e0da83085ad7297a7bdbcd19c001a0851c8
languageName: node
linkType: hard
"iobuffer@npm:^3.1.0":
version: 3.2.0
resolution: "iobuffer@npm:3.2.0"
dependencies:
utf8: ^2.1.2
checksum: 50c1547ac138da6ea7c09b394040e6dbbbe96af93553d43e7076f78b741745a7d781ac38a27be5ff4b1ad24545edb64020940254860e873bac1660acce3c030f
languageName: node
linkType: hard
"iobuffer@npm:^5.0.2, iobuffer@npm:^5.0.3":
version: 5.0.3
resolution: "iobuffer@npm:5.0.3"
checksum: e30548416afcfa9b0bca6122a9674b087e4a5f9082f21a6d7722833782536d8d7966eea786dd2082057363a513c34a339bec7888bb125dd6c4348670e4b30a1a
languageName: node
linkType: hard
"ip@npm:^1.1.5": "ip@npm:^1.1.5":
version: 1.1.5 version: 1.1.5
resolution: "ip@npm:1.1.5" resolution: "ip@npm:1.1.5"
@@ -3672,6 +3871,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-any-array@npm:^1.0.0":
version: 1.0.0
resolution: "is-any-array@npm:1.0.0"
checksum: c24654b1dc34b2e543c1a3a5fc8169c3fd467ba9ffc2e44b0e04561dd5549e71d063006fe0ad27359f9964cd6a0d4870375b3753b14b878c9ff5d7853849727e
languageName: node
linkType: hard
"is-array-type@npm:^1.0.0":
version: 1.0.0
resolution: "is-array-type@npm:1.0.0"
checksum: 14d7efede3221f04c7d6b1166f5bdff34463251b95521f821a9f6cde88a9fda715a1393a80aaebc15494d2e971a0b348b0c92de267bde63a3b18bfeae80da732
languageName: node
linkType: hard
"is-arrayish@npm:^0.2.1": "is-arrayish@npm:^0.2.1":
version: 0.2.1 version: 0.2.1
resolution: "is-arrayish@npm:0.2.1" resolution: "is-arrayish@npm:0.2.1"
@@ -3794,6 +4007,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-finite@npm:^1.0.0":
version: 1.1.0
resolution: "is-finite@npm:1.1.0"
checksum: 532b97ed3d03e04c6bd203984d9e4ba3c0c390efee492bad5d1d1cd1802a68ab27adbd3ef6382f6312bed6c8bb1bd3e325ea79a8dc8fe080ed7a06f5f97b93e7
languageName: node
linkType: hard
"is-fullwidth-code-point@npm:^1.0.0": "is-fullwidth-code-point@npm:^1.0.0":
version: 1.0.0 version: 1.0.0
resolution: "is-fullwidth-code-point@npm:1.0.0" resolution: "is-fullwidth-code-point@npm:1.0.0"
@@ -3843,6 +4063,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-integer@npm:^1.0.7":
version: 1.0.7
resolution: "is-integer@npm:1.0.7"
dependencies:
is-finite: ^1.0.0
checksum: e57ab783fa401df8f86a80f8e47d3e7dfdd19a0acb7183d7bb1e55830364172d7035b6980e98d856a2508b261923803d800c2a430c1e6801d9792c3394827f30
languageName: node
linkType: hard
"is-lambda@npm:^1.0.1": "is-lambda@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "is-lambda@npm:1.0.1" resolution: "is-lambda@npm:1.0.1"
@@ -3887,7 +4116,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4": "is-plain-object@npm:^2.0.1, 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:
@@ -4482,6 +4711,27 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"jpeg-js@npm:^0.4.3":
version: 0.4.3
resolution: "jpeg-js@npm:0.4.3"
checksum: 9e5bacc9135efa7da340b62e81fa56fab0c8516ef617228758132af5b7d31b516cc6e1500cdffb82d3161629be341be980099f2b37eb76b81e26db6e3e848c77
languageName: node
linkType: hard
"js-priority-queue@npm:^0.1.5":
version: 0.1.5
resolution: "js-priority-queue@npm:0.1.5"
checksum: 741a54456101e5625b8b19080b4e5cc329ebc443b21221f809afa807a2a2f7c0cbf717649166de079013753adc89e82b60cb3eeb02bb9250808ff8fc36bd7a00
languageName: node
linkType: hard
"js-quantities@npm:^1.7.6":
version: 1.7.6
resolution: "js-quantities@npm:1.7.6"
checksum: ab3ea04650d2581b09540328e4d644576e998de3ec8c67c7fd113ea2e8520a304568120a813d043a8b529243fecb303effacdd3ca0ad9e077394306749bae715
languageName: node
linkType: hard
"js-tokens@npm:^4.0.0": "js-tokens@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "js-tokens@npm:4.0.0" resolution: "js-tokens@npm:4.0.0"
@@ -4712,6 +4962,27 @@ __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"
@@ -4826,6 +5097,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"median-quickselect@npm:^1.0.1":
version: 1.0.1
resolution: "median-quickselect@npm:1.0.1"
checksum: a01a7270c9c48be15aed3036b78bfcf0c0417987fafa3add8ba7293250d484dc2b6ba4abc527492dbfc6f94880bd353b5f3eafc62429baa866101f51a8a5eb61
languageName: node
linkType: hard
"merge-descriptors@npm:1.0.1": "merge-descriptors@npm:1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "merge-descriptors@npm:1.0.1" resolution: "merge-descriptors@npm:1.0.1"
@@ -5052,6 +5330,240 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ml-array-max@npm:^1.2.3":
version: 1.2.3
resolution: "ml-array-max@npm:1.2.3"
dependencies:
is-any-array: ^1.0.0
checksum: c97e4395653edf15ea2a050d06fd7590306da0b18f0adee985d4db32860f9dd4caa30c22f5ef859164094eab6d13b6e4e71796e07c33625fd65e07d3fda2b5f7
languageName: node
linkType: hard
"ml-array-median@npm:^1.1.1":
version: 1.1.5
resolution: "ml-array-median@npm:1.1.5"
dependencies:
is-any-array: ^1.0.0
median-quickselect: ^1.0.1
checksum: 235845cac797ccd8ef47cc83415496fa0d304e7f5b5ef2d307faa6270b71e0ccef7bcc422709e8c4a0b7e98898dda5c00f19272b075e3af8682dff83ce8fd90c
languageName: node
linkType: hard
"ml-array-min@npm:^1.2.2":
version: 1.2.2
resolution: "ml-array-min@npm:1.2.2"
dependencies:
is-any-array: ^1.0.0
checksum: 1b5da44a51ad2b4720828ed69a856dda63c5e20adeb2fbbcd622e76c14697f97e8e61d504cbb66fffc6c88fe6841344abd6f0aa92209622b8d9c672073dba3f3
languageName: node
linkType: hard
"ml-array-rescale@npm:^1.3.5":
version: 1.3.5
resolution: "ml-array-rescale@npm:1.3.5"
dependencies:
is-any-array: ^1.0.0
ml-array-max: ^1.2.3
ml-array-min: ^1.2.2
checksum: 5abfe1070cde8d38bf8773b55de83c17627271c92c5e1360723b919ecff0b145ce29dd7e7a4d7f1ee603f0ebb780e912e1348ceaa9b7c17855a075c0eb99aa17
languageName: node
linkType: hard
"ml-convolution@npm:0.2.0":
version: 0.2.0
resolution: "ml-convolution@npm:0.2.0"
dependencies:
fft.js: ^4.0.3
next-power-of-two: ^1.0.0
checksum: 95f625e302f5aeff5f0b14236c23b924a2571c945a4583ef256c05ddf81388c75435186c223efc61595c1d5b35b7e9abed128cd69b87a98e9ea2cbf4fab2f2ed
languageName: node
linkType: hard
"ml-disjoint-set@npm:^1.0.0":
version: 1.0.0
resolution: "ml-disjoint-set@npm:1.0.0"
checksum: 0bc6f29243863da943da0c8552672477882acf53a86d8dac9ad177d892d8825604cddb79655ff98874f18f82a23cb1266301e050d5efb5dc4c951e4da6fd37f0
languageName: node
linkType: hard
"ml-distance-euclidean@npm:^2.0.0":
version: 2.0.0
resolution: "ml-distance-euclidean@npm:2.0.0"
checksum: e31f98a947ce6971c35d74e6d2521800f0d219efb34c78b20b5f52debd206008d52e677685c09839e6bab5d2ed233aa009314236e4e548d5fafb60f2f71e2b3e
languageName: node
linkType: hard
"ml-fft@npm:1.3.5":
version: 1.3.5
resolution: "ml-fft@npm:1.3.5"
checksum: 8dfa57e640a7b0259038e28fa497e66eed9e63140d9ab4c56182721b13777c97c6ce1874f00fbe7cb2eb18d78dece90bbee26d9b96bd35c3901b972d4d608638
languageName: node
linkType: hard
"ml-kernel-gaussian@npm:^2.0.2":
version: 2.0.2
resolution: "ml-kernel-gaussian@npm:2.0.2"
dependencies:
ml-distance-euclidean: ^2.0.0
checksum: 5219a769ae046fea4612e186a1b1a400c19e8c8f12508665c6f553790da8bfd2bef20851feab4c005324f501ded3b7195770ff6b2e670f9e8c2a89a0a7d1cc43
languageName: node
linkType: hard
"ml-kernel-polynomial@npm:^2.0.1":
version: 2.0.1
resolution: "ml-kernel-polynomial@npm:2.0.1"
checksum: a5b75efa8ca97729b8882b930f50a3c604ea827bff61384fca7f487098c32ba01c1b34bd75c4b163d0e44a4a531bbdc018ac584ebca28b9158a9bd86e752fe71
languageName: node
linkType: hard
"ml-kernel-sigmoid@npm:^1.0.1":
version: 1.0.1
resolution: "ml-kernel-sigmoid@npm:1.0.1"
checksum: ec30b4ff11be1d33b677cddc399bb7e3797a0105a6ee9cdfd59995a92a651c6e220f4a8e104f54e97407d0bb048f6f158ef6324aaac6f07ca553ce9e121ff175
languageName: node
linkType: hard
"ml-kernel@npm:^3.0.0":
version: 3.0.0
resolution: "ml-kernel@npm:3.0.0"
dependencies:
ml-distance-euclidean: ^2.0.0
ml-kernel-gaussian: ^2.0.2
ml-kernel-polynomial: ^2.0.1
ml-kernel-sigmoid: ^1.0.1
ml-matrix: ^6.1.2
checksum: 095521c766d1f65e13ef6f9a6d69fa11ef980fa2934142aa788b632f228b7afaf7cd1205012c073f18899aae6fc6393be9f0ed583c79a615035927ffe2e15ebb
languageName: node
linkType: hard
"ml-matrix-convolution@npm:0.4.3":
version: 0.4.3
resolution: "ml-matrix-convolution@npm:0.4.3"
dependencies:
ml-fft: 1.3.5
ml-stat: ^1.2.0
checksum: c145ed1debea80df91ff49b78a039b74c64843a1990fea70532b79145a55792ecedd1ecf2bc5e831a447ec9c8c58de2921e9bf701d77fade8f21c700f51b7cf3
languageName: node
linkType: hard
"ml-matrix@npm:^6.1.2, ml-matrix@npm:^6.4.1, ml-matrix@npm:^6.8.0":
version: 6.8.0
resolution: "ml-matrix@npm:6.8.0"
dependencies:
ml-array-rescale: ^1.3.5
checksum: 5d7456e981697148b8ab5c593a1c7d764a337de4e582b61dd830fde299e46a694fbe43d2d2c8321c9d27e71eed00a8285b0c6b9f89b437dc6cad93607813ed1d
languageName: node
linkType: hard
"ml-regression-base@npm:^2.0.1, ml-regression-base@npm:^2.1.3":
version: 2.1.3
resolution: "ml-regression-base@npm:2.1.3"
dependencies:
is-any-array: ^1.0.0
checksum: a0517456163318dee071c1f3ccb092ff1453ae0da24484d497b1e835f5eac9d3ddf7aa2577d4a766ccaf821ef72c146c0c1170770bbb7586d59557207d95592b
languageName: node
linkType: hard
"ml-regression-exponential@npm:^2.0.0":
version: 2.1.0
resolution: "ml-regression-exponential@npm:2.1.0"
dependencies:
ml-regression-base: ^2.1.3
ml-regression-simple-linear: ^2.0.3
checksum: 613c6c34135503ab0db12428eaebab67e8b7f22e7e8ed2c22c717b5fe406d97e6c28fd0e45f448d2ee64bd3eccdb7f42c8be700e8b4d7756d81b8c5a1b67953b
languageName: node
linkType: hard
"ml-regression-multivariate-linear@npm:^2.0.2":
version: 2.0.3
resolution: "ml-regression-multivariate-linear@npm:2.0.3"
dependencies:
ml-matrix: ^6.4.1
checksum: 4e8ebc124f0bb51229c8adf2b23e01d412745b1212901182ed9a76f4d88031f24c443d743e50e0711d6ca632052cb7cf326c8d4c92218a6af55628d56d052f42
languageName: node
linkType: hard
"ml-regression-polynomial@npm:^2.0.0":
version: 2.2.0
resolution: "ml-regression-polynomial@npm:2.2.0"
dependencies:
ml-matrix: ^6.8.0
ml-regression-base: ^2.1.3
checksum: 4eca53fabf0fb875416b3155373360809230bc1cab91dfae22582522785054c34fcfa89889235649f024b50d26d916d936a1530c10cada1aa5551d280e7e16a6
languageName: node
linkType: hard
"ml-regression-power@npm:^2.0.0":
version: 2.0.0
resolution: "ml-regression-power@npm:2.0.0"
dependencies:
ml-regression-base: ^2.0.1
ml-regression-simple-linear: ^2.0.2
checksum: 847fec484126706bbbdeab8c2145ac533401e00513ab625ee44c42ee184d89514732ef9bd911c1911b19766864e08984c3ccb3a7f65318340608e7022a5e1294
languageName: node
linkType: hard
"ml-regression-robust-polynomial@npm:^2.0.0":
version: 2.0.0
resolution: "ml-regression-robust-polynomial@npm:2.0.0"
dependencies:
ml-matrix: ^6.1.2
ml-regression-base: ^2.0.1
checksum: 3bc061cddb745dfafe48f70315e9fc2c54fcfa42884125117b8c6489e388be99a4636181811bfa30e0bb8c7ccebf0f4a7740afd92cd16f5de368687007a19df0
languageName: node
linkType: hard
"ml-regression-simple-linear@npm:^2.0.2, ml-regression-simple-linear@npm:^2.0.3":
version: 2.0.3
resolution: "ml-regression-simple-linear@npm:2.0.3"
dependencies:
ml-regression-base: ^2.0.1
checksum: 61812f3c6dae61d948e7741938c85fcde87fad7b3a7f0403bcd3e466892f0bb7087373453c55e52089f9dd7dd22ab726ef91ca621bb13fc88100075739135d39
languageName: node
linkType: hard
"ml-regression-theil-sen@npm:^2.0.0":
version: 2.0.0
resolution: "ml-regression-theil-sen@npm:2.0.0"
dependencies:
ml-array-median: ^1.1.1
ml-regression-base: ^2.0.1
checksum: 0f05a537a34e07a9ea016ea092f92d7ef33a650e5dbfd0b1b7c433d94024780fa73ef06b1605fe94a2cad17d28649726062c86af15c4c9f16c40393e1d3afe6e
languageName: node
linkType: hard
"ml-regression@npm:^5.0.0":
version: 5.0.0
resolution: "ml-regression@npm:5.0.0"
dependencies:
ml-kernel: ^3.0.0
ml-matrix: ^6.1.2
ml-regression-base: ^2.0.1
ml-regression-exponential: ^2.0.0
ml-regression-multivariate-linear: ^2.0.2
ml-regression-polynomial: ^2.0.0
ml-regression-power: ^2.0.0
ml-regression-robust-polynomial: ^2.0.0
ml-regression-simple-linear: ^2.0.2
ml-regression-theil-sen: ^2.0.0
checksum: 1f9f31e36e469672828efb81c971f686c2b18afb1b633c94dedc52d944adfd79324e94b9ecf1a3ce7f22171d17111aaed55feb26651984f961eb07e322b7821e
languageName: node
linkType: hard
"ml-stat@npm:^1.2.0":
version: 1.3.3
resolution: "ml-stat@npm:1.3.3"
checksum: ff397cc84f2f3d248e68cb8c7051e391ba2fcdf3b3a3bc7ba52c215f58be6cdeeb2bf024cf83f2f2ede8db58c147f29c8fea7bf3cfc5ce513da2699b42b6fabc
languageName: node
linkType: hard
"monotone-chain-convex-hull@npm:^1.0.0":
version: 1.0.0
resolution: "monotone-chain-convex-hull@npm:1.0.0"
checksum: 333aa6dc628f78334ec45e5adec9c320c6662980aa8d09db5d27fbdcdb7e6f9dad6f32cc298dc45a8db163ed493ef5da9727204590bb7b371482f6aa8b14fa09
languageName: node
linkType: hard
"morgan@npm:^1.10.0": "morgan@npm:^1.10.0":
version: 1.10.0 version: 1.10.0
resolution: "morgan@npm:1.10.0" resolution: "morgan@npm:1.10.0"
@@ -5133,6 +5645,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"new-array@npm:^1.0.0":
version: 1.0.0
resolution: "new-array@npm:1.0.0"
checksum: 844ac096924b618a372558e421ee0faa81a582538f28ef97a571e2b2579a1abedb360be6c726f371e9dfdf73283a882d58ac7d7fa6b870b9fcbf80038fddbba0
languageName: node
linkType: hard
"next-power-of-two@npm:^1.0.0":
version: 1.0.0
resolution: "next-power-of-two@npm:1.0.0"
checksum: a77ee4a1ed42ff5fe6dd73e0485ac259fe73fb9b6395eca861dfcf2a0e3051a16801c1f8c90a33bb4a38a89aaf89f5f72efbe30884632f5439458f38419e778e
languageName: node
linkType: hard
"nice-try@npm:^1.0.4": "nice-try@npm:^1.0.4":
version: 1.0.5 version: 1.0.5
resolution: "nice-try@npm:1.0.5" resolution: "nice-try@npm:1.0.5"
@@ -5400,6 +5926,16 @@ __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"
@@ -5524,6 +6060,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"pako@npm:^2.0.2, pako@npm:^2.0.3":
version: 2.0.4
resolution: "pako@npm:2.0.4"
checksum: 82b9b0b99dd830c9103856a6dbd10f0cb2c8c32b9768184727ea381a99666de9a47a069d2e6efe6acf09336f363956b50835c196ef9311b34b7274d420eb0d88
languageName: node
linkType: hard
"parse-json@npm:^5.0.0": "parse-json@npm:^5.0.0":
version: 5.2.0 version: 5.2.0
resolution: "parse-json@npm:5.2.0" resolution: "parse-json@npm:5.2.0"
@@ -5899,6 +6442,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"regenerator-runtime@npm:^0.13.4":
version: 0.13.9
resolution: "regenerator-runtime@npm:0.13.9"
checksum: 65ed455fe5afd799e2897baf691ca21c2772e1a969d19bb0c4695757c2d96249eb74ee3553ea34a91062b2a676beedf630b4c1551cc6299afb937be1426ec55e
languageName: node
linkType: hard
"regex-not@npm:^1.0.0, regex-not@npm:^1.0.2": "regex-not@npm:^1.0.0, regex-not@npm:^1.0.2":
version: 1.0.2 version: 1.0.2
resolution: "regex-not@npm:1.0.2" resolution: "regex-not@npm:1.0.2"
@@ -5934,6 +6484,13 @@ __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"
@@ -6067,6 +6624,51 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"robust-orientation@npm:^1.0.2":
version: 1.2.1
resolution: "robust-orientation@npm:1.2.1"
dependencies:
robust-scale: ^1.0.2
robust-subtract: ^1.0.0
robust-sum: ^1.0.0
two-product: ^1.0.2
checksum: 83b87300009716d96cf17af27b2c787bb7cabe00e82b6740ff4777a601babfcf132b3ec3d10cb1a91886423aa51863026d3befd58058af3b90be98abbda0056e
languageName: node
linkType: hard
"robust-point-in-polygon@npm:^1.0.3":
version: 1.0.3
resolution: "robust-point-in-polygon@npm:1.0.3"
dependencies:
robust-orientation: ^1.0.2
checksum: dc68ef96f6f2c6d2087f8e74583dc2e9add1a86aef402096fd1c9dde5c9ec1209f6710178a053cc48cbf4103488c382ae2a26302b2d27bb3dce4d81f0c4c5951
languageName: node
linkType: hard
"robust-scale@npm:^1.0.2":
version: 1.0.2
resolution: "robust-scale@npm:1.0.2"
dependencies:
two-product: ^1.0.2
two-sum: ^1.0.0
checksum: 4217f15c94bc803c0c78f6011507102cb603a4e9f71721d44e155c17c1fbe989382c8a150d20e23ca51164077395dab698498b9650d2377cc0a69902d73d0a1c
languageName: node
linkType: hard
"robust-subtract@npm:^1.0.0":
version: 1.0.0
resolution: "robust-subtract@npm:1.0.0"
checksum: e9dcc39a1a802d4a34d338844d9382ad7e49f58c5d01ce0d66cd18d6477069475af11a80fba0c0e158211c2b272c1c05950e78cbfc29ea7005f4ecc9e9f9d492
languageName: node
linkType: hard
"robust-sum@npm:^1.0.0":
version: 1.0.0
resolution: "robust-sum@npm:1.0.0"
checksum: b9f32829ba3d6fd9cffeee440e1fb93a7d42f264540bd631abf13d0e8737f3a15a16a15764fa8a2fe86d3db6a1970361cf7ad2ed536c858b59e45f6f493a454b
languageName: node
linkType: hard
"rsvp@npm:^4.8.4": "rsvp@npm:^4.8.4":
version: 4.8.5 version: 4.8.5
resolution: "rsvp@npm:4.8.5" resolution: "rsvp@npm:4.8.5"
@@ -6139,6 +6741,17 @@ __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"
@@ -6815,6 +7428,25 @@ __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"
@@ -6903,6 +7535,25 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"tiff@npm:^2.0.0":
version: 2.1.0
resolution: "tiff@npm:2.1.0"
dependencies:
iobuffer: ^2.1.0
checksum: 2e50c57964b67ede02598460bfd0670f0993773632f996481ba5b65274df65d4f8776bbf6c4c8efda6beaecda9db9f64b65cdb3663783e4a99666bc15a65695c
languageName: node
linkType: hard
"tiff@npm:^5.0.0":
version: 5.0.0
resolution: "tiff@npm:5.0.0"
dependencies:
iobuffer: ^5.0.3
pako: ^2.0.3
checksum: 9f10288ec3153b0200b725bbb5a7b4b88d3c78d699fecaf289c3bdcadd892ce495a5dc9152c38a74f8bf0c457c0bab0c9b0bbccb9e5f25bcc3d6b44418619396
languageName: node
linkType: hard
"tmpl@npm:1.0.x": "tmpl@npm:1.0.x":
version: 1.0.4 version: 1.0.4
resolution: "tmpl@npm:1.0.4" resolution: "tmpl@npm:1.0.4"
@@ -7095,6 +7746,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"two-product@npm:^1.0.2":
version: 1.0.2
resolution: "two-product@npm:1.0.2"
checksum: b289814957df58b91c910c944e7e247aa01a0a70e8fafdf58f01baf7fa1f96c06dc1cbb6cdafb39525e9a5ac0a9566875f1a76a02ef1f736f26e56fca2f0c847
languageName: node
linkType: hard
"two-sum@npm:^1.0.0":
version: 1.0.0
resolution: "two-sum@npm:1.0.0"
checksum: 2c6a995b555233b989f473a5d039bd237d75f4824b9b54dc9d9ab28157f3e412b37156acbb48b322c817a26f3cc85e3da281c9aed4b06e892d2d27ae88db7d32
languageName: node
linkType: hard
"type-check@npm:~0.3.2": "type-check@npm:~0.3.2":
version: 0.3.2 version: 0.3.2
resolution: "type-check@npm:0.3.2" resolution: "type-check@npm:0.3.2"
@@ -7247,6 +7912,16 @@ 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"
@@ -7310,6 +7985,13 @@ typescript@^4.1.3:
languageName: node languageName: node
linkType: hard linkType: hard
"utf8@npm:^2.1.2":
version: 2.1.2
resolution: "utf8@npm:2.1.2"
checksum: de5d18adb219cae7871e1c105249e2fc7e6cae0e01c2b4c2eb6b099851b3bf62d1db6be6d83b5e4dea09036f8d16dd7222ad46eb326b38940a988e86743c1a61
languageName: node
linkType: hard
"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": "util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1":
version: 1.0.2 version: 1.0.2
resolution: "util-deprecate@npm:1.0.2" resolution: "util-deprecate@npm:1.0.2"
@@ -7408,6 +8090,13 @@ typescript@^4.1.3:
languageName: node languageName: node
linkType: hard linkType: hard
"web-worker-manager@npm:^0.2.0":
version: 0.2.0
resolution: "web-worker-manager@npm:0.2.0"
checksum: 7a0595e92f80320d51cc4815e885f507faef1744c2b3e7675813e08aeeb807e1ca873457a79425333faa6b0bafc07bd4a97ebacd45135e7cd18934993d2e1386
languageName: node
linkType: hard
"webidl-conversions@npm:^5.0.0": "webidl-conversions@npm:^5.0.0":
version: 5.0.0 version: 5.0.0
resolution: "webidl-conversions@npm:5.0.0" resolution: "webidl-conversions@npm:5.0.0"
@@ -7601,6 +8290,15 @@ 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"
@@ -7608,6 +8306,16 @@ 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"