diff --git a/js/actionCreators.js b/js/actionCreators.js index 656ffe52..9337c728 100644 --- a/js/actionCreators.js +++ b/js/actionCreators.js @@ -1,9 +1,10 @@ -import { parser } from "winamp-eqf"; +import { parser, creator } from "winamp-eqf"; import MyFile from "./myFile"; import skinParser from "./skinParser"; import { BANDS } from "./constants"; +import { getEqfData } from "./selectors"; -import { clamp } from "./utils"; +import { clamp, base64FromArrayBuffer, downloadURI, normalize } from "./utils"; import { CLOSE_WINAMP, LOAD_AUDIO_FILE, @@ -95,8 +96,6 @@ export function toggleShuffle() { return { type: TOGGLE_SHUFFLE }; } -export const normalize = hrz => Math.round((hrz - 1) / 63 * 100); - function setEqFromFile(file) { return dispatch => { file.processBuffer(arrayBuffer => { @@ -212,3 +211,14 @@ export function toggleEq() { dispatch({ type }); }; } + +export function downloadPreset() { + return (dispatch, getState) => { + const state = getState(); + const data = getEqfData(state); + const arrayBuffer = creator(data); + const base64 = base64FromArrayBuffer(arrayBuffer); + const dataURI = `data:application/zip;base64,${base64}`; + downloadURI(dataURI, "entry.eqf"); + }; +} diff --git a/js/actionCreators.test.js b/js/actionCreators.test.js index 51f5eca7..9d9b026b 100644 --- a/js/actionCreators.test.js +++ b/js/actionCreators.test.js @@ -18,8 +18,7 @@ import { setEqBand, setEqToMax, setEqToMin, - setEqToMid, - normalize + setEqToMid } from "./actionCreators"; test("stop", () => { @@ -124,8 +123,3 @@ test("setEqToMid", () => { ]); expect(mockDispatch.mock.calls).toEqual(expectedCalls); }); - -test("normalize", () => { - expect(normalize(1)).toBe(0); - expect(normalize(64)).toBe(100); -}); diff --git a/js/components/Band.js b/js/components/Band.js index 1fd275da..93271a77 100644 --- a/js/components/Band.js +++ b/js/components/Band.js @@ -22,7 +22,7 @@ const Band = ({ value, backgroundPosition, id, onChange }) =>
2 * Math.round(value / 2); -export const getY = value => roundToEven(32 - value / 100 * 32); +// The line is two pixels wide, and sits "above" the offset specified. So we use 30 not 32 as our max. +const reboundTo32 = rebound(1, 100, 0, 30); + +export const getY = value => roundToEven(reboundTo32(value)); class EqGraph extends React.Component { constructor(props) { diff --git a/js/components/EqualizerWindow.js b/js/components/EqualizerWindow.js index 4a80cc80..747ab993 100644 --- a/js/components/EqualizerWindow.js +++ b/js/components/EqualizerWindow.js @@ -11,12 +11,16 @@ import { setEqToMin } from "../actionCreators"; -import { SET_FOCUSED_WINDOW } from "../actionTypes"; +import { + SET_FOCUSED_WINDOW, + TOGGLE_PRESETS_CONTEXT_MENU +} from "../actionTypes"; import Band from "./Band"; import EqOn from "./EqOn"; import EqAuto from "./EqAuto"; import EqGraph from "./EqGraph"; +import PresetsContextMenu from "./PresetsContextMenu"; import "../../css/equalizer-window.css"; @@ -42,7 +46,12 @@ const EqualizerWindow = props => { -
+
+ +
@@ -72,13 +81,20 @@ const mapDispatchToProps = dispatch => ({ setEqToMin: () => dispatch(setEqToMin()), setEqToMid: () => dispatch(setEqToMid()), setEqToMax: () => dispatch(setEqToMax()), - setHertzValue: hertz => value => dispatch(setEqBand(hertz, value)) + setHertzValue: hertz => value => dispatch(setEqBand(hertz, value)), + togglePresetsContextMenu: e => { + dispatch({ type: TOGGLE_PRESETS_CONTEXT_MENU }); + // TODO: Consider binding to a ref instead. + // https://stackoverflow.com/a/24421834 + e.nativeEvent.stopImmediatePropagation(); + } }); const mapStateToProps = state => ({ doubled: state.display.doubled, selected: state.windows.focused === WINDOWS.EQUALIZER, - closed: !state.windows.equalizer + closed: !state.windows.equalizer, + contextMenuSelected: state.presetsContextMenu.selected }); export default connect(mapStateToProps, mapDispatchToProps)(EqualizerWindow); diff --git a/js/components/PresetsContextMenu.js b/js/components/PresetsContextMenu.js new file mode 100644 index 00000000..2137cd91 --- /dev/null +++ b/js/components/PresetsContextMenu.js @@ -0,0 +1,19 @@ +import React from "react"; +import { connect } from "react-redux"; +import { CLOSE_PRESETS_CONTEXT_MENU } from "../actionTypes"; +import { openFileDialog, downloadPreset } from "../actionCreators"; +import { ContextMenu, Node } from "./contextMenu"; + +const MainContextMenu = props => + + + + ; + +const mapDispatchToProps = (dispatch, ownProps) => ({ + closeMenu: () => dispatch({ type: CLOSE_PRESETS_CONTEXT_MENU }), + openFileDialog: () => dispatch(openFileDialog(ownProps.fileInput)), + downloadPreset: () => dispatch(downloadPreset()) +}); + +export default connect(null, mapDispatchToProps)(MainContextMenu); diff --git a/js/components/__snapshots__/MainWindow.test.js.snap b/js/components/__snapshots__/MainWindow.test.js.snap index 95cd9fd1..84c6c3e0 100644 --- a/js/components/__snapshots__/MainWindow.test.js.snap +++ b/js/components/__snapshots__/MainWindow.test.js.snap @@ -19,89 +19,102 @@ exports[`MainWindow renders to snapshot 1`] = ` id="title-bar" >
-
    -
  • - +
  • + + Winamp2-js + +
  • +
  • - Winamp2-js... - -
  • -
  • -
    -
  • -
  • - Play File... -
  • -
  • -
      -
    • - Load Skin... -
    • -
    • -
      -
    • -
    • - <Base Skin> -
    • -
    • - Mac OSX v1.5 (Aqua) -
    • -
    • - TopazAmp -
    • -
    • - Vizor -
    • -
    • - XMMS Turquoise -
    • -
    • - Zaxon Remake -
    • -
    - Skins -
  • -
  • -
    -
  • -
  • - Exit -
  • -
+
+ +
  • + Play File... +
  • +
  • +
      +
    • + Load Skin... +
    • +
    • +
      +
    • +
    • + <Base Skin> +
    • +
    • + Mac OSX v1.5 (Aqua) +
    • +
    • + TopazAmp +
    • +
    • + Vizor +
    • +
    • + XMMS Turquoise +
    • +
    • + Zaxon Remake +
    • +
    + Skins +
  • +
  • +
    +
  • +
  • + Exit +
  • + +
    - {playlist ? : ""} - {equalizer ? : ""} + {playlist && } + {equalizer && }
    , diff --git a/js/selectors.js b/js/selectors.js new file mode 100644 index 00000000..c9fa7a86 --- /dev/null +++ b/js/selectors.js @@ -0,0 +1,19 @@ +import { denormalize } from "./utils"; +import { BANDS } from "./constants"; + +export const getEqfData = state => { + // state.equalizer.sliders + const { sliders } = state.equalizer; + const preset = { + name: "Entry1", + preamp: denormalize(sliders.preamp) + }; + BANDS.forEach(band => { + preset[`hz${band}`] = denormalize(sliders[band]); + }); + const eqfData = { + presets: [preset], + type: "Winamp EQ library file v1.1" + }; + return eqfData; +}; diff --git a/js/selectors.test.js b/js/selectors.test.js new file mode 100644 index 00000000..4b15a51f --- /dev/null +++ b/js/selectors.test.js @@ -0,0 +1,29 @@ +import reducer from "./reducers"; +import { getEqfData } from "./selectors"; +describe("getEqfData", () => { + it("can extract EQF data from the current state", () => { + const state = reducer(); + //state = reducer(state, { type: "SET_BAND_VALUE", band: 60, value: 100 }); + const actual = getEqfData(state); + const expected = { + presets: [ + { + hz60: 32, + hz170: 32, + hz310: 32, + hz600: 32, + hz1000: 32, + hz3000: 32, + hz12000: 32, + hz14000: 32, + hz16000: 32, + hz6000: 32, + name: "Entry1", + preamp: 32 + } + ], + type: "Winamp EQ library file v1.1" + }; + expect(actual).toEqual(expected); + }); +}); diff --git a/js/utils.js b/js/utils.js index 82ebda25..f459f791 100644 --- a/js/utils.js +++ b/js/utils.js @@ -52,3 +52,29 @@ export const parseIni = text => { }; export const clamp = (value, min, max) => Math.min(Math.max(value, min), max); + +export const base64FromArrayBuffer = arrayBuffer => { + const dataView = new Uint8Array(arrayBuffer); + return window.btoa(String.fromCharCode(...dataView)); +}; + +// https://stackoverflow.com/a/15832662/1263117 +export function downloadURI(uri, name) { + const link = document.createElement("a"); + link.download = name; + link.href = uri; + window.document.body.appendChild(link); + link.click(); + window.document.body.removeChild(link); +} + +export const rebound = (oldMin, oldMax, newMin, newMax) => oldValue => + Math.round( + (oldValue - oldMin) * (newMax - newMin) / (oldMax - oldMin) + newMin + ); + +// Convert a .eqf value to a 1-100 +export const normalize = rebound(1, 64, 1, 100); + +// Convert a 0-100 to an .eqf value +export const denormalize = rebound(1, 100, 1, 64); diff --git a/js/utils.test.js b/js/utils.test.js index 1af18e5a..d048a74a 100644 --- a/js/utils.test.js +++ b/js/utils.test.js @@ -5,7 +5,9 @@ import { getTimeStr, clamp, parseViscolors, - parseIni + parseIni, + normalize, + denormalize } from "./utils"; const fixture = filename => @@ -123,3 +125,12 @@ describe("parseIni", () => { expect(actual).toEqual(expected); }); }); + +test("normalize", () => { + expect(normalize(1)).toBe(1); + expect(normalize(64)).toBe(100); +}); +test("denormalize", () => { + expect(denormalize(1)).toBe(1); + expect(denormalize(100)).toBe(64); +}); diff --git a/yarn.lock b/yarn.lock index fa9faf79..f14147fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -209,10 +209,6 @@ assert@^1.1.1: dependencies: util "0.10.3" -ast-types@0.9.8: - version "0.9.8" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.8.tgz#6cb6a40beba31f49f20928e28439fc14a3dab078" - async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -271,7 +267,7 @@ babel-cli@^6.14.0: optionalDependencies: chokidar "^1.6.1" -babel-code-frame@6.22.0, babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: +babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" dependencies: @@ -787,10 +783,6 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23 lodash "^4.2.0" to-fast-properties "^1.0.1" -babylon@7.0.0-beta.8: - version "7.0.0-beta.8" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.8.tgz#2bdc5ae366041442c27e068cce6f0d7c06ea9949" - babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0: version "6.16.1" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" @@ -1025,7 +1017,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -1721,6 +1713,13 @@ eslint-config-prettier@^1.5.0: dependencies: get-stdin "^5.0.1" +eslint-plugin-prettier@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.1.1.tgz#2fb7e2ab961f2b61d2c8cf91bc17716ca8c53868" + dependencies: + fast-diff "^1.1.1" + jest-docblock "^20.0.1" + eslint-plugin-react@6.10.3: version "6.10.3" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz#c5435beb06774e12c7db2f6abaddcbf900cd3f78" @@ -1811,7 +1810,7 @@ estraverse@~4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2" -esutils@2.0.2, esutils@^2.0.0, esutils@^2.0.2: +esutils@^2.0.0, esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -1915,6 +1914,10 @@ extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +fast-diff@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.1.tgz#0aea0e4e605b6a2189f0e936d4b7fbaf1b7cfd9b" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -2046,10 +2049,6 @@ flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" -flow-parser@0.45.0: - version "0.45.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.45.0.tgz#aa29d4ae27f06aa02817772bba0fcbefef7e62f0" - for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -2147,14 +2146,14 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" -get-stdin@5.0.1, get-stdin@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" - get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" +get-stdin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" + getpass@^0.1.1: version "0.1.6" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" @@ -2174,7 +2173,7 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: @@ -2790,6 +2789,10 @@ jest-diff@^19.0.0: jest-matcher-utils "^19.0.0" pretty-format "^19.0.0" +jest-docblock@^20.0.1: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712" + jest-environment-jsdom@^19.0.2: version "19.0.2" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-19.0.2.tgz#ceda859c4a4b94ab35e4de7dab54b926f293e4a3" @@ -2919,15 +2922,6 @@ jest-util@^19.0.2: leven "^2.0.0" mkdirp "^0.5.1" -jest-validate@19.0.0: - version "19.0.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.0.tgz#8c6318a20ecfeaba0ba5378bfbb8277abded4173" - dependencies: - chalk "^1.1.1" - jest-matcher-utils "^19.0.0" - leven "^2.0.0" - pretty-format "^19.0.0" - jest-validate@^19.0.2: version "19.0.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.2.tgz#dc534df5f1278d5b63df32b14241d4dbf7244c0c" @@ -3295,7 +3289,7 @@ minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -3941,20 +3935,9 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.3.1.tgz#fa0ea84b45ac0ba6de6a1e4cecdcff900d563151" - dependencies: - ast-types "0.9.8" - babel-code-frame "6.22.0" - babylon "7.0.0-beta.8" - chalk "1.1.3" - esutils "2.0.2" - flow-parser "0.45.0" - get-stdin "5.0.1" - glob "7.1.1" - jest-validate "19.0.0" - minimist "1.2.0" +prettier@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.4.4.tgz#a8d1447b14c9bf67e6d420dcadd10fb9a4fad65a" pretty-bytes-cli@^2.0.0: version "2.0.0" @@ -5154,9 +5137,9 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.1" -winamp-eqf@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/winamp-eqf/-/winamp-eqf-0.1.0.tgz#f8a60aee30591343df60e72c0c290a203d1c9615" +winamp-eqf@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/winamp-eqf/-/winamp-eqf-1.0.0.tgz#0c3702e579a25511d0934b78ea8e6a8b9cbb259a" window-size@0.1.0: version "0.1.0"