mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-22 01:43:29 +01:00
Icons for years (#220)
This commit is contained in:
49
src/icon.ts
49
src/icon.ts
@@ -48,8 +48,16 @@ export type IconFeatures = {
|
||||
viewPortIncreasePercent: number | undefined;
|
||||
backgroundColor: string | undefined;
|
||||
foregroundColor: string | undefined;
|
||||
text: string | undefined;
|
||||
};
|
||||
|
||||
export const NO_FEATURES: IconFeatures = {
|
||||
viewPortIncreasePercent: undefined,
|
||||
backgroundColor: undefined,
|
||||
foregroundColor: undefined,
|
||||
text: undefined
|
||||
}
|
||||
|
||||
export type IconSpec = {
|
||||
svg: string | undefined;
|
||||
features: Partial<IconFeatures> | undefined;
|
||||
@@ -93,17 +101,11 @@ export class SvgIcon implements Icon {
|
||||
|
||||
constructor(
|
||||
svg: string,
|
||||
features: Partial<IconFeatures> = {
|
||||
viewPortIncreasePercent: undefined,
|
||||
backgroundColor: undefined,
|
||||
foregroundColor: undefined,
|
||||
}
|
||||
features: Partial<IconFeatures> = {}
|
||||
) {
|
||||
this.svg = svg;
|
||||
this.features = {
|
||||
viewPortIncreasePercent: undefined,
|
||||
backgroundColor: undefined,
|
||||
foregroundColor: undefined,
|
||||
...NO_FEATURES,
|
||||
...features,
|
||||
};
|
||||
}
|
||||
@@ -131,6 +133,17 @@ export class SvgIcon implements Icon {
|
||||
viewBox = viewBox.increasePercent(this.features.viewPortIncreasePercent);
|
||||
element("//svg:svg").setAttribute("viewBox", viewBox.toString());
|
||||
}
|
||||
if(this.features.text) {
|
||||
elements("//svg:text").forEach((text) => {
|
||||
text.textContent = this.features.text!
|
||||
});
|
||||
}
|
||||
if (this.features.foregroundColor) {
|
||||
elements("//svg:path|//svg:text").forEach((path) => {
|
||||
if (path.getAttribute("fill")) path.setAttribute("stroke", this.features.foregroundColor!);
|
||||
else path.setAttribute("fill", this.features.foregroundColor!);
|
||||
});
|
||||
}
|
||||
if (this.features.backgroundColor) {
|
||||
const rect = doc.createElementNS(SVG_NS, "rect");
|
||||
rect.setAttribute("x", `${viewBox.minX}`);
|
||||
@@ -142,12 +155,6 @@ export class SvgIcon implements Icon {
|
||||
const svg = element("//svg:svg")
|
||||
svg.insertBefore(rect, svg.childNodes[0]!);
|
||||
}
|
||||
if (this.features.foregroundColor) {
|
||||
elements("//svg:path").forEach((path) => {
|
||||
if (path.getAttribute("fill")) path.setAttribute("stroke", this.features.foregroundColor!);
|
||||
else path.setAttribute("fill", this.features.foregroundColor!);
|
||||
});
|
||||
}
|
||||
|
||||
return xmlTidy(doc as unknown as Node);
|
||||
};
|
||||
@@ -230,20 +237,24 @@ export type ICON =
|
||||
| "yoda"
|
||||
| "heart"
|
||||
| "star"
|
||||
| "solidStar";
|
||||
| "solidStar"
|
||||
| "yy"
|
||||
| "yyyy";
|
||||
|
||||
const iconFrom = (name: string) =>
|
||||
const svgFrom = (name: string) =>
|
||||
new SvgIcon(
|
||||
fs
|
||||
.readFileSync(path.resolve(__dirname, "..", "web", "icons", name))
|
||||
.toString()
|
||||
);
|
||||
|
||||
const iconFrom = (name: string) => svgFrom(name).with({ features: { viewPortIncreasePercent: 80 } });
|
||||
|
||||
export const ICONS: Record<ICON, SvgIcon> = {
|
||||
artists: iconFrom("navidrome-artists.svg"),
|
||||
albums: iconFrom("navidrome-all.svg"),
|
||||
radio: iconFrom("navidrome-radio.svg"),
|
||||
blank: iconFrom("blank.svg"),
|
||||
blank: svgFrom("blank.svg"),
|
||||
playlists: iconFrom("navidrome-playlists.svg"),
|
||||
genres: iconFrom("Theatre-Mask-111172.svg"),
|
||||
random: iconFrom("navidrome-random.svg"),
|
||||
@@ -308,7 +319,9 @@ export const ICONS: Record<ICON, SvgIcon> = {
|
||||
yoda: iconFrom("Yoda-68107.svg"),
|
||||
heart: iconFrom("Heart-85038.svg"),
|
||||
star: iconFrom("Star-16101.svg"),
|
||||
solidStar: iconFrom("Star-43879.svg")
|
||||
solidStar: iconFrom("Star-43879.svg"),
|
||||
yy: svgFrom("yy.svg"),
|
||||
yyyy: svgFrom("yyyy.svg"),
|
||||
};
|
||||
|
||||
export const STAR_WARS = [ICONS.c3po, ICONS.chewy, ICONS.darth, ICONS.skywalker, ICONS.leia, ICONS.r2d2, ICONS.yoda];
|
||||
|
||||
@@ -498,16 +498,18 @@ function server(
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/icon/:type/size/:size", (req, res) => {
|
||||
const type = req.params["type"]!;
|
||||
app.get("/icon/:type_text/size/:size", (req, res) => {
|
||||
const match = (req.params["type_text"] || "")!.match("^([A-Za-z0-9]+)(?:\:([A-Za-z0-9]+))?$")
|
||||
if (!match)
|
||||
return res.status(400).send();
|
||||
|
||||
const type = match[1]!
|
||||
const text = match[2]
|
||||
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)
|
||||
) {
|
||||
} else if (size != "legacy" && !SONOS_RECOMMENDED_IMAGE_SIZES.includes(size)) {
|
||||
return res.status(400).send();
|
||||
} else {
|
||||
let icon = (ICONS as any)[type]! as Icon;
|
||||
@@ -528,8 +530,8 @@ function server(
|
||||
icon
|
||||
.apply(
|
||||
features({
|
||||
viewPortIncreasePercent: 80,
|
||||
...serverOpts.iconColors,
|
||||
text: text
|
||||
})
|
||||
)
|
||||
.apply(festivals(clock))
|
||||
|
||||
11
src/smapi.ts
11
src/smapi.ts
@@ -251,11 +251,12 @@ const genre = (bonobUrl: URLBuilder, genre: Genre) => ({
|
||||
albumArtURI: iconArtURI(bonobUrl, iconForGenre(genre.name)).href(),
|
||||
});
|
||||
|
||||
const year = (bonobUrl: URLBuilder, year: Year) => ({
|
||||
const yyyy = (bonobUrl: URLBuilder, year: Year) => ({
|
||||
itemType: "albumList",
|
||||
id: `year:${year.year}`,
|
||||
title: year.year,
|
||||
albumArtURI: iconArtURI(bonobUrl, "music").href(),
|
||||
// todo: maybe year.year should be nullable?
|
||||
albumArtURI: year.year !== "?" ? iconArtURI(bonobUrl, "yyyy", year.year).href() : iconArtURI(bonobUrl, "music").href(),
|
||||
});
|
||||
|
||||
const playlist = (bonobUrl: URLBuilder, playlist: Playlist) => ({
|
||||
@@ -286,9 +287,9 @@ export const coverArtURI = (
|
||||
O.getOrElseW(() => iconArtURI(bonobUrl, "vinyl"))
|
||||
);
|
||||
|
||||
export const iconArtURI = (bonobUrl: URLBuilder, icon: ICON) =>
|
||||
export const iconArtURI = (bonobUrl: URLBuilder, icon: ICON, text: string | undefined = undefined) =>
|
||||
bonobUrl.append({
|
||||
pathname: `/icon/${icon}/size/legacy`,
|
||||
pathname: `/icon/${text == undefined ? icon : `${icon}:${text}`}/size/legacy`,
|
||||
});
|
||||
|
||||
export const sonosifyMimeType = (mimeType: string) =>
|
||||
@@ -888,7 +889,7 @@ function bindSmapiSoapServiceToExpress(
|
||||
.then(([page, total]) =>
|
||||
getMetadataResult({
|
||||
mediaCollection: page.map((it) =>
|
||||
year(bonobUrl, it)
|
||||
yyyy(bonobUrl, it)
|
||||
),
|
||||
index: paging._index,
|
||||
total,
|
||||
|
||||
@@ -805,7 +805,7 @@ export class SubsonicMusicLibrary implements MusicLibrary {
|
||||
years = async () => {
|
||||
const q: AlbumQuery = {
|
||||
_index: 0,
|
||||
_count: 100000, // FIXME: better than this ?
|
||||
_count: 100000, // FIXME: better than this, probably doesnt work anyway as max _count is 500 or something
|
||||
type: "alphabeticalByArtist",
|
||||
};
|
||||
const years = this.subsonic.getAlbumList2(this.credentials, q)
|
||||
|
||||
Reference in New Issue
Block a user