// Dynamically set the css background images for all the sprites import React from "react"; import { createPortal } from "react-dom"; import { connect } from "react-redux"; import { LETTERS } from "../constants"; import { imageSelectors, cursorSelectors } from "../skinSelectors"; const CSS_PREFIX = "#webamp"; const mapRegionNamesToIds = { normal: "mainWindowClipPath", windowshade: "shadeMainWindowClipPath", equalizer: "equalizerWindowClipPath", equalizerws: "shadeEqualizerWindowClipPath" }; const mapRegionNamesToMatcher = { normal: "#main-window:not(.shade)", windowshade: "#main-window.shade", equalizer: "#equalizer-window:not(.shade)", equalizerws: "#equalizer-window.shade" }; const numExIsUsed = skinImages => !!skinImages.DIGIT_0_EX; class Css extends React.Component { componentWillMount() { const style = document.createElement("style"); style.type = "text/css"; style.id = "webamp-skin"; document.head.appendChild(style); this.style = style; } componentWillUnmount() { this.style.remove(); this.style = null; } render() { return createPortal(this.props.children, this.style); } } class ClipPaths extends React.Component { componentWillMount() { const paths = document.createElement("div"); document.body.appendChild(paths); this.paths = paths; } componentWillUnmount() { this.paths.remove(); this.paths = null; } render() { // this.props.children should be an object containing arrays of strings. The // keys are ids, and the arrays are arrays of polygon point strings const { children } = this.props; return createPortal( {Object.keys(children).map(id => ( {children[id].map((points, i) => ( ))} ))} , this.paths ); } } const FALLBACKS = { MAIN_BALANCE_BACKGROUND: "MAIN_VOLUME_BACKGROUND", MAIN_BALANCE_THUMB: "MAIN_VOLUME_THUMB", MAIN_BALANCE_THUMB_ACTIVE: "MAIN_VOLUME_THUMB_SELECTED" }; const Skin = props => { const { skinImages, skinCursors, skinGenLetterWidths } = props; 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; }` ); } const clipPaths = {}; for (const [regionName, polygons] of Object.entries(props.skinRegion)) { if (polygons) { const matcher = mapRegionNamesToMatcher[regionName]; const id = mapRegionNamesToIds[regionName]; clipPaths[id] = polygons; cssRules.push(`${CSS_PREFIX} ${matcher} { clip-path: url(#${id}); }`); } } // TODO: Find a way to make this declarative. cssRules.push( `#webamp-media-library { background-color: ${props.skinGenExColors.windowBackground}; color: ${props.skinGenExColors.windowText}; }` ); cssRules.push( `#webamp-media-library input { caret-color: ${props.skinGenExColors.windowText}; }` ); cssRules.push( `#webamp-media-library .webamp-media-library-item { color: ${props.skinGenExColors.itemForeground}; background-color: ${props.skinGenExColors.itemBackground}; border-right: 1px solid ${props.skinGenExColors.divider}; border-bottom: 1px solid ${props.skinGenExColors.divider}; }` ); cssRules.push( `#webamp-media-library button { color: ${props.skinGenExColors.buttonText}; }` ); cssRules.push( `#webamp-media-library .webamp-media-library-vertical-divider { }` ); cssRules.push( `#webamp-media-library .webamp-media-library-vertical-divider-line, #webamp-media-library .webamp-media-library-horizontal-divider-line { background-color: ${props.skinGenExColors.divider}; }` ); cssRules.push( `#webamp-media-library .webamp-media-library-table { color: ${props.skinGenExColors.itemForeground}; background-color: ${props.skinGenExColors.itemBackground}; }` ); cssRules.push( `#webamp-media-library .webamp-media-library-table thead { color: ${props.skinGenExColors.listHeaderText}; background-color: ${props.skinGenExColors.listHeaderBackground}; }` ); cssRules.push( `#webamp-media-library .webamp-media-library-table thead th { border-top: 1px solid ${props.skinGenExColors.listHeaderFrameTopAndLeft}; border-left: 1px solid ${ props.skinGenExColors.listHeaderFrameTopAndLeft }; border-bottom: 1px solid ${ props.skinGenExColors.listHeaderFrameBottomAndRight }; border-right: 1px solid ${ props.skinGenExColors.listHeaderFrameBottomAndRight }; }` ); return (
{cssRules.join("\n")} {clipPaths}
); }; export default connect(state => ({ skinImages: state.display.skinImages, skinCursors: state.display.skinCursors, skinRegion: state.display.skinRegion, skinGenLetterWidths: state.display.skinGenLetterWidths, skinGenExColors: state.display.skinGenExColors }))(Skin);