mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-25 02:57:30 +00:00
First stab at media library
This commit is contained in:
parent
0c2b1a1c2f
commit
01f3b1a0ea
18 changed files with 723 additions and 27 deletions
81
css/media-library-window.css
Normal file
81
css/media-library-window.css
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#webamp-media-library {
|
||||
font-size: 10px;
|
||||
font-family: "MS Sans Serif", "Segoe UI", sans-serif;
|
||||
-webkit-font-smoothing: none;
|
||||
padding-right: 2px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
#webamp-media-library input {
|
||||
height: 15px;
|
||||
border: none;
|
||||
background-color: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#webamp-media-library th {
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
padding-left: 5px;
|
||||
white-space: nowrap;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
#webamp-media-library td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
#webamp-media-library ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#webamp-media-library ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#webamp-media-library ul li ul {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#webamp-media-library .webamp-media-library-table {
|
||||
width: 100%;
|
||||
border-spacing: unset;
|
||||
}
|
||||
#webamp-media-library .webamp-media-library-vertical-divider {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
#webamp-media-library .webamp-media-library-vertical-divider-line {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#webamp-media-library .webamp-media-library-horizontal-divider {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
#webamp-media-library .webamp-media-library-horizontal-divider-line {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#webamp-media-library button {
|
||||
border: none;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: inherit;
|
||||
height: 18px;
|
||||
cursor: inherit;
|
||||
/* TODO: I think only the center is supposed to stretch */
|
||||
background-size: 100% 18px;
|
||||
}
|
||||
|
|
@ -110,6 +110,7 @@ export function setSkinFromArrayBuffer(
|
|||
}
|
||||
try {
|
||||
const skinData = await skinParser(arrayBuffer, JSZip);
|
||||
// @ts-ignore TODO: We still need to type skinParser.
|
||||
dispatch({
|
||||
type: SET_SKIN_DATA,
|
||||
data: {
|
||||
|
|
@ -118,7 +119,8 @@ export function setSkinFromArrayBuffer(
|
|||
skinPlaylistStyle: skinData.playlistStyle,
|
||||
skinCursors: skinData.cursors,
|
||||
skinRegion: skinData.region,
|
||||
skinGenLetterWidths: skinData.genLetterWidths
|
||||
skinGenLetterWidths: skinData.genLetterWidths,
|
||||
skinGenExColors: skinData.genExColors
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import WindowManager from "./WindowManager";
|
|||
import MainWindow from "./MainWindow";
|
||||
import PlaylistWindow from "./PlaylistWindow";
|
||||
import EqualizerWindow from "./EqualizerWindow";
|
||||
import MediaLibraryWindow from "./MediaLibraryWindow";
|
||||
import Skin from "./Skin";
|
||||
|
||||
import "../../css/webamp.css";
|
||||
|
|
@ -116,6 +117,12 @@ class App extends React.Component {
|
|||
analyser={media.getAnalyser()}
|
||||
/>
|
||||
);
|
||||
case WINDOWS.MEDIA_LIBRARY:
|
||||
return (
|
||||
<MediaLibraryWindow
|
||||
ref={component => this._gotRef(id, component)}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
if (!w.generic) {
|
||||
throw new Error("Tried to render an unknown window:", id);
|
||||
|
|
@ -123,7 +130,7 @@ class App extends React.Component {
|
|||
const Component = genWindowComponents[id];
|
||||
return (
|
||||
<Component
|
||||
chromeRef={component => this._gotRef(id, component)}
|
||||
ref={component => this._gotRef(id, component)}
|
||||
title={w.title}
|
||||
windowId={id}
|
||||
onFocusedKeyDown={listener => this._emitter.on(id, listener)}
|
||||
|
|
|
|||
33
js/components/MediaLibraryWindow/AlbumsTable.tsx
Normal file
33
js/components/MediaLibraryWindow/AlbumsTable.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import * as React from "react";
|
||||
|
||||
interface Props {}
|
||||
|
||||
export default class AlbumsTable extends React.Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<div className="webamp-media-library-item" style={{ flexGrow: 1 }}>
|
||||
<table
|
||||
className="webamp-media-library-table"
|
||||
style={{ overflow: "scroll" }}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Album</th>
|
||||
<th>Tracks</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>All (1 album)</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ben Mason</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
33
js/components/MediaLibraryWindow/ArtistsTable.tsx
Normal file
33
js/components/MediaLibraryWindow/ArtistsTable.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import * as React from "react";
|
||||
|
||||
interface Props {}
|
||||
|
||||
export default class ArtistsTable extends React.Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<div className="webamp-media-library-item" style={{ height: "100%" }}>
|
||||
<table className="webamp-media-library-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Artist</th>
|
||||
<th>Album</th>
|
||||
<th>Tracks</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>All (1 artist)</td>
|
||||
<td>1</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ben Mason</td>
|
||||
<td>1</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
44
js/components/MediaLibraryWindow/Sidebar.tsx
Normal file
44
js/components/MediaLibraryWindow/Sidebar.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import * as React from "react";
|
||||
|
||||
export default class Sidebar extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
<div className="webamp-media-library-item" style={{ flexGrow: 1 }}>
|
||||
<ul>
|
||||
<li>
|
||||
Local Media
|
||||
<ul>
|
||||
<li>Audio</li>
|
||||
<li>Video</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Playlist</li>
|
||||
<li>
|
||||
Devices
|
||||
<ul>
|
||||
<li>CD E:</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Internet Radio</li>
|
||||
<li>Internet TV</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: 1
|
||||
}}
|
||||
>
|
||||
Library
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
69
js/components/MediaLibraryWindow/TracksTable.tsx
Normal file
69
js/components/MediaLibraryWindow/TracksTable.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import * as React from "react";
|
||||
|
||||
interface Props {}
|
||||
|
||||
export default class TracksTable extends React.Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 2
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ paddingBottom: 3, display: "flex", flexDirection: "row" }}
|
||||
>
|
||||
<span>Search:</span>
|
||||
<input
|
||||
style={{ marginLeft: 12, flexGrow: 1 }}
|
||||
type="text"
|
||||
className="webamp-media-library-item"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{ flexGrow: 1, overflowY: "scroll" }}
|
||||
className="webamp-media-library-item"
|
||||
>
|
||||
<table
|
||||
className="webamp-media-library-table"
|
||||
style={{ overflow: "scroll" }}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Artist</th>
|
||||
<th>Title</th>
|
||||
<th>Album</th>
|
||||
<th>Length</th>
|
||||
<th>Track #</th>
|
||||
<th>Genere</th>
|
||||
<th>Year</th>
|
||||
<th>Filename</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Ben Mason</td>
|
||||
<td>Easy</td>
|
||||
<td>Bad Pands</td>
|
||||
<td>3:25</td>
|
||||
<td>1</td>
|
||||
<td>Primus</td>
|
||||
<td>2001</td>
|
||||
<td>BenMason-Easy.mp3</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style={{ marginTop: 2 }}>
|
||||
<button>Play</button>
|
||||
<button>Enqueue</button>
|
||||
<button>Play all</button>
|
||||
<button>Enqueue all</button>
|
||||
<span>1 item [3:25]</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
148
js/components/MediaLibraryWindow/index.tsx
Normal file
148
js/components/MediaLibraryWindow/index.tsx
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import GenWindow from "../GenWindow";
|
||||
import { WINDOWS } from "../../constants";
|
||||
import "../../../css/media-library-window.css";
|
||||
import { AppState, SkinGenExColors } from "../../types";
|
||||
import * as Selectors from "../../selectors";
|
||||
import Sidebar from "./Sidebar";
|
||||
import ArtistsTable from "./ArtistsTable";
|
||||
import AlbumsTable from "./AlbumsTable";
|
||||
import TracksTable from "./TracksTable";
|
||||
|
||||
interface Props {
|
||||
skinGenExColors: SkinGenExColors;
|
||||
}
|
||||
|
||||
interface State {
|
||||
sidebarWidth: number;
|
||||
topPlaylistSectionHeight: number;
|
||||
artistsPanelWidth: number;
|
||||
}
|
||||
|
||||
class MediaLibraryWindow extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
sidebarWidth: 100,
|
||||
topPlaylistSectionHeight: 200,
|
||||
artistsPanelWidth: 150
|
||||
};
|
||||
}
|
||||
|
||||
_onMouseMove(cb: (e: MouseEvent) => void) {
|
||||
const handleMouseUp = () => {
|
||||
document.removeEventListener("mousemove", cb);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
document.addEventListener("mousemove", cb);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
}
|
||||
|
||||
_handleSidebarMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const { pageX: startX } = e;
|
||||
const initialWidth = this.state.sidebarWidth;
|
||||
this._onMouseMove((moveEvent: MouseEvent) => {
|
||||
this.setState({
|
||||
sidebarWidth: initialWidth + moveEvent.pageX - startX
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
_handlePlaylistResizeMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const { pageY: startY } = e;
|
||||
const initialHeight = this.state.topPlaylistSectionHeight;
|
||||
this._onMouseMove((moveEvent: MouseEvent) => {
|
||||
this.setState({
|
||||
topPlaylistSectionHeight: initialHeight + moveEvent.pageY - startY
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
_handleArtistsResizeMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const { pageX: startX } = e;
|
||||
const initialWidth = this.state.artistsPanelWidth;
|
||||
this._onMouseMove((moveEvent: MouseEvent) => {
|
||||
this.setState({
|
||||
artistsPanelWidth: initialWidth + moveEvent.pageX - startX
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const colors = this.props.skinGenExColors;
|
||||
return (
|
||||
<GenWindow title={"Winamp Library"} windowId={WINDOWS.MEDIA_LIBRARY}>
|
||||
{({ height, width }: { height: number; width: number }) => (
|
||||
<div
|
||||
id="webamp-media-library"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<div style={{ width: this.state.sidebarWidth }}>
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div
|
||||
className="webamp-media-library-vertical-divider"
|
||||
onMouseDown={this._handleSidebarMouseDown}
|
||||
>
|
||||
<div className="webamp-media-library-vertical-divider-line" />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexShrink: 1,
|
||||
flexGrow: 1,
|
||||
// https://stackoverflow.com/a/35609992/1263117
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
flexShrink: 0,
|
||||
height: this.state.topPlaylistSectionHeight
|
||||
}}
|
||||
>
|
||||
<div style={{ width: this.state.artistsPanelWidth }}>
|
||||
<ArtistsTable />
|
||||
</div>
|
||||
<div
|
||||
className="webamp-media-library-vertical-divider"
|
||||
onMouseDown={this._handleArtistsResizeMouseDown}
|
||||
>
|
||||
<div className="webamp-media-library-vertical-divider-line" />
|
||||
</div>
|
||||
<AlbumsTable />
|
||||
</div>
|
||||
<div
|
||||
className="webamp-media-library-horizontal-divider"
|
||||
onMouseDown={this._handlePlaylistResizeMouseDown}
|
||||
>
|
||||
<div className="webamp-media-library-horizontal-divider-line" />
|
||||
</div>
|
||||
<TracksTable />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</GenWindow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
skinGenExColors: Selectors.getSkinGenExColors(state)
|
||||
};
|
||||
};
|
||||
export default connect(mapStateToProps)(MediaLibraryWindow);
|
||||
|
|
@ -125,11 +125,7 @@ class PresetsLoader extends React.Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<GenWindow
|
||||
ref={this.props.chromeRef}
|
||||
title={this.props.title}
|
||||
windowId={this.props.windowId}
|
||||
>
|
||||
<GenWindow title={this.props.title} windowId={this.props.windowId}>
|
||||
{({ height, width }) => (
|
||||
<ContextMenuWrapper
|
||||
onDoubleClick={this._handleRequestFullsceen}
|
||||
|
|
|
|||
|
|
@ -146,6 +146,72 @@ const Skin = props => {
|
|||
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 (
|
||||
<div>
|
||||
<Css>{cssRules.join("\n")}</Css>
|
||||
|
|
@ -158,5 +224,6 @@ export default connect(state => ({
|
|||
skinImages: state.display.skinImages,
|
||||
skinCursors: state.display.skinCursors,
|
||||
skinRegion: state.display.skinRegion,
|
||||
skinGenLetterWidths: state.display.skinGenLetterWidths
|
||||
skinGenLetterWidths: state.display.skinGenLetterWidths,
|
||||
skinGenExColors: state.display.skinGenExColors
|
||||
}))(Skin);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ export const BANDS: Band[] = [
|
|||
export const WINDOWS = {
|
||||
MAIN: "main",
|
||||
PLAYLIST: "playlist",
|
||||
EQUALIZER: "equalizer"
|
||||
EQUALIZER: "equalizer",
|
||||
MEDIA_LIBRARY: "mediaLibrary"
|
||||
};
|
||||
|
||||
export const LOAD_STYLE: Record<LoadStyle, LoadStyle> = {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import {
|
|||
Cursors,
|
||||
SkinRegion,
|
||||
GenLetterWidths,
|
||||
PlaylistStyle
|
||||
PlaylistStyle,
|
||||
SkinGenExColors
|
||||
} from "../types";
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ export interface DisplayState {
|
|||
skinGenLetterWidths: GenLetterWidths | null;
|
||||
skinColors: string[]; // Theoretically this could be a tuple of a specific length
|
||||
skinPlaylistStyle: PlaylistStyle | null;
|
||||
skinGenExColors: SkinGenExColors;
|
||||
working: boolean;
|
||||
closed: boolean;
|
||||
loading: boolean;
|
||||
|
|
@ -51,6 +53,31 @@ export interface DisplayState {
|
|||
dummyVizData: null; // TODO: Figure out what kind of data this actually is.
|
||||
}
|
||||
|
||||
const defaultSkinGenExColors = {
|
||||
itemBackground: "rgb(0,0,0)",
|
||||
itemForeground: "rgb(0,255,0)",
|
||||
windowBackground: "rgb(56,55,87)",
|
||||
buttonText: "rgb(57,57,66)",
|
||||
windowText: "rgb(255,255,255)",
|
||||
divider: "rgb(117,116,139)",
|
||||
playlistSelection: "rgb(0,0,198)",
|
||||
listHeaderBackground: "rgb(72,72,120)",
|
||||
listHeaderText: "rgb(255,255,255)",
|
||||
listHeaderFrameTopAndLeft: "rgb(108,108,180)",
|
||||
listHeaderFrameBottomAndRight: "rgb(36,36,60)",
|
||||
listHeaderFramePressed: "rgb(18,18,30)",
|
||||
listHeaderDeadArea: "rgb(36,36,60)",
|
||||
scrollbarOne: "rgb(36,36,60)",
|
||||
scrollbarTwo: "rgb(36,36,60)",
|
||||
pressedScrollbarOne: "rgb(121,130,150)",
|
||||
pressedScrollbarTwo: "rgb(78,88,110)",
|
||||
scrollbarDeadArea: "rgb(36,36,60)",
|
||||
listTextHighlighted: "rgb(0,198,255)",
|
||||
listTextHighlightedBackground: "rgb(0,198,255)",
|
||||
listTextSelected: "rgb(0,198,255)",
|
||||
listTextSelectedBackground: "rgb(0,198,255)"
|
||||
};
|
||||
|
||||
const defaultDisplayState = {
|
||||
doubled: false,
|
||||
marqueeStep: 0,
|
||||
|
|
@ -68,6 +95,7 @@ const defaultDisplayState = {
|
|||
dummyVizData: null,
|
||||
playlistScrollPosition: 0,
|
||||
skinGenLetterWidths: null, // TODO: Get the default value for this?
|
||||
skinGenExColors: defaultSkinGenExColors,
|
||||
additionalVisualizers: [],
|
||||
zIndex: 0
|
||||
};
|
||||
|
|
@ -84,7 +112,8 @@ const display = (
|
|||
skinCursors,
|
||||
skinPlaylistStyle,
|
||||
skinRegion,
|
||||
skinGenLetterWidths
|
||||
skinGenLetterWidths,
|
||||
skinGenExColors
|
||||
} = defaultDisplayState;
|
||||
return {
|
||||
...state,
|
||||
|
|
@ -93,7 +122,8 @@ const display = (
|
|||
skinCursors,
|
||||
skinPlaylistStyle,
|
||||
skinRegion,
|
||||
skinGenLetterWidths
|
||||
skinGenLetterWidths,
|
||||
skinGenExColors
|
||||
};
|
||||
|
||||
case TOGGLE_DOUBLESIZE_MODE:
|
||||
|
|
@ -126,7 +156,8 @@ const display = (
|
|||
skinPlaylistStyle: data.skinPlaylistStyle,
|
||||
skinCursors: data.skinCursors,
|
||||
skinRegion: data.skinRegion,
|
||||
skinGenLetterWidths: data.skinGenLetterWidths
|
||||
skinGenLetterWidths: data.skinGenLetterWidths,
|
||||
skinGenExColors: data.skinGenExColors || defaultSkinGenExColors
|
||||
};
|
||||
case TOGGLE_VISUALIZER_STYLE:
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -96,6 +96,19 @@ const defaultWindowsState: WindowsState = {
|
|||
generic: false,
|
||||
hotkey: "Alt+E",
|
||||
position: { x: 0, y: 0 }
|
||||
},
|
||||
[WINDOWS.MEDIA_LIBRARY]: {
|
||||
title: "Winamp Library",
|
||||
size: [0, 0],
|
||||
open: true,
|
||||
hidden: false,
|
||||
shade: false,
|
||||
canResize: true,
|
||||
canShade: false,
|
||||
canDouble: false,
|
||||
generic: false,
|
||||
hotkey: "Alt+E",
|
||||
position: { x: 0, y: 0 }
|
||||
}
|
||||
},
|
||||
browserWindowSize: { width: 0, height: 0 }
|
||||
|
|
|
|||
|
|
@ -430,6 +430,10 @@ export const getSkinPlaylistStyle = (state: AppState): PlaylistStyle => {
|
|||
);
|
||||
};
|
||||
|
||||
export const getSkinGenExColors = (state: AppState) => {
|
||||
return state.display.skinGenExColors;
|
||||
};
|
||||
|
||||
export const getVisualizerStyle = (state: AppState) =>
|
||||
fromDisplay.getVisualizerStyle(state.display);
|
||||
|
||||
|
|
|
|||
102
js/skinParser.js
102
js/skinParser.js
|
|
@ -1,7 +1,7 @@
|
|||
import SKIN_SPRITES from "./skinSprites";
|
||||
import regionParser from "./regionParser";
|
||||
import { LETTERS, DEFAULT_SKIN } from "./constants";
|
||||
import { parseViscolors, parseIni, getFileExtension } from "./utils";
|
||||
import { parseViscolors, parseIni, getFileExtension, objectMap } from "./utils";
|
||||
|
||||
const shallowMerge = objs =>
|
||||
objs.reduce((prev, img) => Object.assign(prev, img), {});
|
||||
|
|
@ -145,16 +145,18 @@ async function genPlaylistStyle(zip) {
|
|||
|
||||
// Winamp seems to permit colors that contain too many characters.
|
||||
// For compatibility with existing skins, we normalize them here.
|
||||
["normal", "current", "normalbg", "selectedbg"].forEach(colorKey => {
|
||||
let color = data[colorKey];
|
||||
if (!color) {
|
||||
return;
|
||||
["normal", "current", "normalbg", "selectedbg", "mbFG", "mbBG"].forEach(
|
||||
colorKey => {
|
||||
let color = data[colorKey];
|
||||
if (!color) {
|
||||
return;
|
||||
}
|
||||
if (color[0] !== "#") {
|
||||
color = `#${color}`;
|
||||
}
|
||||
data[colorKey] = color.slice(0, 7);
|
||||
}
|
||||
if (color[0] !== "#") {
|
||||
color = `#${color}`;
|
||||
}
|
||||
data[colorKey] = color.slice(0, 7);
|
||||
});
|
||||
);
|
||||
|
||||
return { ...DEFAULT_SKIN.playlistStyle, ...data };
|
||||
}
|
||||
|
|
@ -233,24 +235,97 @@ async function genGenTextSprites(zip) {
|
|||
return [letterWidths, getSpriteUrisFromImg(img, sprites)];
|
||||
}
|
||||
|
||||
async function genGenExColors(zip) {
|
||||
const img = await genImgFromFilename(zip, "GENEX");
|
||||
if (img == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
context.drawImage(img, 0, 0);
|
||||
|
||||
const getColorAt = x =>
|
||||
`rgb(${context
|
||||
.getImageData(x, 0, 1, 1)
|
||||
// Discard the alpha channel
|
||||
.data.slice(0, 3)
|
||||
.join(",")})`;
|
||||
|
||||
const colors = {
|
||||
// (1) x=48: item background (background to edits, listviews, etc.)
|
||||
itemBackground: 48,
|
||||
// (2) x=50: item foreground (text colour of edits, listviews, etc.)
|
||||
itemForeground: 50,
|
||||
// (3) x=52: window background (used to set the bg color for the dialog)
|
||||
windowBackground: 52,
|
||||
// (4) x=54: button text colour
|
||||
buttonText: 54,
|
||||
// (5) x=56: window text colour
|
||||
windowText: 56,
|
||||
// (6) x=58: colour of dividers and sunken borders
|
||||
divider: 58,
|
||||
// (7) x=60: selection colour for entries inside playlists (nothing else yet)
|
||||
playlistSelection: 60,
|
||||
// (8) x=62: listview header background colour
|
||||
listHeaderBackground: 62,
|
||||
// (9) x=64: listview header text colour
|
||||
listHeaderText: 64,
|
||||
// (10) x=66: listview header frame top and left colour
|
||||
listHeaderFrameTopAndLeft: 66,
|
||||
// (11) x=68: listview header frame bottom and right colour
|
||||
listHeaderFrameBottomAndRight: 68,
|
||||
// (12) x=70: listview header frame colour, when pressed
|
||||
listHeaderFramePressed: 70,
|
||||
// (13) x=72: listview header dead area colour
|
||||
listHeaderDeadArea: 72,
|
||||
// (14) x=74: scrollbar colour #1
|
||||
scrollbarOne: 74,
|
||||
// (15) x=76: scrollbar colour #2
|
||||
scrollbarTwo: 76,
|
||||
// (16) x=78: pressed scrollbar colour #1
|
||||
pressedScrollbarOne: 78,
|
||||
// (17) x=80: pressed scrollbar colour #2
|
||||
pressedScrollbarTwo: 80,
|
||||
// (18) x=82: scrollbar dead area colour
|
||||
scrollbarDeadArea: 82,
|
||||
// (19) x=84 List view text colour highlighted
|
||||
listTextHighlighted: 84,
|
||||
// (20) x=86 List view background colour highlighted
|
||||
listTextHighlightedBackground: 86,
|
||||
// (21) x=88 List view text colour selected
|
||||
listTextSelected: 88,
|
||||
// (22) x=90 List view background colour selected
|
||||
listTextSelectedBackground: 90
|
||||
};
|
||||
|
||||
return objectMap(colors, getColorAt);
|
||||
}
|
||||
|
||||
// A promise that, given an array buffer returns a skin style object
|
||||
async function skinParser(zipFileBuffer, JSZip) {
|
||||
const zip = await JSZip.loadAsync(zipFileBuffer);
|
||||
|
||||
const [
|
||||
colors,
|
||||
playlistStyle,
|
||||
images,
|
||||
cursors,
|
||||
region,
|
||||
genTextSprites
|
||||
genTextSprites,
|
||||
genExColors
|
||||
] = await Promise.all([
|
||||
genVizColors(zip),
|
||||
genPlaylistStyle(zip),
|
||||
genImages(zip),
|
||||
genCursors(zip),
|
||||
genRegion(zip),
|
||||
genGenTextSprites(zip)
|
||||
genGenTextSprites(zip),
|
||||
genGenExColors(zip)
|
||||
]);
|
||||
console.log(genExColors);
|
||||
|
||||
const [genLetterWidths, genTextImages] = genTextSprites || [null, {}];
|
||||
|
||||
|
|
@ -260,7 +335,8 @@ async function skinParser(zipFileBuffer, JSZip) {
|
|||
images: { ...images, ...genTextImages },
|
||||
genLetterWidths,
|
||||
cursors,
|
||||
region
|
||||
region,
|
||||
genExColors
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -315,7 +315,8 @@ export const imageSelectors: Selectors = {
|
|||
GEN_MIDDLE_LEFT_BOTTOM: [".gen-window .gen-middle-left-bottom"],
|
||||
GEN_MIDDLE_RIGHT: [".gen-window .gen-middle-right"],
|
||||
GEN_MIDDLE_RIGHT_BOTTOM: [".gen-window .gen-middle-right-bottom"],
|
||||
GEN_CLOSE_SELECTED: [".gen-window .gen-close:active"]
|
||||
GEN_CLOSE_SELECTED: [".gen-window .gen-close:active"],
|
||||
GENEX_BUTTON_BACKGROUND_UNPRESSED: ["#webamp-media-library button"]
|
||||
};
|
||||
|
||||
Object.keys(FONT_LOOKUP).forEach(character => {
|
||||
|
|
|
|||
|
|
@ -722,5 +722,69 @@ export default {
|
|||
{ name: "GEN_MIDDLE_RIGHT", x: 139, y: 42, width: 8, height: 29 },
|
||||
{ name: "GEN_MIDDLE_RIGHT_BOTTOM", x: 170, y: 42, width: 8, height: 24 },
|
||||
{ name: "GEN_CLOSE_SELECTED", x: 148, y: 42, width: 9, height: 9 }
|
||||
],
|
||||
GENEX: [
|
||||
{
|
||||
name: "GENEX_BUTTON_BACKGROUND_UNPRESSED",
|
||||
x: 0,
|
||||
y: 0,
|
||||
height: 15,
|
||||
width: 47
|
||||
},
|
||||
{
|
||||
name: "GENEX_BUTTON_BACKGROUND_PRESSED",
|
||||
x: 0,
|
||||
y: 1,
|
||||
height: 15,
|
||||
width: 47
|
||||
},
|
||||
{ name: "GENEX_SCROLL_UP_UNPRESSED", x: 0, y: 31, height: 14, width: 14 },
|
||||
{
|
||||
name: "GENEX_SCROLL_DOWN_UNPRESSED",
|
||||
x: 14,
|
||||
y: 31,
|
||||
height: 14,
|
||||
width: 14
|
||||
},
|
||||
{ name: "GENEX_SCROLL_UP_PRESSED", x: 28, y: 31, height: 14, width: 14 },
|
||||
{ name: "GENEX_SCROLL_DOWN_PRESSED", x: 42, y: 31, height: 14, width: 14 },
|
||||
{ name: "GENEX_SCROLL_LEFT_UNPRESSED", x: 0, y: 45, height: 14, width: 14 },
|
||||
{
|
||||
name: "GENEX_SCROLL_RIGHT_UNPRESSED",
|
||||
x: 14,
|
||||
y: 45,
|
||||
height: 14,
|
||||
width: 14
|
||||
},
|
||||
{ name: "GENEX_SCROLL_LEFT_PRESSED", x: 28, y: 45, height: 14, width: 14 },
|
||||
{ name: "GENEX_SCROLL_RIGHT_PRESSED", x: 42, y: 45, height: 14, width: 14 },
|
||||
{
|
||||
name: "GENEX_VERTICAL_SCROLL_HANDLE_UNPRESSED",
|
||||
x: 56,
|
||||
y: 31,
|
||||
height: 28,
|
||||
width: 14
|
||||
},
|
||||
{
|
||||
name: "GENEX_VERTICAL_SCROLL_HANDLE_PRESSED",
|
||||
x: 70,
|
||||
y: 31,
|
||||
height: 28,
|
||||
width: 14
|
||||
},
|
||||
{
|
||||
name: "GENEX_HORIZONTAL_SCROLL_HANDLE_UNPRESSED",
|
||||
x: 84,
|
||||
y: 31,
|
||||
height: 14,
|
||||
width: 28
|
||||
},
|
||||
{
|
||||
name: "GENEX_HORIZONTAL_SCROLL_HANDLE_PRESSED",
|
||||
x: 84,
|
||||
y: 45,
|
||||
height: 14,
|
||||
width: 28
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
26
js/types.ts
26
js/types.ts
|
|
@ -50,6 +50,31 @@ export type SkinImages = { [sprite: string]: string };
|
|||
// TODO: type these keys
|
||||
export type SkinRegion = { [windowName: string]: string[] };
|
||||
|
||||
export interface SkinGenExColors {
|
||||
itemBackground: string;
|
||||
itemForeground: string;
|
||||
windowBackground: string;
|
||||
buttonText: string;
|
||||
windowText: string;
|
||||
divider: string;
|
||||
playlistSelection: string;
|
||||
listHeaderBackground: string;
|
||||
listHeaderText: string;
|
||||
listHeaderFrameTopAndLeft: string;
|
||||
listHeaderFrameBottomAndRight: string;
|
||||
listHeaderFramePressed: string;
|
||||
listHeaderDeadArea: string;
|
||||
scrollbarOne: string;
|
||||
scrollbarTwo: string;
|
||||
pressedScrollbarOne: string;
|
||||
pressedScrollbarTwo: string;
|
||||
scrollbarDeadArea: string;
|
||||
listTextHighlighted: string;
|
||||
listTextHighlightedBackground: string;
|
||||
listTextSelected: string;
|
||||
listTextSelectedBackground: string;
|
||||
}
|
||||
|
||||
export type WindowId = string;
|
||||
|
||||
// TODO: Fill these out once we actually use them.
|
||||
|
|
@ -60,6 +85,7 @@ type SkinData = {
|
|||
skinCursors: Cursors;
|
||||
skinRegion: SkinRegion;
|
||||
skinGenLetterWidths: GenLetterWidths;
|
||||
skinGenExColors: SkinGenExColors | null;
|
||||
};
|
||||
|
||||
export type Action =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue