diff --git a/css/base-skin.css b/css/base-skin.css index 3568eb3b..bf0a1257 100644 --- a/css/base-skin.css +++ b/css/base-skin.css @@ -225,6 +225,7 @@ #webamp .gen-window .gen-middle-right {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAdCAYAAACXFC2jAAAALElEQVQ4T2PMyqr6f2D/MQYYcHC0YoDxP395y8A4qoBhNBwg6WM0HAZXOAAAZMQtu5vd5AgAAAAASUVORK5CYII=)} #webamp .gen-window .gen-middle-right-bottom {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAYCAYAAADH2bwQAAAAKUlEQVQ4T2PMyqr6f2D/MQYYcHC0YoDxP395y8A4qoBhNBwg6WMEhQMAnDr5qQGVDEcAAAAASUVORK5CYII=)} #webamp .gen-window .gen-close:active {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAfElEQVQoU2PcMjH5PwMBwAhStOPMA4bvjD8Yrl9+B1euqSvEwPmfg8HDRIEBrGj92RtgBUY6UmDFIMlzV54xgBQGGmtAFLUtOAZWAJIIt1VkWHn4PpxflWCFUATSpSfAz5A3cSvDpHxvhksfPoJNR1FE0CSi3ESU7wiFEwDes2XpVzKmTwAAAABJRU5ErkJggg==)} +#webamp #webamp-media-library button {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAPCAYAAAB0i5aaAAAAhUlEQVRIS2Ncu/XYf4YhCK7fv8/ACHK8k5flkHL+vm3HGZauW4tw/Lnz14eEBz48/8AACvVjx44NTcefO3+O4fy166OOp2tyAyWb0ZCna5BDLRsN+YEIdZCdoyE/qEIeVOUOFYBSVIIcDapyv394P1TcD6lhW6Ys/Q/yyY/vP4aMw2EOBQAnuriposTxowAAAABJRU5ErkJggg==)} #webamp .character-48 {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAGCAYAAAAL+1RLAAAALklEQVQYV2NkYGD4z/CIAQLkIBQjwyOG/zAOWFIOWRBJNUQlTCuGSpAEXBCLRQAyQhABbALQ/gAAAABJRU5ErkJggg==)} #webamp .character-49 {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAGCAYAAAAL+1RLAAAAJElEQVQYV2NkYGD4zwACjxgYGOTALAZGsCBIAARQBLGqJFsQAB97DAFASJPiAAAAAElFTkSuQmCC)} #webamp .character-50 {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAGCAYAAAAL+1RLAAAAL0lEQVQYV2NkeMTwnwEG5CAMRgYGJMFHDAwMcngFQSpAAK4dZCaUAzOaEcUiqGoALSMMAep9mTsAAAAASUVORK5CYII=)} @@ -415,3 +416,40 @@ #webamp #volume {cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAADoAgAAFgAAACgAAAAgAAAAQAAAAAEABAAAAAAAgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAwMDAAICAgAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACHcAAAAAAAAAAAAAAAAAAAj3AAAAAAAAAAAAAAAAAACPcAAHAAAAAHAAAAAAAAAAj3AAdwAHcAB3AAAAAAgACPcAAP8AD/AA/wAAAAAIcAj3AAAPAAAAAPAAAAAACPcPcAAAAAAAAAAAAAAAAAj/cAAAAAAAAAAAAAAAAAAI//d3dwAAAAAAAAAAAAAACP/3d3AAAAAAAAAAAAAAAAj/d3cAAAAAAAAAAAAAAAAI/3dwAAAAAAAAAAAAAAAACPd3AAAAAAAAAAAAAAAAAAj3cAAAAAAAAAAAAAAAAAAIdwAAAAAAAAAAAAAAAAAACHAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////////////////////////////////////////////////////////////j////w////8M/P/+GMx/3hCEP8wwhD/EOMx/wHz8/8AH///AD///wB///8A////Af///wP///8H////D////x////8/////f////w==), auto} #webamp #volume input {cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAADoAgAAFgAAACgAAAAgAAAAQAAAAAEABAAAAAAAgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAwMDAAICAgAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACHcAAAAAAAAAAAAAAAAAAAj3AAAAAAAAAAAAAAAAAACPcAAHAAAAAHAAAAAAAAAAj3AAdwAHcAB3AAAAAAgACPcAAP8AD/AA/wAAAAAIcAj3AAAPAAAAAPAAAAAACPcPcAAAAAAAAAAAAAAAAAj/cAAAAAAAAAAAAAAAAAAI//d3dwAAAAAAAAAAAAAACP/3d3AAAAAAAAAAAAAAAAj/d3cAAAAAAAAAAAAAAAAI/3dwAAAAAAAAAAAAAAAACPd3AAAAAAAAAAAAAAAAAAj3cAAAAAAAAAAAAAAAAAAIdwAAAAAAAAAAAAAAAAAACHAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////////////////////////////////////////////////////////////j////w////8M/P/+GMx/3hCEP8wwhD/EOMx/wHz8/8AH///AD///wB///8A////Af///wP///8H////D////x////8/////f////w==), auto} #webamp #balance {cursor: url(data:image/x-win-bitmap;base64,AAACAAEAICAAAAAAAADoAgAAFgAAACgAAAAgAAAAQAAAAAEABAAAAAAAgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAwMDAAICAgAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACHcAAAAAAAAAAAAAAAAAAAj3AAAAAAAAAAAAAAAAAACPcAAHAAAAAHAAAAAAAAAAj3AAdwAHcAB3AAAAAAgACPcAAP8AD/AA/wAAAAAIcAj3AAAPAAAAAPAAAAAACPcPcAAAAAAAAAAAAAAAAAj/cAAAAAAAAAAAAAAAAAAI//d3dwAAAAAAAAAAAAAACP/3d3AAAAAAAAAAAAAAAAj/d3cAAAAAAAAAAAAAAAAI/3dwAAAAAAAAAAAAAAAACPd3AAAAAAAAAAAAAAAAAAj3cAAAAAAAAAAAAAAAAAAIdwAAAAAAAAAAAAAAAAAACHAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////////////////////////////////////////////////////////////j////w////8M/P/+GMx/3hCEP8wwhD/EOMx/wHz8/8AH///AD///wB///8A////Af///wP///8H////D////x////8/////f////w==), auto} +#webamp-media-library { + background-color: rgb(56,55,87); + color: rgb(255,255,255); + } +#webamp-media-library input { + caret-color: rgb(255,255,255); + } +#webamp-media-library .webamp-media-library-item { + color: rgb(0,255,0); + background-color: rgb(0,0,0); + border-right: 1px solid rgb(117,116,139); + border-bottom: 1px solid rgb(117,116,139); + } +#webamp-media-library button { + color: rgb(57,57,66); + } +#webamp-media-library .webamp-media-library-vertical-divider { + } +#webamp-media-library .webamp-media-library-vertical-divider-line, + #webamp-media-library .webamp-media-library-horizontal-divider-line + { + background-color: rgb(117,116,139); + } +#webamp-media-library .webamp-media-library-table { + color: rgb(0,255,0); + background-color: rgb(0,0,0); + } +#webamp-media-library .webamp-media-library-table thead { + color: rgb(255,255,255); + background-color: rgb(72,72,120); + } +#webamp-media-library .webamp-media-library-table thead th { + border-top: 1px solid rgb(108,108,180); + border-left: 1px solid rgb(108,108,180); + border-bottom: 1px solid rgb(36,36,60); + border-right: 1px solid rgb(36,36,60); + } diff --git a/css/media-library-window.css b/css/media-library-window.css new file mode 100644 index 00000000..7382d77f --- /dev/null +++ b/css/media-library-window.css @@ -0,0 +1,102 @@ +#webamp-media-library { + font-size: 11px; + font-family: "MS Sans Serif", "Segoe UI", sans-serif; + -webkit-font-smoothing: none; + padding-right: 2px; + padding-bottom: 3px; + user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + -webkit-touch-callout: none; + -o-user-select: none; + -moz-user-select: none; +} + +#webamp-media-library input { + height: 15px; + border: none; + background-color: inherit; + padding: 0; +} + +#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-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 .webamp-media-library-item { + overflow: hidden; + white-space: nowrap; +} + +#webamp-media-library .webamp-media-library-table .library-table-heading > div { + margin-right: 1px; + margin-bottom: 1px; +} + +#webamp-media-library + .webamp-media-library-table + .library-table-heading + > div:active { + margin-right: 0px; + margin-bottom: 0px; + margin-top: 1px; + margin-left: 1px; + border: 1px solid transparent; +} + +#webamp-media-library .library-button { + border: none; + padding: 0; + cursor: pointer; + outline: inherit; + height: 15px; + cursor: inherit; + display: inline-grid; + grid-template-columns: 4px auto 4px; + margin-right: 4px; + align-items: stretch; +} + +#webamp-media-library .library-button-center { + text-align: center; +} + +#webamp-media-library-track-summary-duration { + /* This plus the margin-right on the button add up to 8px; */ + margin-left: 4px; +} diff --git a/demo/js/index.js b/demo/js/index.js index 3b3660cb..11667394 100644 --- a/demo/js/index.js +++ b/demo/js/index.js @@ -5,6 +5,7 @@ import React from "react"; import ReactDOM from "react-dom"; import createMiddleware from "raven-for-redux"; import isButterchurnSupported from "butterchurn/lib/isSupported.min"; +import base from "../../skins/base-2.91-png.wsz"; import { WINDOWS } from "../../js/constants"; import * as Selectors from "../../js/selectors"; @@ -53,9 +54,17 @@ const MIN_MILKDROP_WIDTH = 725; let screenshot = false; let skinUrl = configSkinUrl; +let library = false; if ("URLSearchParams" in window) { const params = new URLSearchParams(location.search); screenshot = params.get("screenshot"); + library = Boolean(params.get("library")); + // The default skin CSS baked into the JS library does not have full Media + // Library support. If we are going to show the library we have to load a + // skin at start time. + if (library && skinUrl == null) { + skinUrl = base; + } skinUrl = params.get("skinUrl") || skinUrl; } @@ -114,13 +123,21 @@ Raven.context(async () => { let __initialWindowLayout = null; if (isButterchurnSupported()) { const startWithMilkdropHidden = + library || document.body.clientWidth < MIN_MILKDROP_WIDTH || skinUrl != null || screenshot; __butterchurnOptions = getButterchurnOptions(startWithMilkdropHidden); - if (startWithMilkdropHidden) { + if (library) { + __initialWindowLayout = { + [WINDOWS.MAIN]: { position: { x: 0, y: 0 } }, + [WINDOWS.EQUALIZER]: { position: { x: 0, y: 116 } }, + [WINDOWS.PLAYLIST]: { position: { x: 0, y: 232 }, size: [0, 4] }, + [WINDOWS.MEDIA_LIBRARY]: { position: { x: 275, y: 0 }, size: [7, 12] }, + }; + } else if (startWithMilkdropHidden) { __initialWindowLayout = { [WINDOWS.MAIN]: { position: { x: 0, y: 0 } }, [WINDOWS.EQUALIZER]: { position: { x: 0, y: 116 } }, @@ -153,6 +170,7 @@ Raven.context(async () => { import( /* webpackChunkName: "music-metadata-browser" */ "music-metadata-browser/dist/index" ), + __enableMediaLibrary: library, __initialWindowLayout, __initialState: screenshot ? screenshotInitialState : initialState, __butterchurnOptions, diff --git a/js/actionTypes.ts b/js/actionTypes.ts index 2e129a1a..8daa196b 100644 --- a/js/actionTypes.ts +++ b/js/actionTypes.ts @@ -73,6 +73,7 @@ export const LOAD_SERIALIZED_STATE = "LOAD_SERIALIZED_STATE"; export const RESET_WINDOW_SIZES = "RESET_WINDOW_SIZES"; export const BROWSER_WINDOW_SIZE_CHANGED = "BROWSER_WINDOW_SIZE_CHANGED"; export const LOAD_DEFAULT_SKIN = "LOAD_DEFAULT_SKIN"; +export const ENABLE_MEDIA_LIBRARY = "ENABLE_MEDIA_LIBRARY"; export const ENABLE_MILKDROP = "ENABLE_MILKDROP"; export const SET_MILKDROP_DESKTOP = "SET_MILKDROP_DESKTOP"; export const SET_VISUALIZER_STYLE = "SET_VISUALIZER_STYLE"; diff --git a/js/components/App.tsx b/js/components/App.tsx index e40ac16a..0c60237a 100644 --- a/js/components/App.tsx +++ b/js/components/App.tsx @@ -20,6 +20,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"; @@ -122,6 +123,8 @@ class App extends React.Component { return ; case WINDOWS.PLAYLIST: return ; + case WINDOWS.MEDIA_LIBRARY: + return ; case WINDOWS.MILKDROP: return ; default: diff --git a/js/components/MediaLibraryWindow/AlbumsTable.tsx b/js/components/MediaLibraryWindow/AlbumsTable.tsx new file mode 100644 index 00000000..6db9dd98 --- /dev/null +++ b/js/components/MediaLibraryWindow/AlbumsTable.tsx @@ -0,0 +1,17 @@ +import * as React from "react"; +import LibraryTable from "./LibraryTable"; + +const AlbumsTable = React.memo(() => { + return ( + + ); +}); + +export default AlbumsTable; diff --git a/js/components/MediaLibraryWindow/ArtistsTable.tsx b/js/components/MediaLibraryWindow/ArtistsTable.tsx new file mode 100644 index 00000000..7e6e58b2 --- /dev/null +++ b/js/components/MediaLibraryWindow/ArtistsTable.tsx @@ -0,0 +1,19 @@ +import * as React from "react"; +import LibraryTable from "./LibraryTable"; + +interface Props {} + +export default class ArtistsTable extends React.Component { + render() { + return ( + + ); + } +} diff --git a/js/components/MediaLibraryWindow/LibraryButton.tsx b/js/components/MediaLibraryWindow/LibraryButton.tsx new file mode 100644 index 00000000..14dedcc4 --- /dev/null +++ b/js/components/MediaLibraryWindow/LibraryButton.tsx @@ -0,0 +1,16 @@ +import React from "react"; +type Props = React.HTMLAttributes; + +// TODO: This should be a `