// Dynamically set the css background images for all the sprites import React from "react"; import { LETTERS } from "../constants"; import { imageSelectors, cursorSelectors } from "../skinSelectors"; import { useTypedSelector } from "../hooks"; import * as Selectors from "../selectors"; import { SkinImages } from "../types"; import { createSelector } from "reselect"; import Css from "./Css"; import ClipPaths from "./ClipPaths"; const CSS_PREFIX = "#webamp"; const mapRegionNamesToIds: { [key: string]: string } = { normal: "mainWindowClipPath", windowshade: "shadeMainWindowClipPath", equalizer: "equalizerWindowClipPath", equalizerws: "shadeEqualizerWindowClipPath", }; const mapRegionNamesToMatcher: { [key: string]: string } = { normal: "#main-window:not(.shade)", windowshade: "#main-window.shade", equalizer: "#equalizer-window:not(.shade)", equalizerws: "#equalizer-window.shade", }; const numExIsUsed = (skinImages: SkinImages) => Boolean(skinImages.DIGIT_0_EX); const FALLBACKS: { [key: string]: string } = { MAIN_BALANCE_BACKGROUND: "MAIN_VOLUME_BACKGROUND", MAIN_BALANCE_THUMB: "MAIN_VOLUME_THUMB", MAIN_BALANCE_THUMB_ACTIVE: "MAIN_VOLUME_THUMB_SELECTED", }; const getCssRules = createSelector( Selectors.getSkinImages, Selectors.getSkinCursors, Selectors.getSkinLetterWidths, Selectors.getSkinRegion, (skinImages, skinCursors, skinGenLetterWidths, skinRegion): string | null => { if (!skinImages || !skinCursors) { return null; } const cssRules = []; Object.keys(imageSelectors).forEach(imageName => { const imageUrl = skinImages[imageName] || skinImages[FALLBACKS[imageName]]; if (imageUrl) { imageSelectors[imageName].forEach(selector => { cssRules.push( `${CSS_PREFIX} ${selector} {background-image: url(${imageUrl})}` ); }); } }); if (skinGenLetterWidths != null) { LETTERS.forEach(letter => { const width = skinGenLetterWidths[`GEN_TEXT_${letter}`]; const selectedWidth = skinGenLetterWidths[`GEN_TEXT_SELECTED_${letter}`]; cssRules.push( `${CSS_PREFIX} .gen-text-${letter.toLowerCase()} {width: ${width}px;}` ); cssRules.push( `${CSS_PREFIX} .selected .gen-text-${letter.toLowerCase()} {width: ${selectedWidth}px;}` ); }); } Object.keys(cursorSelectors).forEach(cursorName => { const cursorUrl = skinCursors[cursorName]; if (cursorUrl) { cursorSelectors[cursorName].forEach(selector => { cssRules.push( `${ // TODO: Fix this hack // Maybe our CSS name spacing should be based on some other class/id // than the one we use for defining the main div. // That way it could be shared by both the player and the context menu. selector.startsWith("#webamp-context-menu") ? "" : CSS_PREFIX } ${selector} {cursor: url(${cursorUrl}), auto}` ); }); } }); if (numExIsUsed(skinImages)) { // This alternate number file requires that the minus sign be // formatted differently. cssRules.push( `${CSS_PREFIX} .status #time #minus-sign { top: 0px; left: -1px; width: 9px; height: 13px; }` ); } for (const [regionName, polygons] of Object.entries(skinRegion)) { if (polygons) { const matcher = mapRegionNamesToMatcher[regionName]; const id = mapRegionNamesToIds[regionName]; cssRules.push(`${CSS_PREFIX} ${matcher} { clip-path: url(#${id}); }`); } } return cssRules.join("\n"); } ); const getClipPaths = createSelector(Selectors.getSkinRegion, skinRegion => { const clipPaths: { [id: string]: string[] } = {}; for (const [regionName, polygons] of Object.entries(skinRegion)) { if (polygons) { const id = mapRegionNamesToIds[regionName]; clipPaths[id] = polygons; } } return clipPaths; }); export default function Skin() { const cssRules = useTypedSelector(getCssRules); const clipPaths = useTypedSelector(getClipPaths); if (cssRules == null) { return null; } return ( <> {cssRules} {clipPaths} ); }