mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-24 02:27:37 +00:00
Revert "Revert "Serialize to indexdb" (#664)"
This reverts commit a053818c5e.
This commit is contained in:
parent
4f48ebb89e
commit
fc84ad4abf
45 changed files with 3207 additions and 392 deletions
|
|
@ -4,6 +4,9 @@
|
|||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Prevent accidental highlighting */
|
||||
|
|
|
|||
115
js/__snapshots__/serialization.test.ts.snap
Normal file
115
js/__snapshots__/serialization.test.ts.snap
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`can serialize the initial state 1`] = `
|
||||
Object {
|
||||
"display": Object {
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinColors": Array [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)",
|
||||
],
|
||||
"skinCursors": null,
|
||||
"skinGenLetterWidths": null,
|
||||
"skinImages": Object {
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII=",
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
},
|
||||
"skinPlaylistStyle": null,
|
||||
"skinRegion": Object {},
|
||||
"visualizerStyle": 0,
|
||||
},
|
||||
"equalizer": Object {
|
||||
"auto": false,
|
||||
"on": true,
|
||||
"sliders": Object {
|
||||
"1000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"170": 50,
|
||||
"3000": 50,
|
||||
"310": 50,
|
||||
"60": 50,
|
||||
"600": 50,
|
||||
"6000": 50,
|
||||
"preamp": 50,
|
||||
},
|
||||
},
|
||||
"media": Object {
|
||||
"balance": 0,
|
||||
"repeat": false,
|
||||
"shuffle": false,
|
||||
"volume": 78,
|
||||
},
|
||||
"version": 1,
|
||||
"windows": Object {
|
||||
"focused": "main",
|
||||
"genWindows": Object {
|
||||
"equalizer": Object {
|
||||
"hidden": false,
|
||||
"open": true,
|
||||
"position": Object {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"shade": false,
|
||||
"size": Array [
|
||||
0,
|
||||
0,
|
||||
],
|
||||
},
|
||||
"main": Object {
|
||||
"hidden": false,
|
||||
"open": true,
|
||||
"position": Object {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"shade": false,
|
||||
"size": Array [
|
||||
0,
|
||||
0,
|
||||
],
|
||||
},
|
||||
"playlist": Object {
|
||||
"hidden": false,
|
||||
"open": true,
|
||||
"position": Object {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"shade": false,
|
||||
"size": Array [
|
||||
0,
|
||||
0,
|
||||
],
|
||||
},
|
||||
},
|
||||
"positionsAreRelative": true,
|
||||
},
|
||||
}
|
||||
`;
|
||||
111
js/__tests__/fixtures/serializedState/v1/balance.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/balance.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 40,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/double-mode.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/double-mode.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": true,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/equalizer-auto.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/equalizer-auto.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": true,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/equalizer-band.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/equalizer-band.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 100,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/equalizer-on.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/equalizer-on.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": false,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/equalizer-preamp.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/equalizer-preamp.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 10
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/focused-window.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/focused-window.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "equalizer"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/llama-mode.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/llama-mode.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": true,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 100,
|
||||
"y": 100
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/repeat.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/repeat.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": true
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/shuffle.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/shuffle.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": true,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/volume.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/volume.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 50,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/window-hidden.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/window-hidden.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": true,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/window-open.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/window-open.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": false,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": false,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 100,
|
||||
"y": 100
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/window-shade.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/window-shade.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": true,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
111
js/__tests__/fixtures/serializedState/v1/window-size.json
Normal file
111
js/__tests__/fixtures/serializedState/v1/window-size.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"version": 1,
|
||||
"media": {
|
||||
"volume": 78,
|
||||
"balance": 0,
|
||||
"shuffle": false,
|
||||
"repeat": false
|
||||
},
|
||||
"equalizer": {
|
||||
"on": true,
|
||||
"auto": false,
|
||||
"sliders": {
|
||||
"60": 50,
|
||||
"170": 50,
|
||||
"310": 50,
|
||||
"600": 50,
|
||||
"1000": 50,
|
||||
"3000": 50,
|
||||
"6000": 50,
|
||||
"12000": 50,
|
||||
"14000": 50,
|
||||
"16000": 50,
|
||||
"preamp": 50
|
||||
}
|
||||
},
|
||||
"display": {
|
||||
"visualizerStyle": 0,
|
||||
"doubled": false,
|
||||
"llama": false,
|
||||
"marqueeStep": 0,
|
||||
"skinImages": {
|
||||
"EQ_PREAMP_LINE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAABCAYAAADpXEERAAAAE0lEQVQoU2Pcdfruf4ZRMKRDAAD1lwNjTqcaUQAAAABJRU5ErkJggg==",
|
||||
"EQ_GRAPH_LINE_COLORS": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATCAYAAABRC2cZAAAAR0lEQVQYV2O4rCT9n+F9kOJ/hvfViv8ZHkzSQCE2afxneH/HEJm49Nr0PwOYWPLIAkp0PjL4z1B41uQ/Q9QGnf8MWrPEIAQANWYwvnlToNIAAAAASUVORK5CYII="
|
||||
},
|
||||
"skinCursors": null,
|
||||
"skinRegion": {},
|
||||
"skinGenLetterWidths": null,
|
||||
"skinColors": [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(24,33,41)",
|
||||
"rgb(239,49,16)",
|
||||
"rgb(206,41,16)",
|
||||
"rgb(214,90,0)",
|
||||
"rgb(214,102,0)",
|
||||
"rgb(214,115,0)",
|
||||
"rgb(198,123,8)",
|
||||
"rgb(222,165,24)",
|
||||
"rgb(214,181,33)",
|
||||
"rgb(189,222,41)",
|
||||
"rgb(148,222,33)",
|
||||
"rgb(41,206,16)",
|
||||
"rgb(50,190,16)",
|
||||
"rgb(57,181,16)",
|
||||
"rgb(49,156,8)",
|
||||
"rgb(41,148,0)",
|
||||
"rgb(24,132,8)",
|
||||
"rgb(255,255,255)",
|
||||
"rgb(214,214,222)",
|
||||
"rgb(181,189,189)",
|
||||
"rgb(160,170,175)",
|
||||
"rgb(148,156,165)",
|
||||
"rgb(150,150,150)"
|
||||
],
|
||||
"skinPlaylistStyle": null
|
||||
},
|
||||
"windows": {
|
||||
"positionsAreRelative": true,
|
||||
"genWindows": {
|
||||
"main": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"equalizer": {
|
||||
"size": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"playlist": {
|
||||
"size": [
|
||||
4,
|
||||
5
|
||||
],
|
||||
"open": true,
|
||||
"hidden": false,
|
||||
"shade": false,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"focused": "main"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
import { BANDS } from "../constants";
|
||||
|
||||
import { SET_EQ_ON, SET_EQ_OFF, SET_BAND_VALUE } from "../actionTypes";
|
||||
import {
|
||||
SET_EQ_ON,
|
||||
SET_EQ_OFF,
|
||||
SET_BAND_VALUE,
|
||||
SET_EQ_AUTO
|
||||
} from "../actionTypes";
|
||||
import { Band, Dispatchable } from "../types";
|
||||
|
||||
const BAND_SNAP_DISTANCE = 10;
|
||||
|
|
@ -52,3 +57,9 @@ export function toggleEq(): Dispatchable {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleEqAuto(): Dispatchable {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: SET_EQ_AUTO, value: !getState().equalizer.auto });
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@ import {
|
|||
CLOSE_REQUESTED,
|
||||
MINIMIZE_WINAMP,
|
||||
SET_FOCUS,
|
||||
UNSET_FOCUS
|
||||
UNSET_FOCUS,
|
||||
LOAD_SERIALIZED_STATE
|
||||
} from "../actionTypes";
|
||||
import { Dispatchable } from "../types";
|
||||
import { ensureWindowsAreOnScreen } from "./windows";
|
||||
import { SerializedStateV1 } from "../serializedStates/v1Types";
|
||||
|
||||
export {
|
||||
toggleDoubleSizeMode,
|
||||
|
|
@ -19,7 +22,15 @@ export {
|
|||
setWindowSize,
|
||||
toggleWindow,
|
||||
updateWindowPositions,
|
||||
toggleMainWindowShadeMode
|
||||
toggleMainWindowShadeMode,
|
||||
centerWindowsInContainer,
|
||||
centerWindowsInView,
|
||||
resetWindowSizes,
|
||||
browserWindowSizeChanged,
|
||||
ensureWindowsAreOnScreen,
|
||||
stackWindows,
|
||||
toggleLlamaMode,
|
||||
setFocusedWindow
|
||||
} from "./windows";
|
||||
export {
|
||||
play,
|
||||
|
|
@ -43,7 +54,8 @@ export {
|
|||
setEqToMid,
|
||||
setEqToMin,
|
||||
setPreamp,
|
||||
toggleEq
|
||||
toggleEq,
|
||||
toggleEqAuto
|
||||
} from "./equalizer";
|
||||
export {
|
||||
addTracksFromReferences,
|
||||
|
|
@ -108,3 +120,13 @@ export function setFocus(input: string): Dispatchable {
|
|||
export function unsetFocus(): Dispatchable {
|
||||
return { type: UNSET_FOCUS };
|
||||
}
|
||||
|
||||
export function loadSerializedState(
|
||||
// In the future this type should be the union of all versioned types.
|
||||
serializedState: SerializedStateV1
|
||||
): Dispatchable {
|
||||
return dispatch => {
|
||||
dispatch({ type: LOAD_SERIALIZED_STATE, serializedState });
|
||||
dispatch(ensureWindowsAreOnScreen());
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import {
|
||||
getWindowGraph,
|
||||
getWindowSizes,
|
||||
getWindowPositions
|
||||
} from "../selectors";
|
||||
import * as Selectors from "../selectors";
|
||||
|
||||
import { objectMap } from "../utils";
|
||||
import * as Utils from "../utils";
|
||||
import {
|
||||
UPDATE_WINDOW_POSITIONS,
|
||||
TOGGLE_DOUBLESIZE_MODE,
|
||||
|
|
@ -12,7 +8,11 @@ import {
|
|||
TOGGLE_WINDOW,
|
||||
CLOSE_WINDOW,
|
||||
TOGGLE_WINDOW_SHADE_MODE,
|
||||
SET_WINDOW_VISIBILITY
|
||||
SET_WINDOW_VISIBILITY,
|
||||
BROWSER_WINDOW_SIZE_CHANGED,
|
||||
RESET_WINDOW_SIZES,
|
||||
TOGGLE_LLAMA_MODE,
|
||||
SET_FOCUSED_WINDOW
|
||||
} from "../actionTypes";
|
||||
|
||||
import { getPositionDiff, SizeDiff } from "../resizeUtils";
|
||||
|
|
@ -28,12 +28,12 @@ import { Action, Dispatchable, WindowId, WindowPositions } from "../types";
|
|||
function withWindowGraphIntegrity(action: Action): Dispatchable {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const graph = getWindowGraph(state);
|
||||
const originalSizes = getWindowSizes(state);
|
||||
const graph = Selectors.getWindowGraph(state);
|
||||
const originalSizes = Selectors.getWindowSizes(state);
|
||||
|
||||
dispatch(action);
|
||||
|
||||
const newSizes = getWindowSizes(getState());
|
||||
const newSizes = Selectors.getWindowSizes(getState());
|
||||
const sizeDiff: SizeDiff = {};
|
||||
for (const window of Object.keys(newSizes)) {
|
||||
const original = originalSizes[window];
|
||||
|
|
@ -45,9 +45,9 @@ function withWindowGraphIntegrity(action: Action): Dispatchable {
|
|||
}
|
||||
|
||||
const positionDiff = getPositionDiff(graph, sizeDiff);
|
||||
const windowPositions = getWindowPositions(state);
|
||||
const windowPositions = Selectors.getWindowPositions(state);
|
||||
|
||||
const newPositions = objectMap(windowPositions, (position, key) =>
|
||||
const newPositions = Utils.objectMap(windowPositions, (position, key) =>
|
||||
applyDiff(position, positionDiff[key])
|
||||
);
|
||||
|
||||
|
|
@ -59,6 +59,10 @@ export function toggleDoubleSizeMode(): Dispatchable {
|
|||
return withWindowGraphIntegrity({ type: TOGGLE_DOUBLESIZE_MODE });
|
||||
}
|
||||
|
||||
export function toggleLlamaMode(): Dispatchable {
|
||||
return { type: TOGGLE_LLAMA_MODE };
|
||||
}
|
||||
|
||||
export function toggleEqualizerShadeMode(): Dispatchable {
|
||||
return withWindowGraphIntegrity({
|
||||
type: TOGGLE_WINDOW_SHADE_MODE,
|
||||
|
|
@ -92,6 +96,10 @@ export function showWindow(windowId: WindowId): Dispatchable {
|
|||
return { type: SET_WINDOW_VISIBILITY, windowId, hidden: false };
|
||||
}
|
||||
|
||||
export function setFocusedWindow(window: WindowId): Dispatchable {
|
||||
return { type: SET_FOCUSED_WINDOW, window };
|
||||
}
|
||||
|
||||
export function setWindowSize(
|
||||
windowId: WindowId,
|
||||
size: [number, number]
|
||||
|
|
@ -104,7 +112,146 @@ export function toggleWindow(windowId: WindowId): Dispatchable {
|
|||
}
|
||||
|
||||
export function updateWindowPositions(
|
||||
positions: WindowPositions
|
||||
positions: WindowPositions,
|
||||
absolute?: boolean
|
||||
): Dispatchable {
|
||||
return { type: UPDATE_WINDOW_POSITIONS, positions };
|
||||
return { type: UPDATE_WINDOW_POSITIONS, positions, absolute };
|
||||
}
|
||||
|
||||
export function centerWindowsInContainer(container: HTMLElement): Dispatchable {
|
||||
return (dispatch, getState) => {
|
||||
if (!Selectors.getPositionsAreRelative(getState())) {
|
||||
return;
|
||||
}
|
||||
const { left, top } = container.getBoundingClientRect();
|
||||
const { scrollWidth: width, scrollHeight: height } = container;
|
||||
dispatch(centerWindows({ left, top, width, height }));
|
||||
};
|
||||
}
|
||||
|
||||
export function centerWindowsInView(): Dispatchable {
|
||||
return centerWindows({
|
||||
left: window.scrollX,
|
||||
top: window.scrollY,
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
});
|
||||
}
|
||||
|
||||
export function centerWindows(box: {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}): Dispatchable {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const windowsInfo = Selectors.getWindowsInfo(state);
|
||||
const getOpen = Selectors.getWindowOpen(state);
|
||||
const { top, left, width, height } = box;
|
||||
|
||||
const offsetLeft = left + window.scrollX;
|
||||
const offsetTop = top + window.scrollY;
|
||||
|
||||
// A layout has been suplied. We will compute the bounding box and
|
||||
// center the given layout.
|
||||
const bounding = Utils.calculateBoundingBox(
|
||||
windowsInfo.filter(w => getOpen(w.key))
|
||||
);
|
||||
|
||||
const boxHeight = bounding.bottom - bounding.top;
|
||||
const boxWidth = bounding.right - bounding.left;
|
||||
|
||||
const move = {
|
||||
x: Math.ceil(offsetLeft - bounding.left + (width - boxWidth) / 2),
|
||||
y: Math.ceil(offsetTop - bounding.top + (height - boxHeight) / 2)
|
||||
};
|
||||
|
||||
const newPositions = windowsInfo.reduce(
|
||||
(pos, w) => ({
|
||||
...pos,
|
||||
[w.key]: { x: move.x + w.x, y: move.y + w.y }
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
dispatch(updateWindowPositions(newPositions, true));
|
||||
};
|
||||
}
|
||||
|
||||
export function browserWindowSizeChanged() {
|
||||
const { height, width } = Utils.getWindowSize();
|
||||
return { type: BROWSER_WINDOW_SIZE_CHANGED, height, width };
|
||||
}
|
||||
|
||||
export function resetWindowSizes(): Dispatchable {
|
||||
return { type: RESET_WINDOW_SIZES };
|
||||
}
|
||||
|
||||
export function stackWindows(): Dispatchable {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(
|
||||
updateWindowPositions(Selectors.getStackedLayoutPositions(getState()))
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function ensureWindowsAreOnScreen(): Dispatchable {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
const windowsInfo = Selectors.getWindowsInfo(state);
|
||||
const getOpen = Selectors.getWindowOpen(state);
|
||||
const { height, width } = Utils.getWindowSize();
|
||||
const bounding = Utils.calculateBoundingBox(
|
||||
windowsInfo.filter(w => getOpen(w.key))
|
||||
);
|
||||
const positions = Selectors.getWindowPositions(state);
|
||||
|
||||
// Are we good?
|
||||
if (
|
||||
bounding.left >= 0 &&
|
||||
bounding.top >= 0 &&
|
||||
bounding.right <= width &&
|
||||
bounding.bottom <= height
|
||||
) {
|
||||
// My work here is done.
|
||||
return;
|
||||
}
|
||||
|
||||
const boundingHeight = bounding.bottom - bounding.top;
|
||||
const boundingWidth = bounding.right - bounding.left;
|
||||
|
||||
// Could we simply shift all the windows by a constant offset?
|
||||
if (boundingWidth <= width && boundingHeight <= height) {
|
||||
let moveY = 0;
|
||||
let moveX = 0;
|
||||
if (bounding.top <= 0) {
|
||||
moveY = bounding.top;
|
||||
} else if (bounding.bottom > height) {
|
||||
moveY = bounding.bottom - height;
|
||||
}
|
||||
|
||||
if (bounding.left <= 0) {
|
||||
moveX = bounding.left;
|
||||
} else if (bounding.right > width) {
|
||||
moveX = bounding.right - width;
|
||||
}
|
||||
|
||||
const newPositions = Utils.objectMap(positions, position => ({
|
||||
x: position.x - moveX,
|
||||
y: position.y - moveY
|
||||
}));
|
||||
|
||||
dispatch(updateWindowPositions(newPositions));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Try moving the individual groups to try to fit them in
|
||||
|
||||
// I give up. Just reset everything.
|
||||
dispatch(resetWindowSizes());
|
||||
dispatch(stackWindows());
|
||||
dispatch(centerWindowsInView());
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,3 +71,6 @@ export const SET_DUMMY_VIZ_DATA = "SET_DUMMY_VIZ_DATA";
|
|||
export const SET_WINDOW_VISIBILITY = "SET_WINDOW_VISIBILITY";
|
||||
export const LOADING = "LOADING";
|
||||
export const CLOSE_REQUESTED = "CLOSE_REQUESTED";
|
||||
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";
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class App extends React.Component {
|
|||
|
||||
_setFocus() {
|
||||
const binding = this._bindings[this.props.focused];
|
||||
if (binding.node) {
|
||||
if (binding && binding.node) {
|
||||
binding.node.focus();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,220 +0,0 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import {
|
||||
snapDiffManyToMany,
|
||||
boundingBox,
|
||||
snapWithinDiff,
|
||||
snap,
|
||||
traceConnection,
|
||||
applyDiff,
|
||||
applyMultipleDiffs
|
||||
} from "../snapUtils";
|
||||
import { getWindowsInfo, getWindowHidden, getWindowOpen } from "../selectors";
|
||||
import { updateWindowPositions } from "../actionCreators";
|
||||
import { WINDOW_HEIGHT, WINDOW_WIDTH } from "../constants";
|
||||
import { calculateBoundingBox } from "../utils";
|
||||
|
||||
const abuts = (a, b) => {
|
||||
// TODO: This is kinda a hack. They should really be touching, not just within snapping distance.
|
||||
// Also, overlapping should not count.
|
||||
const wouldMoveTo = snap(a, b);
|
||||
return wouldMoveTo.x !== undefined || wouldMoveTo.y !== undefined;
|
||||
};
|
||||
|
||||
class WindowManager extends React.Component {
|
||||
componentDidMount() {
|
||||
this.centerWindows();
|
||||
}
|
||||
|
||||
centerWindows = () => {
|
||||
const { container } = this.props;
|
||||
|
||||
const rect = container.getBoundingClientRect();
|
||||
const offsetLeft = rect.left + window.scrollX;
|
||||
const offsetTop = rect.top + window.scrollY;
|
||||
const width = container.scrollWidth;
|
||||
const height = container.scrollHeight;
|
||||
|
||||
if (this.props.windowsInfo.some(w => w.x == null || w.y == null)) {
|
||||
// Some windows do not have an initial position, so we'll come up
|
||||
// with your own layout.
|
||||
const windowPositions = {};
|
||||
const keys = this.windowKeys();
|
||||
const totalHeight = keys.length * WINDOW_HEIGHT;
|
||||
const globalOffsetLeft = Math.max(0, width / 2 - WINDOW_WIDTH / 2);
|
||||
const globalOffsetTop = Math.max(0, height / 2 - totalHeight / 2);
|
||||
keys.forEach((key, i) => {
|
||||
const offset = WINDOW_HEIGHT * i;
|
||||
windowPositions[key] = {
|
||||
x: Math.ceil(offsetLeft + globalOffsetLeft),
|
||||
y: Math.ceil(offsetTop + (globalOffsetTop + offset))
|
||||
};
|
||||
});
|
||||
this.props.updateWindowPositions(windowPositions);
|
||||
} else {
|
||||
// A layout has been suplied. We will compute the bounding box and
|
||||
// center the given layout.
|
||||
const bounding = calculateBoundingBox(
|
||||
this.props.windowsInfo.filter(w => this.props.getWindowOpen(w.key))
|
||||
);
|
||||
|
||||
const boxHeight = bounding.bottom - bounding.top;
|
||||
const boxWidth = bounding.right - bounding.left;
|
||||
|
||||
const move = {
|
||||
x: Math.ceil(offsetLeft + (width - boxWidth) / 2),
|
||||
y: Math.ceil(offsetTop + (height - boxHeight) / 2)
|
||||
};
|
||||
|
||||
const newPositions = this.props.windowsInfo.reduce(
|
||||
(pos, w) => ({ ...pos, [w.key]: { x: move.x + w.x, y: move.y + w.y } }),
|
||||
{}
|
||||
);
|
||||
|
||||
this.props.updateWindowPositions(newPositions);
|
||||
}
|
||||
};
|
||||
|
||||
movingAndStationaryNodes(key) {
|
||||
const windows = this.props.windowsInfo.filter(
|
||||
w =>
|
||||
this.props.windows[w.key] != null && !this.props.getWindowHidden(w.key)
|
||||
);
|
||||
const targetNode = windows.find(node => node.key === key);
|
||||
|
||||
let movingSet = new Set([targetNode]);
|
||||
// Only the main window brings other windows along.
|
||||
if (key === "main") {
|
||||
const findAllConnected = traceConnection(abuts);
|
||||
movingSet = findAllConnected(windows, targetNode);
|
||||
}
|
||||
|
||||
const stationary = windows.filter(w => !movingSet.has(w));
|
||||
const moving = Array.from(movingSet);
|
||||
|
||||
return [moving, stationary];
|
||||
}
|
||||
|
||||
handleMouseDown = (key, e) => {
|
||||
if (!e.target.classList.contains("draggable")) {
|
||||
return;
|
||||
}
|
||||
// Prevent dragging from highlighting text.
|
||||
e.preventDefault();
|
||||
|
||||
const [moving, stationary] = this.movingAndStationaryNodes(key);
|
||||
|
||||
const mouseStart = { x: e.clientX, y: e.clientY };
|
||||
// Aparently this is crazy across browsers.
|
||||
const browserSize = {
|
||||
width: Math.max(
|
||||
document.body.scrollWidth,
|
||||
document.documentElement.scrollWidth,
|
||||
document.body.offsetWidth,
|
||||
document.documentElement.offsetWidth,
|
||||
document.body.clientWidth,
|
||||
document.documentElement.clientWidth
|
||||
),
|
||||
height: Math.max(
|
||||
document.body.scrollHeight,
|
||||
document.documentElement.scrollHeight,
|
||||
document.body.offsetHeight,
|
||||
document.documentElement.offsetHeight,
|
||||
document.body.clientHeight,
|
||||
document.documentElement.clientHeight
|
||||
)
|
||||
};
|
||||
|
||||
const box = boundingBox(moving);
|
||||
|
||||
const handleMouseMove = ee => {
|
||||
const proposedDiff = {
|
||||
x: ee.clientX - mouseStart.x,
|
||||
y: ee.clientY - mouseStart.y
|
||||
};
|
||||
|
||||
const proposedWindows = moving.map(node => ({
|
||||
...node,
|
||||
...applyDiff(node, proposedDiff)
|
||||
}));
|
||||
|
||||
const proposedBox = {
|
||||
...box,
|
||||
...applyDiff(box, proposedDiff)
|
||||
};
|
||||
|
||||
const snapDiff = snapDiffManyToMany(proposedWindows, stationary);
|
||||
|
||||
const withinDiff = snapWithinDiff(proposedBox, browserSize);
|
||||
|
||||
const finalDiff = applyMultipleDiffs(proposedDiff, snapDiff, withinDiff);
|
||||
|
||||
const windowPositionDiff = moving.reduce((diff, window) => {
|
||||
diff[window.key] = applyDiff(window, finalDiff);
|
||||
return diff;
|
||||
}, {});
|
||||
|
||||
this.props.updateWindowPositions(windowPositionDiff);
|
||||
};
|
||||
|
||||
const removeListeners = () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
window.removeEventListener("mouseup", removeListeners);
|
||||
};
|
||||
|
||||
window.addEventListener("mouseup", removeListeners);
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
};
|
||||
|
||||
// Keys for the visible windows
|
||||
windowKeys() {
|
||||
// TODO: Iterables can probably do this better.
|
||||
return Object.keys(this.props.windows).filter(
|
||||
key => !!this.props.windows[key]
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0
|
||||
};
|
||||
|
||||
const windows = this.props.windowsInfo.filter(
|
||||
w => this.props.windows[w.key]
|
||||
);
|
||||
|
||||
return windows.map(w => (
|
||||
<div
|
||||
key={w.key}
|
||||
onMouseDown={e => this.handleMouseDown(w.key, e)}
|
||||
style={{ ...style, transform: `translate(${w.x}px, ${w.y}px)` }}
|
||||
>
|
||||
{this.props.windows[w.key]}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
WindowManager.propTypes = {
|
||||
windows: PropTypes.object.isRequired,
|
||||
container: PropTypes.instanceOf(Element).isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
windowsInfo: getWindowsInfo(state),
|
||||
getWindowHidden: getWindowHidden(state),
|
||||
getWindowOpen: getWindowOpen(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
updateWindowPositions
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(WindowManager);
|
||||
161
js/components/WindowManager.tsx
Normal file
161
js/components/WindowManager.tsx
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import React, { ReactNode } from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { Box, Diff } from "../snapUtils";
|
||||
import * as SnapUtils from "../snapUtils";
|
||||
import * as Selectors from "../selectors";
|
||||
import { updateWindowPositions } from "../actionCreators";
|
||||
import {
|
||||
WindowInfo,
|
||||
Dispatch,
|
||||
WindowPositions,
|
||||
AppState,
|
||||
WindowId
|
||||
} from "../types";
|
||||
const abuts = (a: Box, b: Box) => {
|
||||
// TODO: This is kinda a hack. They should really be touching, not just within snapping distance.
|
||||
// Also, overlapping should not count.
|
||||
const wouldMoveTo = SnapUtils.snap(a, b);
|
||||
return wouldMoveTo.x !== undefined || wouldMoveTo.y !== undefined;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
container: HTMLElement;
|
||||
windowsInfo: WindowInfo[];
|
||||
browserWindowSize: { height: number; width: number };
|
||||
updateWindowPositions(positions: WindowPositions, center: boolean): void;
|
||||
getWindowHidden(windowId: WindowId): boolean;
|
||||
windows: { [windowId: string]: ReactNode };
|
||||
}
|
||||
|
||||
class WindowManager extends React.Component<Props> {
|
||||
movingAndStationaryNodes(key: WindowId): [WindowInfo[], WindowInfo[]] {
|
||||
const windows = this.props.windowsInfo.filter(
|
||||
w =>
|
||||
this.props.windows[w.key] != null && !this.props.getWindowHidden(w.key)
|
||||
);
|
||||
const targetNode = windows.find(node => node.key === key);
|
||||
if (targetNode == null) {
|
||||
throw new Error("Tried to move a node that does not exist");
|
||||
}
|
||||
|
||||
let movingSet = new Set([targetNode]);
|
||||
// Only the main window brings other windows along.
|
||||
if (key === "main") {
|
||||
const findAllConnected = SnapUtils.traceConnection(abuts);
|
||||
movingSet = findAllConnected(windows, targetNode);
|
||||
}
|
||||
|
||||
const stationary = windows.filter(w => !movingSet.has(w));
|
||||
const moving = Array.from(movingSet);
|
||||
|
||||
return [moving, stationary];
|
||||
}
|
||||
|
||||
handleMouseDown = (key: WindowId, e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!(e.target as HTMLElement).classList.contains("draggable")) {
|
||||
return;
|
||||
}
|
||||
// Prevent dragging from highlighting text.
|
||||
e.preventDefault();
|
||||
|
||||
const [moving, stationary] = this.movingAndStationaryNodes(key);
|
||||
|
||||
const mouseStart = { x: e.clientX, y: e.clientY };
|
||||
const { browserWindowSize } = this.props;
|
||||
|
||||
const box = SnapUtils.boundingBox(moving);
|
||||
|
||||
const handleMouseMove = (ee: MouseEvent) => {
|
||||
const proposedDiff = {
|
||||
x: ee.clientX - mouseStart.x,
|
||||
y: ee.clientY - mouseStart.y
|
||||
};
|
||||
|
||||
const proposedWindows = moving.map(node => ({
|
||||
...node,
|
||||
...SnapUtils.applyDiff(node, proposedDiff)
|
||||
}));
|
||||
|
||||
const proposedBox = {
|
||||
...box,
|
||||
...SnapUtils.applyDiff(box, proposedDiff)
|
||||
};
|
||||
|
||||
const snapDiff = SnapUtils.snapDiffManyToMany(
|
||||
proposedWindows,
|
||||
stationary
|
||||
);
|
||||
|
||||
const withinDiff = SnapUtils.snapWithinDiff(
|
||||
proposedBox,
|
||||
browserWindowSize
|
||||
);
|
||||
|
||||
const finalDiff = SnapUtils.applyMultipleDiffs(
|
||||
proposedDiff,
|
||||
snapDiff,
|
||||
withinDiff
|
||||
);
|
||||
|
||||
const windowPositionDiff = moving.reduce(
|
||||
(diff: { [windowId: string]: Diff }, window) => {
|
||||
diff[window.key] = SnapUtils.applyDiff(window, finalDiff);
|
||||
return diff;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
this.props.updateWindowPositions(windowPositionDiff, false);
|
||||
};
|
||||
|
||||
const removeListeners = () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
window.removeEventListener("mouseup", removeListeners);
|
||||
};
|
||||
|
||||
window.addEventListener("mouseup", removeListeners);
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
};
|
||||
|
||||
render() {
|
||||
const style: React.CSSProperties = {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0
|
||||
};
|
||||
|
||||
const windows = this.props.windowsInfo.filter(
|
||||
w => this.props.windows[w.key]
|
||||
);
|
||||
|
||||
return windows.map(w => (
|
||||
<div
|
||||
key={w.key}
|
||||
onMouseDown={e => this.handleMouseDown(w.key, e)}
|
||||
style={{ ...style, transform: `translate(${w.x}px, ${w.y}px)` }}
|
||||
>
|
||||
{this.props.windows[w.key]}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
windowsInfo: Selectors.getWindowsInfo(state),
|
||||
getWindowHidden: Selectors.getWindowHidden(state),
|
||||
getWindowOpen: Selectors.getWindowOpen(state),
|
||||
browserWindowSize: Selectors.getBrowserWindowSize(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||
return {
|
||||
updateWindowPositions: (positions: WindowPositions) =>
|
||||
dispatch(updateWindowPositions(positions))
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(WindowManager);
|
||||
|
|
@ -21,7 +21,7 @@ import { TOGGLE_TIME_MODE, TOGGLE_LLAMA_MODE } from "./actionTypes";
|
|||
import { arraysAreEqual } from "./utils";
|
||||
import { Dispatch } from "./types";
|
||||
|
||||
export default function(dispatch: Dispatch) {
|
||||
export function bindHotkeys(dispatch: Dispatch): () => void {
|
||||
let keylog: number[] = [];
|
||||
const trigger = [
|
||||
78, // N
|
||||
|
|
@ -33,7 +33,8 @@ export default function(dispatch: Dispatch) {
|
|||
70, // F
|
||||
84 // T
|
||||
];
|
||||
document.addEventListener("keydown", e => {
|
||||
|
||||
const listener = (e: KeyboardEvent) => {
|
||||
if (e.ctrlKey) {
|
||||
switch (e.keyCode) {
|
||||
case 68: // CTRL+D
|
||||
|
|
@ -142,5 +143,10 @@ export default function(dispatch: Dispatch) {
|
|||
dispatch({ type: TOGGLE_LLAMA_MODE });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
document.addEventListener("keydown", listener);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", listener);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
17
js/index.js
17
js/index.js
|
|
@ -13,6 +13,7 @@ import green from "../skins/Green-Dimension-V2.wsz";
|
|||
import internetArchive from "../skins/Internet-Archive.wsz";
|
||||
import MilkdropWindow from "./components/MilkdropWindow";
|
||||
import screenshotInitialState from "./screenshotInitialState";
|
||||
|
||||
import WebampLazy from "./webampLazy";
|
||||
import enableMediaSession from "./mediaSession";
|
||||
import {
|
||||
|
|
@ -28,6 +29,7 @@ import {
|
|||
SET_EQ_AUTO,
|
||||
SET_DUMMY_VIZ_DATA
|
||||
} from "./actionTypes";
|
||||
|
||||
import { loadFilesFromReferences } from "./actionCreators";
|
||||
|
||||
import {
|
||||
|
|
@ -37,6 +39,8 @@ import {
|
|||
disableMarquee
|
||||
} from "./config";
|
||||
|
||||
import { bindToIndexedDB } from "./indexedDB";
|
||||
|
||||
const requireJSZip = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
require.ensure(
|
||||
|
|
@ -83,11 +87,15 @@ const NOISY_ACTION_TYPES = new Set([
|
|||
const MIN_MILKDROP_WIDTH = 725;
|
||||
|
||||
let screenshot = false;
|
||||
let clearState = false;
|
||||
let useState = false;
|
||||
let skinUrl = configSkinUrl;
|
||||
if ("URLSearchParams" in window) {
|
||||
const params = new URLSearchParams(location.search);
|
||||
screenshot = params.get("screenshot");
|
||||
skinUrl = params.get("skinUrl") || skinUrl;
|
||||
clearState = Boolean(params.get("clearState"));
|
||||
useState = Boolean(params.get("useState"));
|
||||
}
|
||||
|
||||
function supressDragAndDrop(e) {
|
||||
|
|
@ -154,7 +162,7 @@ function genAudioFileUrlsFromDropbox() {
|
|||
});
|
||||
}
|
||||
|
||||
Raven.context(() => {
|
||||
Raven.context(async () => {
|
||||
window.Raven = Raven;
|
||||
if (screenshot) {
|
||||
document.getElementsByClassName("about")[0].style.visibility = "hidden";
|
||||
|
|
@ -283,6 +291,7 @@ Raven.context(() => {
|
|||
|
||||
enableMediaSession(webamp);
|
||||
|
||||
// Expose a file input in the DOM for testing.
|
||||
const fileInput = document.createElement("input");
|
||||
fileInput.id = "webamp-file-input";
|
||||
fileInput.style.display = "none";
|
||||
|
|
@ -293,8 +302,10 @@ Raven.context(() => {
|
|||
});
|
||||
document.body.appendChild(fileInput);
|
||||
|
||||
webamp.renderWhenReady(document.getElementById("app"));
|
||||
|
||||
// Expose webamp instance for debugging and integration tests.
|
||||
window.__webamp = webamp;
|
||||
|
||||
await bindToIndexedDB(webamp, clearState, useState);
|
||||
|
||||
await webamp.renderWhenReady(document.getElementById("app"));
|
||||
});
|
||||
|
|
|
|||
41
js/indexedDB.js
Normal file
41
js/indexedDB.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import IdbKvStore from "idb-kv-store";
|
||||
import { throttle } from "./utils";
|
||||
const LOCAL_STORAGE_KEY = "webamp_state";
|
||||
|
||||
export async function bindToIndexedDB(webamp, clearState, useState) {
|
||||
const localStore = new IdbKvStore("webamp_state_database");
|
||||
|
||||
if (clearState) {
|
||||
try {
|
||||
await localStore.clear();
|
||||
} catch (e) {
|
||||
console.log("Failed to clear our IndexeddB state", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!useState) {
|
||||
return;
|
||||
}
|
||||
|
||||
let previousSerializedState = null;
|
||||
try {
|
||||
previousSerializedState = await localStore.get(LOCAL_STORAGE_KEY);
|
||||
} catch (e) {
|
||||
console.error("Failed to load the saved state from IndexedDB", e);
|
||||
}
|
||||
|
||||
if (previousSerializedState != null) {
|
||||
webamp.__loadSerializedState(previousSerializedState);
|
||||
}
|
||||
|
||||
async function persist() {
|
||||
const serializedState = webamp.__getSerializedState();
|
||||
try {
|
||||
await localStore.set(LOCAL_STORAGE_KEY, serializedState);
|
||||
} catch (e) {
|
||||
console.log("Failed to save our state to IndexedDB", e);
|
||||
}
|
||||
}
|
||||
|
||||
webamp.__onStateChange(throttle(persist, 1000));
|
||||
}
|
||||
|
|
@ -17,11 +17,19 @@ import {
|
|||
SET_EQ_ON,
|
||||
PLAY_TRACK,
|
||||
BUFFER_TRACK,
|
||||
CHANNEL_COUNT_CHANGED
|
||||
CHANNEL_COUNT_CHANGED,
|
||||
LOAD_SERIALIZED_STATE
|
||||
} from "./actionTypes";
|
||||
import { next as nextTrack } from "./actionCreators";
|
||||
import { getCurrentTrackId } from "./selectors";
|
||||
import { MiddlewareStore, Dispatchable, Action, Dispatch } from "./types";
|
||||
import * as Selectors from "./selectors";
|
||||
import {
|
||||
MiddlewareStore,
|
||||
Dispatchable,
|
||||
Action,
|
||||
Dispatch,
|
||||
Slider
|
||||
} from "./types";
|
||||
import { objectForEach } from "./utils";
|
||||
|
||||
export default (media: Media) => (store: MiddlewareStore) => {
|
||||
const {
|
||||
|
|
@ -58,7 +66,7 @@ export default (media: Media) => (store: MiddlewareStore) => {
|
|||
});
|
||||
|
||||
media.on("fileLoaded", () => {
|
||||
const id = getCurrentTrackId(store.getState());
|
||||
const id = Selectors.getCurrentTrackId(store.getState());
|
||||
if (id == null) {
|
||||
// Attempted to set the metadata for a track that was already removed.
|
||||
// Really, the media should have been stopped when the track was removed.
|
||||
|
|
@ -82,7 +90,8 @@ export default (media: Media) => (store: MiddlewareStore) => {
|
|||
});
|
||||
|
||||
return (next: Dispatch) => (action: Action) => {
|
||||
// TODO: Consider doing this after the action, and using the state as the source of truth.
|
||||
const returnValue = next(action);
|
||||
const state = store.getState();
|
||||
switch (action.type) {
|
||||
case PLAY:
|
||||
media.play();
|
||||
|
|
@ -94,10 +103,10 @@ export default (media: Media) => (store: MiddlewareStore) => {
|
|||
media.stop();
|
||||
break;
|
||||
case SET_VOLUME:
|
||||
media.setVolume(action.volume);
|
||||
media.setVolume(Selectors.getVolume(state));
|
||||
break;
|
||||
case SET_BALANCE:
|
||||
media.setBalance(action.balance);
|
||||
media.setBalance(Selectors.getBalance(state));
|
||||
break;
|
||||
case SEEK_TO_PERCENT_COMPLETE:
|
||||
media.seekToPercentComplete(action.percent);
|
||||
|
|
@ -129,7 +138,27 @@ export default (media: Media) => (store: MiddlewareStore) => {
|
|||
case SET_EQ_ON:
|
||||
media.enableEq();
|
||||
break;
|
||||
case LOAD_SERIALIZED_STATE: {
|
||||
// Set ALL THE THINGS!
|
||||
if (Selectors.getEqualizerEnabled(state)) {
|
||||
media.enableEq();
|
||||
} else {
|
||||
media.disableEq();
|
||||
}
|
||||
media.setVolume(Selectors.getVolume(state));
|
||||
media.setBalance(Selectors.getBalance(state));
|
||||
objectForEach(state.equalizer.sliders, (value, slider) => {
|
||||
if (slider === "preamp") {
|
||||
media.setPreamp(value);
|
||||
} else {
|
||||
// @ts-ignore I don't know how to teach TypeScript about objects
|
||||
// that use Slider as keys
|
||||
media.setEqBand(slider, value);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
return next(action);
|
||||
return returnValue;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@ import {
|
|||
SET_Z_INDEX,
|
||||
DISABLE_MARQUEE,
|
||||
SET_DUMMY_VIZ_DATA,
|
||||
LOADING
|
||||
LOADING,
|
||||
LOAD_SERIALIZED_STATE
|
||||
} from "../actionTypes";
|
||||
import { DEFAULT_SKIN, VISUALIZER_ORDER } from "../constants";
|
||||
import { DisplaySerializedStateV1 } from "../serializedStates/v1Types";
|
||||
|
||||
export interface DisplayState {
|
||||
additionalVisualizers: Array<string>;
|
||||
|
|
@ -123,12 +125,44 @@ const display = (
|
|||
return { ...state, zIndex: action.zIndex };
|
||||
case SET_DUMMY_VIZ_DATA:
|
||||
return { ...state, dummyVizData: action.data };
|
||||
case LOAD_SERIALIZED_STATE:
|
||||
return { ...state, ...action.serializedState.display };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
export default display;
|
||||
|
||||
export const getSerializedState = (
|
||||
state: DisplayState
|
||||
): DisplaySerializedStateV1 => {
|
||||
// My kingdom for a type-safe `_.pick`.
|
||||
const {
|
||||
visualizerStyle,
|
||||
doubled,
|
||||
llama,
|
||||
marqueeStep,
|
||||
skinImages,
|
||||
skinCursors,
|
||||
skinRegion,
|
||||
skinGenLetterWidths,
|
||||
skinColors,
|
||||
skinPlaylistStyle
|
||||
} = state;
|
||||
return {
|
||||
visualizerStyle,
|
||||
doubled,
|
||||
llama,
|
||||
marqueeStep,
|
||||
skinImages,
|
||||
skinCursors,
|
||||
skinRegion,
|
||||
skinGenLetterWidths,
|
||||
skinColors,
|
||||
skinPlaylistStyle
|
||||
};
|
||||
};
|
||||
|
||||
export const getVisualizationOrder = (state: DisplayState): Array<string> => {
|
||||
return [...state.additionalVisualizers, ...VISUALIZER_ORDER];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ import {
|
|||
SET_BAND_VALUE,
|
||||
SET_EQ_AUTO,
|
||||
SET_EQ_ON,
|
||||
SET_EQ_OFF
|
||||
SET_EQ_OFF,
|
||||
LOAD_SERIALIZED_STATE
|
||||
} from "../actionTypes";
|
||||
import { EqualizerSerializedStateV1 } from "../serializedStates/v1Types";
|
||||
|
||||
export interface EqualizerState {
|
||||
on: boolean;
|
||||
|
|
@ -45,9 +47,17 @@ const equalizer = (
|
|||
return { ...state, on: false };
|
||||
case SET_EQ_AUTO:
|
||||
return { ...state, auto: action.value };
|
||||
case LOAD_SERIALIZED_STATE:
|
||||
return action.serializedState.equalizer || state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export function getSerializedState(
|
||||
state: EqualizerState
|
||||
): EqualizerSerializedStateV1 {
|
||||
return state;
|
||||
}
|
||||
|
||||
export default equalizer;
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ import {
|
|||
TOGGLE_TIME_MODE,
|
||||
UPDATE_TIME_ELAPSED,
|
||||
ADD_TRACK_FROM_URL,
|
||||
CHANNEL_COUNT_CHANGED
|
||||
CHANNEL_COUNT_CHANGED,
|
||||
LOAD_SERIALIZED_STATE
|
||||
} from "../actionTypes";
|
||||
import { TIME_MODE, MEDIA_STATUS } from "../constants";
|
||||
import { MediaSerializedStateV1 } from "../serializedStates/v1Types";
|
||||
|
||||
export interface MediaState {
|
||||
timeMode: string; // TODO: Convert this to an enum
|
||||
|
|
@ -98,9 +100,16 @@ const media = (
|
|||
return { ...state, repeat: !state.repeat };
|
||||
case TOGGLE_SHUFFLE:
|
||||
return { ...state, shuffle: !state.shuffle };
|
||||
case LOAD_SERIALIZED_STATE:
|
||||
return { ...state, ...action.serializedState.media };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export function getSerializedState(state: MediaState): MediaSerializedStateV1 {
|
||||
const { volume, balance, shuffle, repeat } = state;
|
||||
return { volume, balance, shuffle, repeat };
|
||||
}
|
||||
|
||||
export default media;
|
||||
|
|
|
|||
|
|
@ -28,9 +28,7 @@ import { shuffle, moveSelected, objectMap, objectFilter } from "../utils";
|
|||
|
||||
export interface PlaylistState {
|
||||
trackOrder: number[];
|
||||
// https://github.com/Microsoft/TypeScript/pull/12253#issuecomment-263132208
|
||||
// TODO: Using numbers for keys is kinda annoying. Consider retyping as string
|
||||
tracks: { [id: number]: PlaylistTrack };
|
||||
tracks: { [id: string]: PlaylistTrack };
|
||||
lastSelectedIndex: number | null;
|
||||
currentTrack: number | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { WebampWindow, WindowPositions, Action } from "../types";
|
||||
import { Action, WindowId } from "../types";
|
||||
import { WINDOWS } from "../constants";
|
||||
import {
|
||||
SET_FOCUSED_WINDOW,
|
||||
|
|
@ -8,17 +8,54 @@ import {
|
|||
ADD_GEN_WINDOW,
|
||||
UPDATE_WINDOW_POSITIONS,
|
||||
WINDOW_SIZE_CHANGED,
|
||||
TOGGLE_WINDOW_SHADE_MODE
|
||||
TOGGLE_WINDOW_SHADE_MODE,
|
||||
LOAD_SERIALIZED_STATE,
|
||||
BROWSER_WINDOW_SIZE_CHANGED,
|
||||
RESET_WINDOW_SIZES
|
||||
} from "../actionTypes";
|
||||
import * as Utils from "../utils";
|
||||
import { WindowsSerializedStateV1 } from "../serializedStates/v1Types";
|
||||
|
||||
export interface WindowState {
|
||||
focused: string;
|
||||
genWindows: { [name: string]: WebampWindow };
|
||||
positions: WindowPositions;
|
||||
export interface WindowPosition {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
const defaultWindowsState: WindowState = {
|
||||
export type WindowPositions = {
|
||||
[windowId: string]: WindowPosition;
|
||||
};
|
||||
|
||||
export interface WebampWindow {
|
||||
title: string;
|
||||
size: [number, number];
|
||||
open: boolean;
|
||||
hidden: boolean;
|
||||
shade?: boolean;
|
||||
canResize: boolean;
|
||||
canShade: boolean;
|
||||
canDouble: boolean;
|
||||
generic: boolean;
|
||||
hotkey?: string;
|
||||
position: WindowPosition;
|
||||
}
|
||||
|
||||
export interface WindowInfo {
|
||||
key: WindowId;
|
||||
height: number;
|
||||
width: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
export interface WindowsState {
|
||||
focused: string;
|
||||
genWindows: { [name: string]: WebampWindow };
|
||||
browserWindowSize: { height: number; width: number };
|
||||
positionsAreRelative: boolean;
|
||||
}
|
||||
|
||||
const defaultWindowsState: WindowsState = {
|
||||
focused: WINDOWS.MAIN,
|
||||
positionsAreRelative: true,
|
||||
genWindows: {
|
||||
// TODO: Remove static capabilites and derive them from ids/generic
|
||||
main: {
|
||||
|
|
@ -31,7 +68,8 @@ const defaultWindowsState: WindowState = {
|
|||
canShade: true,
|
||||
canDouble: true,
|
||||
generic: false,
|
||||
hotkey: "Alt+W"
|
||||
hotkey: "Alt+W",
|
||||
position: { x: 0, y: 0 }
|
||||
},
|
||||
equalizer: {
|
||||
title: "Equalizer",
|
||||
|
|
@ -43,7 +81,8 @@ const defaultWindowsState: WindowState = {
|
|||
canShade: true,
|
||||
canDouble: true,
|
||||
generic: false,
|
||||
hotkey: "Alt+G"
|
||||
hotkey: "Alt+G",
|
||||
position: { x: 0, y: 0 }
|
||||
},
|
||||
playlist: {
|
||||
title: "Playlist Editor",
|
||||
|
|
@ -55,16 +94,17 @@ const defaultWindowsState: WindowState = {
|
|||
canShade: true,
|
||||
canDouble: false,
|
||||
generic: false,
|
||||
hotkey: "Alt+E"
|
||||
hotkey: "Alt+E",
|
||||
position: { x: 0, y: 0 }
|
||||
}
|
||||
},
|
||||
positions: {}
|
||||
browserWindowSize: { width: 0, height: 0 }
|
||||
};
|
||||
|
||||
const windows = (
|
||||
state: WindowState = defaultWindowsState,
|
||||
state: WindowsState = defaultWindowsState,
|
||||
action: Action
|
||||
): WindowState => {
|
||||
): WindowsState => {
|
||||
switch (action.type) {
|
||||
case SET_FOCUSED_WINDOW:
|
||||
return { ...state, focused: action.window };
|
||||
|
|
@ -136,7 +176,8 @@ const windows = (
|
|||
canShade: false,
|
||||
canResize: true,
|
||||
canDouble: false,
|
||||
generic: true
|
||||
generic: true,
|
||||
position: { x: 0, y: 0 }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -160,11 +201,71 @@ const windows = (
|
|||
case UPDATE_WINDOW_POSITIONS:
|
||||
return {
|
||||
...state,
|
||||
positions: { ...state.positions, ...action.positions }
|
||||
positionsAreRelative:
|
||||
action.absolute === true ? false : state.positionsAreRelative,
|
||||
genWindows: Utils.objectMap(state.genWindows, (w, windowId) => {
|
||||
const newPosition = action.positions[windowId];
|
||||
if (newPosition == null) {
|
||||
return w;
|
||||
}
|
||||
return { ...w, position: newPosition };
|
||||
})
|
||||
};
|
||||
case RESET_WINDOW_SIZES:
|
||||
return {
|
||||
...state,
|
||||
genWindows: Utils.objectMap(state.genWindows, w => ({
|
||||
...w,
|
||||
// Not sure why TypeScript can't figure this out for itself.
|
||||
size: [0, 0] as [number, number]
|
||||
}))
|
||||
};
|
||||
case LOAD_SERIALIZED_STATE: {
|
||||
const {
|
||||
genWindows,
|
||||
focused,
|
||||
positionsAreRelative
|
||||
} = action.serializedState.windows;
|
||||
return {
|
||||
...state,
|
||||
positionsAreRelative,
|
||||
genWindows: Utils.objectMap(state.genWindows, (w, windowId) => {
|
||||
const serializedW = genWindows[windowId];
|
||||
if (serializedW == null) {
|
||||
return w;
|
||||
}
|
||||
return { ...w, ...serializedW };
|
||||
}),
|
||||
focused
|
||||
};
|
||||
}
|
||||
case BROWSER_WINDOW_SIZE_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
browserWindowSize: { height: action.height, width: action.width }
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export function getSerializedState(
|
||||
state: WindowsState
|
||||
): WindowsSerializedStateV1 {
|
||||
return {
|
||||
positionsAreRelative: state.positionsAreRelative,
|
||||
genWindows: Utils.objectMap(state.genWindows, w => {
|
||||
return {
|
||||
size: w.size,
|
||||
open: w.open,
|
||||
hidden: w.hidden,
|
||||
shade: w.shade || false,
|
||||
position: w.position
|
||||
};
|
||||
}),
|
||||
focused: state.focused
|
||||
};
|
||||
}
|
||||
|
||||
export default windows;
|
||||
|
|
|
|||
105
js/selectors.ts
105
js/selectors.ts
|
|
@ -4,16 +4,11 @@ import {
|
|||
WebampWindow,
|
||||
WindowId,
|
||||
WindowInfo,
|
||||
LoadedURLTrack
|
||||
LoadedURLTrack,
|
||||
WindowPositions
|
||||
} from "./types";
|
||||
import { createSelector } from "reselect";
|
||||
import {
|
||||
denormalize,
|
||||
getTimeStr,
|
||||
clamp,
|
||||
percentToIndex,
|
||||
objectMap
|
||||
} from "./utils";
|
||||
import * as Utils from "./utils";
|
||||
import {
|
||||
BANDS,
|
||||
TRACK_HEIGHT,
|
||||
|
|
@ -26,17 +21,21 @@ import {
|
|||
import { createPlaylistURL } from "./playlistHtml";
|
||||
import * as fromPlaylist from "./reducers/playlist";
|
||||
import * as fromDisplay from "./reducers/display";
|
||||
import * as fromEqualizer from "./reducers/equalizer";
|
||||
import * as fromMedia from "./reducers/media";
|
||||
import * as fromWindows from "./reducers/windows";
|
||||
import { generateGraph } from "./resizeUtils";
|
||||
import { SerializedStateV1 } from "./serializedStates/v1Types";
|
||||
|
||||
export const getSliders = (state: AppState) => state.equalizer.sliders;
|
||||
|
||||
export const getEqfData = createSelector(getSliders, sliders => {
|
||||
const preset: { [key: string]: number | string } = {
|
||||
name: "Entry1",
|
||||
preamp: denormalize(sliders.preamp)
|
||||
preamp: Utils.denormalize(sliders.preamp)
|
||||
};
|
||||
BANDS.forEach(band => {
|
||||
preset[`hz${band}`] = denormalize(sliders[band]);
|
||||
preset[`hz${band}`] = Utils.denormalize(sliders[band]);
|
||||
});
|
||||
const eqfData = {
|
||||
presets: [preset],
|
||||
|
|
@ -89,7 +88,9 @@ export const getRunningTimeMessage = createSelector(
|
|||
getTotalRunningTime,
|
||||
getSelectedRunningTime,
|
||||
(totalRunningTime, selectedRunningTime) =>
|
||||
`${getTimeStr(selectedRunningTime)}/${getTimeStr(totalRunningTime)}`
|
||||
`${Utils.getTimeStr(selectedRunningTime)}/${Utils.getTimeStr(
|
||||
totalRunningTime
|
||||
)}`
|
||||
);
|
||||
|
||||
// TODO: use slectors to get memoization
|
||||
|
|
@ -137,7 +138,7 @@ export const nextTrack = (state: AppState, n = 1) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
nextIndex = clamp(nextIndex, 0, trackCount - 1);
|
||||
nextIndex = Utils.clamp(nextIndex, 0, trackCount - 1);
|
||||
return trackOrder[nextIndex];
|
||||
};
|
||||
|
||||
|
|
@ -181,7 +182,7 @@ export const getScrollOffset = createSelector(
|
|||
getNumberOfVisibleTracks,
|
||||
(playlistScrollPosition, trackCount, numberOfVisibleTracks) => {
|
||||
const overflow = Math.max(0, trackCount - numberOfVisibleTracks);
|
||||
return percentToIndex(playlistScrollPosition / 100, overflow + 1);
|
||||
return Utils.percentToIndex(playlistScrollPosition / 100, overflow + 1);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -287,7 +288,7 @@ export const getMediaText = createSelector(
|
|||
minimalMediaText == null
|
||||
? null
|
||||
: // TODO: Maybe the ` *** ` should actually be added by the marquee
|
||||
`${minimalMediaText} (${getTimeStr(duration)}) *** `
|
||||
`${minimalMediaText} (${Utils.getTimeStr(duration)}) *** `
|
||||
);
|
||||
|
||||
export const getNumberOfTracks = (state: AppState) =>
|
||||
|
|
@ -308,21 +309,19 @@ export const getPlaylistURL = createSelector(
|
|||
(numberOfTracks, playlistDuration, trackOrder, tracks, getDisplayName) =>
|
||||
createPlaylistURL({
|
||||
numberOfTracks,
|
||||
averageTrackLength: getTimeStr(playlistDuration / numberOfTracks),
|
||||
averageTrackLength: Utils.getTimeStr(playlistDuration / numberOfTracks),
|
||||
// TODO: Handle hours
|
||||
playlistLengthMinutes: Math.floor(playlistDuration / 60),
|
||||
playlistLengthSeconds: Math.floor(playlistDuration % 60),
|
||||
tracks: trackOrder.map(
|
||||
(id, i) =>
|
||||
`${i + 1}. ${getDisplayName(id)} (${getTimeStr(tracks[id].duration)})`
|
||||
`${i + 1}. ${getDisplayName(id)} (${Utils.getTimeStr(
|
||||
tracks[id].duration
|
||||
)})`
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
export function getWindowPositions(state: AppState) {
|
||||
return state.windows.positions;
|
||||
}
|
||||
|
||||
const WINDOW_HEIGHT = 116;
|
||||
const SHADE_WINDOW_HEIGHT = 14;
|
||||
|
||||
|
|
@ -355,19 +354,40 @@ export function getWindowHidden(state: AppState) {
|
|||
return (windowId: WindowId) => state.windows.genWindows[windowId].hidden;
|
||||
}
|
||||
|
||||
export function getFocusedWindow(state: AppState): WindowId {
|
||||
return state.windows.focused;
|
||||
}
|
||||
|
||||
export function getWindowPosition(state: AppState) {
|
||||
return (windowId: WindowId) => state.windows.genWindows[windowId].position;
|
||||
}
|
||||
|
||||
export function getPositionsAreRelative(state: AppState) {
|
||||
return state.windows.positionsAreRelative;
|
||||
}
|
||||
|
||||
export const getGenWindows = (state: AppState) => {
|
||||
return state.windows.genWindows;
|
||||
};
|
||||
|
||||
export const getWindowPositions = createSelector(
|
||||
getGenWindows,
|
||||
(windows): WindowPositions => Utils.objectMap(windows, w => w.position)
|
||||
);
|
||||
|
||||
export function getDoubled(state: AppState) {
|
||||
return state.display.doubled;
|
||||
}
|
||||
|
||||
export function getLlamaMode(state: AppState) {
|
||||
return state.display.llama;
|
||||
}
|
||||
|
||||
export const getWindowSizes = createSelector(
|
||||
getGenWindows,
|
||||
getDoubled,
|
||||
(windows, doubled) => {
|
||||
return objectMap(windows, w => getWPixelSize(w, doubled));
|
||||
return Utils.objectMap(windows, w => getWPixelSize(w, doubled));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -375,6 +395,7 @@ export const getWindowPixelSize = createSelector(getWindowSizes, sizes => {
|
|||
return (windowId: WindowId) => sizes[windowId];
|
||||
});
|
||||
|
||||
// TODO: Now that both size and position are stored on genWindows this seems a bit silly.
|
||||
export const getWindowsInfo = createSelector(
|
||||
getWindowSizes,
|
||||
getWindowPositions,
|
||||
|
|
@ -401,5 +422,47 @@ export const getVisualizerStyle = (state: AppState) =>
|
|||
|
||||
export const getVolume = (state: AppState) => state.media.volume;
|
||||
export const getBalance = (state: AppState) => state.media.balance;
|
||||
export const getShuffle = (state: AppState) => state.media.shuffle;
|
||||
export const getRepeat = (state: AppState) => state.media.repeat;
|
||||
|
||||
export const getChannels = (state: AppState) => state.media.channels;
|
||||
export function getSerlializedState(state: AppState): SerializedStateV1 {
|
||||
return {
|
||||
version: 1,
|
||||
media: fromMedia.getSerializedState(state.media),
|
||||
equalizer: fromEqualizer.getSerializedState(state.equalizer),
|
||||
display: fromDisplay.getSerializedState(state.display),
|
||||
windows: fromWindows.getSerializedState(state.windows)
|
||||
};
|
||||
}
|
||||
|
||||
export function getEqualizerEnabled(state: AppState): boolean {
|
||||
return state.equalizer.on;
|
||||
}
|
||||
|
||||
export function getEqualizerAuto(state: AppState): boolean {
|
||||
return state.equalizer.auto;
|
||||
}
|
||||
|
||||
export function getBrowserWindowSize(
|
||||
state: AppState
|
||||
): { height: number; width: number } {
|
||||
return state.windows.browserWindowSize;
|
||||
}
|
||||
|
||||
export const getOpenWindows = createSelector(getGenWindows, genWindows =>
|
||||
Utils.objectFilter(genWindows, w => w.open)
|
||||
);
|
||||
|
||||
export const getStackedLayoutPositions = createSelector(
|
||||
getOpenWindows,
|
||||
getDoubled,
|
||||
(openWindows, doubled): WindowPositions => {
|
||||
let offset = 0;
|
||||
return Utils.objectMap(openWindows, w => {
|
||||
const position = { x: 0, y: offset };
|
||||
offset += getWPixelSize(w, doubled).height;
|
||||
return position;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
|||
239
js/serialization.test.ts
Normal file
239
js/serialization.test.ts
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import reducer from "./reducers";
|
||||
import * as Selectors from "./selectors";
|
||||
import * as Actions from "./actionCreators";
|
||||
import { kebabCase } from "lodash";
|
||||
|
||||
import { createStore, applyMiddleware } from "redux";
|
||||
import thunk from "redux-thunk";
|
||||
import { LOAD_SERIALIZED_STATE } from "./actionTypes";
|
||||
import { SerializedStateV1 } from "./serializedStates/v1Types";
|
||||
import { Dispatchable, AppState } from "./types";
|
||||
|
||||
function fixturePath(name: string) {
|
||||
return path.join(
|
||||
__dirname,
|
||||
"__tests__/fixtures/serializedState/v1/",
|
||||
`${kebabCase(name)}.json`
|
||||
);
|
||||
}
|
||||
|
||||
function writeFixture(name: string, state: SerializedStateV1) {
|
||||
fs.writeFileSync(fixturePath(name), JSON.stringify(state, null, 4));
|
||||
}
|
||||
|
||||
function readFixture(name: string): SerializedStateV1 {
|
||||
return JSON.parse(fs.readFileSync(fixturePath(name), "utf8"));
|
||||
}
|
||||
|
||||
interface SerializationTestParams<T> {
|
||||
name: string;
|
||||
action: Dispatchable;
|
||||
selector(state: AppState): T;
|
||||
expected: T;
|
||||
}
|
||||
|
||||
function testSerialization<T>({
|
||||
name,
|
||||
action,
|
||||
selector,
|
||||
expected
|
||||
}: SerializationTestParams<T>) {
|
||||
test(name, () => {
|
||||
const firstStore = getStore();
|
||||
// Does not match the expected selection in the initial state
|
||||
expect(selector(firstStore.getState())).not.toEqual(expected);
|
||||
|
||||
firstStore.dispatch(action);
|
||||
// Ensure we actually changed something
|
||||
expect(firstStore.getState()).not.toEqual(getStore().getState());
|
||||
|
||||
// Ensure the expectation is true before we serialize
|
||||
expect(selector(firstStore.getState())).toEqual(expected);
|
||||
|
||||
const serializedState = Selectors.getSerlializedState(
|
||||
firstStore.getState()
|
||||
);
|
||||
|
||||
if (!fs.existsSync(fixturePath(name))) {
|
||||
writeFixture(name, serializedState);
|
||||
console.warn("Wrote new fixture: ", fixturePath(name));
|
||||
}
|
||||
|
||||
const readSerializedState = readFixture(name);
|
||||
|
||||
// Ensure our serialization strategy hasn't changed since we last serialized
|
||||
expect(readSerializedState).toEqual(serializedState);
|
||||
|
||||
// Try to apply this serialized state to the default state
|
||||
const secondStore = getStore();
|
||||
secondStore.dispatch({
|
||||
type: LOAD_SERIALIZED_STATE,
|
||||
serializedState: readSerializedState
|
||||
});
|
||||
|
||||
// Ensure our restored state conforms to expectation
|
||||
expect(selector(secondStore.getState())).toEqual(expected);
|
||||
|
||||
// TODO: Try to apply the serialized state to a complex non-initial state
|
||||
});
|
||||
}
|
||||
|
||||
const defaultSerializedState = Selectors.getSerlializedState(
|
||||
getStore().getState()
|
||||
);
|
||||
|
||||
function getStore() {
|
||||
const extras = {};
|
||||
const enhancer = applyMiddleware(thunk.withExtraArgument(extras));
|
||||
return createStore(reducer, enhancer);
|
||||
}
|
||||
|
||||
describe("can serialize", () => {
|
||||
test("the initial state", () => {
|
||||
const store = getStore();
|
||||
const serializedState = Selectors.getSerlializedState(store.getState());
|
||||
expect(serializedState).toMatchSnapshot();
|
||||
|
||||
const newStore = getStore();
|
||||
newStore.dispatch({
|
||||
type: LOAD_SERIALIZED_STATE,
|
||||
serializedState
|
||||
});
|
||||
expect(store.getState()).toEqual(newStore.getState());
|
||||
});
|
||||
|
||||
/* Media */
|
||||
testSerialization({
|
||||
name: "volume",
|
||||
action: Actions.setVolume(50),
|
||||
selector: Selectors.getVolume,
|
||||
expected: 50
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "balance",
|
||||
action: Actions.setBalance(40),
|
||||
selector: Selectors.getBalance,
|
||||
expected: 40
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "shuffle",
|
||||
action: Actions.toggleShuffle(),
|
||||
selector: Selectors.getShuffle,
|
||||
expected: true
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "repeat",
|
||||
action: Actions.toggleRepeat(),
|
||||
selector: Selectors.getRepeat,
|
||||
expected: true
|
||||
});
|
||||
|
||||
/* Equalizer */
|
||||
testSerialization({
|
||||
name: "equalizer on",
|
||||
action: Actions.toggleEq(),
|
||||
selector: Selectors.getEqualizerEnabled,
|
||||
expected: false
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "equalizer auto",
|
||||
action: Actions.toggleEqAuto(),
|
||||
selector: Selectors.getEqualizerAuto,
|
||||
expected: true
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "equalizer band",
|
||||
action: Actions.setEqBand(60, 100),
|
||||
selector: state => Selectors.getSliders(state)[60],
|
||||
expected: 100
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "equalizer preamp",
|
||||
action: Actions.setPreamp(10),
|
||||
selector: state => Selectors.getSliders(state).preamp,
|
||||
expected: 10
|
||||
});
|
||||
|
||||
/* Display */
|
||||
testSerialization({
|
||||
name: "double mode",
|
||||
action: Actions.toggleDoubleSizeMode(),
|
||||
selector: Selectors.getDoubled,
|
||||
expected: true
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "llama mode",
|
||||
action: Actions.toggleLlamaMode(),
|
||||
selector: Selectors.getLlamaMode,
|
||||
expected: true
|
||||
});
|
||||
|
||||
// TODO
|
||||
// * marqueeStep
|
||||
// * skinImages
|
||||
// * skinCursors
|
||||
// * skinRegion
|
||||
// * skinGenLetterWidths
|
||||
// * skinColors
|
||||
// * skinPlaylistStyle
|
||||
// * focused
|
||||
|
||||
/* Gen Windows */
|
||||
testSerialization({
|
||||
name: "window size",
|
||||
action: Actions.setWindowSize("playlist", [4, 5]),
|
||||
selector: state => Selectors.getWindowSize(state)("playlist"),
|
||||
expected: [4, 5]
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "window open",
|
||||
action: Actions.closeWindow("playlist"),
|
||||
selector: state => Selectors.getWindowOpen(state)("playlist"),
|
||||
expected: false
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "window hidden",
|
||||
action: Actions.hideWindow("playlist"),
|
||||
selector: state => Selectors.getWindowHidden(state)("playlist"),
|
||||
expected: true
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "window shade",
|
||||
action: Actions.toggleEqualizerShadeMode(),
|
||||
selector: state => Selectors.getWindowShade(state)("equalizer"),
|
||||
expected: true
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "main window position",
|
||||
action: Actions.updateWindowPositions({ main: { x: 100, y: 100 } }),
|
||||
selector: state => Selectors.getWindowPosition(state)("main"),
|
||||
expected: { x: 100, y: 100 }
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "window positions are relative",
|
||||
action: Actions.updateWindowPositions({ main: { x: 100, y: 100 } }, true),
|
||||
selector: Selectors.getPositionsAreRelative,
|
||||
expected: false
|
||||
});
|
||||
|
||||
testSerialization({
|
||||
name: "focused window",
|
||||
action: Actions.setFocusedWindow("equalizer"),
|
||||
selector: state => Selectors.getFocusedWindow(state),
|
||||
expected: "equalizer"
|
||||
});
|
||||
});
|
||||
64
js/serializedStates/v1Types.ts
Normal file
64
js/serializedStates/v1Types.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* NOTE: This file must not import any other types
|
||||
*/
|
||||
|
||||
export interface WindowsSerializedStateV1 {
|
||||
positionsAreRelative: boolean;
|
||||
genWindows: {
|
||||
[windowId: string]: {
|
||||
size: [number, number];
|
||||
open: boolean;
|
||||
hidden: boolean;
|
||||
shade: boolean;
|
||||
position: { x: number; y: number };
|
||||
};
|
||||
};
|
||||
focused: string;
|
||||
}
|
||||
|
||||
export interface DisplaySerializedStateV1 {
|
||||
visualizerStyle: number;
|
||||
doubled: boolean;
|
||||
llama: boolean;
|
||||
marqueeStep: number;
|
||||
skinImages: { [sprite: string]: string };
|
||||
skinCursors: { [cursor: string]: string } | null;
|
||||
skinRegion: { [windowName: string]: string[] };
|
||||
skinGenLetterWidths: { [letter: string]: number } | null;
|
||||
skinColors: string[]; // Theoretically this could be a tuple of a specific length
|
||||
skinPlaylistStyle: { [state: string]: string } | null;
|
||||
}
|
||||
|
||||
export interface EqualizerSerializedStateV1 {
|
||||
on: boolean;
|
||||
auto: boolean;
|
||||
sliders: Record<
|
||||
| 60
|
||||
| 170
|
||||
| 310
|
||||
| 600
|
||||
| 1000
|
||||
| 3000
|
||||
| 6000
|
||||
| 12000
|
||||
| 14000
|
||||
| 16000
|
||||
| "preamp",
|
||||
number
|
||||
>;
|
||||
}
|
||||
|
||||
export interface MediaSerializedStateV1 {
|
||||
volume: number;
|
||||
balance: number;
|
||||
shuffle: boolean;
|
||||
repeat: boolean;
|
||||
}
|
||||
|
||||
export interface SerializedStateV1 {
|
||||
version: 1;
|
||||
windows: WindowsSerializedStateV1;
|
||||
media: MediaSerializedStateV1;
|
||||
display: DisplaySerializedStateV1;
|
||||
equalizer: EqualizerSerializedStateV1;
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ interface Point {
|
|||
y: number;
|
||||
}
|
||||
|
||||
interface Diff {
|
||||
export interface Diff {
|
||||
x?: number;
|
||||
y?: number;
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ interface BoundingBox {
|
|||
height: number;
|
||||
}
|
||||
|
||||
interface Box extends Point {
|
||||
export interface Box extends Point {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ export const snap = (boxA: Box, boxB: Box) => {
|
|||
return { x, y };
|
||||
};
|
||||
|
||||
export const snapDiff = (a: Box, b: Box): Diff => {
|
||||
export const snapDiff = (a: Box, b: Box): Point => {
|
||||
const newPos = snap(a, b);
|
||||
return {
|
||||
x: newPos.x === undefined ? 0 : newPos.x - a.x,
|
||||
|
|
@ -73,7 +73,7 @@ export const snapDiff = (a: Box, b: Box): Diff => {
|
|||
};
|
||||
|
||||
// TODO: Use the first x and y combo
|
||||
export const snapDiffManyToMany = (as: Box[], bs: Box[]): Diff => {
|
||||
export const snapDiffManyToMany = (as: Box[], bs: Box[]): Point => {
|
||||
let x: number | undefined = 0;
|
||||
let y: number | undefined = 0;
|
||||
for (const a of as) {
|
||||
|
|
@ -120,7 +120,7 @@ export const snapWithin = (boxA: Box, boundingBox: BoundingBox): Diff => {
|
|||
return { x, y };
|
||||
};
|
||||
|
||||
export const snapWithinDiff = (a: Box, b: Box) => {
|
||||
export const snapWithinDiff = (a: Box, b: BoundingBox) => {
|
||||
const newPos = snapWithin(a, b);
|
||||
return {
|
||||
x: newPos.x === undefined ? 0 : newPos.x - a.x,
|
||||
|
|
|
|||
49
js/types.ts
49
js/types.ts
|
|
@ -3,9 +3,17 @@ import { SettingsState } from "./reducers/settings";
|
|||
import { UserInputState } from "./reducers/userInput";
|
||||
import { MediaState } from "./reducers/media";
|
||||
import { DisplayState } from "./reducers/display";
|
||||
import { WindowState } from "./reducers/windows";
|
||||
import { WindowsState, WindowPositions } from "./reducers/windows";
|
||||
import { EqualizerState } from "./reducers/equalizer";
|
||||
import { NetworkState } from "./reducers/network";
|
||||
import { SerializedStateV1 } from "./serializedStates/v1Types";
|
||||
|
||||
export {
|
||||
WebampWindow,
|
||||
WindowInfo,
|
||||
WindowPosition,
|
||||
WindowPositions
|
||||
} from "./reducers/windows";
|
||||
|
||||
export type Skin = {
|
||||
url: string;
|
||||
|
|
@ -53,13 +61,6 @@ type SkinData = {
|
|||
skinGenLetterWidths: GenLetterWidths;
|
||||
};
|
||||
|
||||
interface WindowPosition {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
export type WindowPositions = {
|
||||
[windowId: string]: WindowPosition;
|
||||
};
|
||||
export type Action =
|
||||
| {
|
||||
type: "@@init";
|
||||
|
|
@ -253,6 +254,7 @@ export type Action =
|
|||
| {
|
||||
type: "UPDATE_WINDOW_POSITIONS";
|
||||
positions: WindowPositions;
|
||||
absolute?: boolean;
|
||||
}
|
||||
| {
|
||||
type: "CLICKED_TRACK";
|
||||
|
|
@ -341,28 +343,13 @@ export type Action =
|
|||
| {
|
||||
type: "CLOSE_REQUESTED";
|
||||
cancel: () => void;
|
||||
};
|
||||
|
||||
export interface WebampWindow {
|
||||
title: string;
|
||||
size: [number, number];
|
||||
open: boolean;
|
||||
hidden: boolean;
|
||||
shade?: boolean;
|
||||
canResize: boolean;
|
||||
canShade: boolean;
|
||||
canDouble: boolean;
|
||||
generic: boolean;
|
||||
hotkey?: string;
|
||||
}
|
||||
|
||||
export interface WindowInfo {
|
||||
key: WindowId;
|
||||
height: number;
|
||||
width: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: "LOAD_SERIALIZED_STATE";
|
||||
serializedState: SerializedStateV1;
|
||||
}
|
||||
| { type: "RESET_WINDOW_SIZES" }
|
||||
| { type: "BROWSER_WINDOW_SIZE_CHANGED"; height: number; width: number };
|
||||
|
||||
export type MediaTagRequestStatus =
|
||||
| "INITIALIZED"
|
||||
|
|
@ -457,7 +444,7 @@ export interface PlaylistTrack {
|
|||
|
||||
export interface AppState {
|
||||
userInput: UserInputState;
|
||||
windows: WindowState;
|
||||
windows: WindowsState;
|
||||
display: DisplayState;
|
||||
settings: SettingsState;
|
||||
equalizer: EqualizerState;
|
||||
|
|
|
|||
95
js/utils.ts
95
js/utils.ts
|
|
@ -1,4 +1,5 @@
|
|||
import { DEFAULT_SKIN } from "./constants";
|
||||
import { WindowInfo } from "./types";
|
||||
|
||||
interface Time {
|
||||
minutesFirstDigit: string;
|
||||
|
|
@ -254,18 +255,37 @@ export function spliceIn<T>(original: T[], start: number, newValues: T[]): T[] {
|
|||
return newArr;
|
||||
}
|
||||
|
||||
type Procedure = (...args: any[]) => void;
|
||||
export function debounce(func: Function, delay: number): Function {
|
||||
let timeout: number;
|
||||
let callbackArgs: any[] = [];
|
||||
|
||||
export function debounce<F extends Procedure>(func: F, delay: number): F {
|
||||
let token: NodeJS.Timer;
|
||||
return function(this: any, ...args: any[]): void {
|
||||
if (token != null) {
|
||||
clearTimeout(token);
|
||||
return function(context: Object, ...args: any[]): void {
|
||||
callbackArgs = args;
|
||||
|
||||
if (timeout != null) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
token = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
timeout = window.setTimeout(() => {
|
||||
func.apply(context, callbackArgs);
|
||||
}, delay);
|
||||
} as any;
|
||||
};
|
||||
}
|
||||
|
||||
// Trailing edge only throttle
|
||||
export function throttle(func: Function, delay: number): Function {
|
||||
let timeout: number | null = null;
|
||||
let callbackArgs: any[] = [];
|
||||
|
||||
return function(context: Object, ...args: any[]): void {
|
||||
callbackArgs = args;
|
||||
|
||||
if (!timeout) {
|
||||
timeout = window.setTimeout(() => {
|
||||
func.apply(context, callbackArgs);
|
||||
timeout = null;
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
|
|
@ -292,7 +312,7 @@ export function objectMap<V, N>(
|
|||
export function objectFilter<V>(
|
||||
obj: { [key: string]: V },
|
||||
predicate: (value: V, key: string) => boolean
|
||||
) {
|
||||
): { [key: string]: V } {
|
||||
// TODO: Could return the original reference if no values change
|
||||
return Object.keys(obj).reduce((newObj: { [key: string]: V }, key) => {
|
||||
if (predicate(obj[key], key)) {
|
||||
|
|
@ -302,25 +322,20 @@ export function objectFilter<V>(
|
|||
}, {});
|
||||
}
|
||||
|
||||
interface Window {
|
||||
left: number;
|
||||
top: number;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const calculateBoundingBox = (windows: Window[]) =>
|
||||
windows.reduce(
|
||||
(b, w) => ({
|
||||
left: Math.min(b.left, w.x),
|
||||
top: Math.min(b.top, w.y),
|
||||
bottom: Math.max(b.bottom, w.y + w.height),
|
||||
right: Math.max(b.right, w.x + w.width)
|
||||
}),
|
||||
{ top: 0, bottom: 0, left: 0, right: 0 }
|
||||
);
|
||||
export const calculateBoundingBox = (windows: WindowInfo[]) =>
|
||||
windows
|
||||
.map(w => ({
|
||||
left: w.x,
|
||||
top: w.y,
|
||||
bottom: w.y + w.height,
|
||||
right: w.x + w.width
|
||||
}))
|
||||
.reduce((b, w) => ({
|
||||
left: Math.min(b.left, w.left),
|
||||
top: Math.min(b.top, w.top),
|
||||
bottom: Math.max(b.bottom, w.bottom),
|
||||
right: Math.max(b.right, w.right)
|
||||
}));
|
||||
|
||||
export function findLastIndex<T>(arr: T[], cb: (val: T) => boolean) {
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
|
|
@ -330,3 +345,25 @@ export function findLastIndex<T>(arr: T[], cb: (val: T) => boolean) {
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function getWindowSize(): { width: number; height: number } {
|
||||
// Aparently this is crazy across browsers.
|
||||
return {
|
||||
width: Math.max(
|
||||
document.body.scrollWidth,
|
||||
document.documentElement.scrollWidth,
|
||||
document.body.offsetWidth,
|
||||
document.documentElement.offsetWidth,
|
||||
document.body.clientWidth,
|
||||
document.documentElement.clientWidth
|
||||
),
|
||||
height: Math.max(
|
||||
document.body.scrollHeight,
|
||||
document.documentElement.scrollHeight,
|
||||
document.body.offsetHeight,
|
||||
document.documentElement.offsetHeight,
|
||||
document.body.clientHeight,
|
||||
document.documentElement.clientHeight
|
||||
)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,22 +4,13 @@ import { Provider } from "react-redux";
|
|||
|
||||
import getStore from "./store";
|
||||
import App from "./components/App";
|
||||
import Hotkeys from "./hotkeys";
|
||||
import { bindHotkeys } from "./hotkeys";
|
||||
import Media from "./media";
|
||||
import * as Selectors from "./selectors";
|
||||
import {
|
||||
setSkinFromUrl,
|
||||
loadMediaFiles,
|
||||
setWindowSize,
|
||||
play,
|
||||
pause,
|
||||
seekBackward,
|
||||
seekForward,
|
||||
next,
|
||||
previous
|
||||
} from "./actionCreators";
|
||||
import * as Actions from "./actionCreators";
|
||||
|
||||
import { LOAD_STYLE } from "./constants";
|
||||
import { uniqueId, objectMap, objectForEach } from "./utils";
|
||||
import * as Utils from "./utils";
|
||||
|
||||
import {
|
||||
SET_AVAILABLE_SKINS,
|
||||
|
|
@ -28,7 +19,6 @@ import {
|
|||
CLOSE_WINAMP,
|
||||
MINIMIZE_WINAMP,
|
||||
ADD_GEN_WINDOW,
|
||||
UPDATE_WINDOW_POSITIONS,
|
||||
LOADED,
|
||||
REGISTER_VISUALIZER,
|
||||
SET_Z_INDEX,
|
||||
|
|
@ -64,6 +54,7 @@ class Winamp {
|
|||
}
|
||||
|
||||
constructor(options) {
|
||||
this._subscriptions = [];
|
||||
this._actionEmitter = new Emitter();
|
||||
this.options = options;
|
||||
const {
|
||||
|
|
@ -99,7 +90,7 @@ class Winamp {
|
|||
this.genWindows = [];
|
||||
if (__extraWindows) {
|
||||
this.genWindows = __extraWindows.map(genWindow => ({
|
||||
id: genWindow.id || `${genWindow.title}-${uniqueId()}`,
|
||||
id: genWindow.id || `${genWindow.title}-${Utils.uniqueId()}`,
|
||||
...genWindow
|
||||
}));
|
||||
|
||||
|
|
@ -126,8 +117,14 @@ class Winamp {
|
|||
this.store.dispatch({ type: NETWORK_DISCONNECTED })
|
||||
);
|
||||
|
||||
this.store.dispatch(Actions.browserWindowSizeChanged());
|
||||
window.addEventListener("resize", () => {
|
||||
this.store.dispatch(Actions.browserWindowSizeChanged());
|
||||
this.store.dispatch(Actions.ensureWindowsAreOnScreen());
|
||||
});
|
||||
|
||||
if (initialSkin) {
|
||||
this.store.dispatch(setSkinFromUrl(initialSkin.url));
|
||||
this.store.dispatch(Actions.setSkinFromUrl(initialSkin.url));
|
||||
} else {
|
||||
// We are using the default skin.
|
||||
this.store.dispatch({ type: LOADED });
|
||||
|
|
@ -147,56 +144,62 @@ class Winamp {
|
|||
}
|
||||
|
||||
const layout = options.__initialWindowLayout;
|
||||
if (layout != null) {
|
||||
objectForEach(layout, (w, windowId) => {
|
||||
if (layout == null) {
|
||||
this.store.dispatch(Actions.stackWindows());
|
||||
} else {
|
||||
Utils.objectForEach(layout, (w, windowId) => {
|
||||
if (w.size != null) {
|
||||
this.store.dispatch(setWindowSize(windowId, w.size));
|
||||
this.store.dispatch(Actions.setWindowSize(windowId, w.size));
|
||||
}
|
||||
});
|
||||
this.store.dispatch({
|
||||
type: UPDATE_WINDOW_POSITIONS,
|
||||
positions: objectMap(layout, w => w.position)
|
||||
});
|
||||
this.store.dispatch(
|
||||
Actions.updateWindowPositions(
|
||||
Utils.objectMap(layout, w => w.position),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (enableHotkeys) {
|
||||
new Hotkeys(this.store.dispatch);
|
||||
this._subscriptions.push(bindHotkeys(this.store.dispatch));
|
||||
}
|
||||
}
|
||||
|
||||
play() {
|
||||
this.store.dispatch(play());
|
||||
this.store.dispatch(Actions.play());
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.store.dispatch(pause());
|
||||
this.store.dispatch(Actions.pause());
|
||||
}
|
||||
|
||||
seekBackward(seconds) {
|
||||
this.store.dispatch(seekBackward(seconds));
|
||||
this.store.dispatch(Actions.seekBackward(seconds));
|
||||
}
|
||||
|
||||
seekForward(seconds) {
|
||||
this.store.dispatch(seekForward(seconds));
|
||||
this.store.dispatch(Actions.seekForward(seconds));
|
||||
}
|
||||
|
||||
nextTrack() {
|
||||
this.store.dispatch(next());
|
||||
this.store.dispatch(Actions.next());
|
||||
}
|
||||
|
||||
previousTrack() {
|
||||
this.store.dispatch(previous());
|
||||
this.store.dispatch(Actions.previous());
|
||||
}
|
||||
|
||||
// Append this array of tracks to the end of the current playlist.
|
||||
appendTracks(tracks) {
|
||||
const nextIndex = Selectors.getTrackCount(this.store.getState());
|
||||
this.store.dispatch(loadMediaFiles(tracks, LOAD_STYLE.BUFFER, nextIndex));
|
||||
this.store.dispatch(
|
||||
Actions.loadMediaFiles(tracks, LOAD_STYLE.BUFFER, nextIndex)
|
||||
);
|
||||
}
|
||||
|
||||
// Replace any existing tracks with this array of tracks, and begin playing.
|
||||
setTracksToPlay(tracks) {
|
||||
this.store.dispatch(loadMediaFiles(tracks, LOAD_STYLE.PLAY));
|
||||
this.store.dispatch(Actions.loadMediaFiles(tracks, LOAD_STYLE.PLAY));
|
||||
}
|
||||
|
||||
onWillClose(cb) {
|
||||
|
|
@ -231,7 +234,20 @@ class Winamp {
|
|||
return storeHas(this.store, state => !state.display.loading);
|
||||
}
|
||||
|
||||
__loadSerializedState(serializedState) {
|
||||
this.store.dispatch(Actions.loadSerializedState(serializedState));
|
||||
}
|
||||
|
||||
__getSerializedState() {
|
||||
return Selectors.getSerlializedState(this.store.getState());
|
||||
}
|
||||
|
||||
__onStateChange(cb) {
|
||||
return this.store.subscribe(cb);
|
||||
}
|
||||
|
||||
async renderWhenReady(node) {
|
||||
this.store.dispatch(Actions.centerWindowsInContainer(node));
|
||||
await this.skinIsLoaded();
|
||||
const genWindowComponents = {};
|
||||
this.genWindows.forEach(w => {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
"@types/classnames": "^2.2.6",
|
||||
"@types/invariant": "^2.2.29",
|
||||
"@types/jest": "^23.3.2",
|
||||
"@types/lodash": "^4.14.116",
|
||||
"@types/react": "^16.4.14",
|
||||
"@types/react-dom": "^16.0.7",
|
||||
"@types/react-redux": "^6.0.8",
|
||||
|
|
@ -89,6 +90,7 @@
|
|||
"html-webpack-inline-svg-plugin": "^1.2.4",
|
||||
"html-webpack-plugin": "^3.0.6",
|
||||
"http-server": "^0.11.1",
|
||||
"idb-kv-store": "^4.4.0",
|
||||
"invariant": "^2.2.3",
|
||||
"jest": "^23.5.0",
|
||||
"jest-image-snapshot": "^2.4.1",
|
||||
|
|
@ -97,6 +99,7 @@
|
|||
"jest-runner-eslint": "^0.4.0",
|
||||
"jsmediatags": "^3.8.1",
|
||||
"jszip": "^3.1.3",
|
||||
"lodash": "^4.17.11",
|
||||
"milkdrop-preset-converter-aws": "^0.1.0",
|
||||
"prettier": "^1.14.2",
|
||||
"prop-types": "^15.5.10",
|
||||
|
|
|
|||
19
redux.md
Normal file
19
redux.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Thoughts on Redux
|
||||
|
||||
## Actions describe a thing that happened
|
||||
|
||||
Actions with names like `"SET_USER_NAME"` are a smell. Instead, prefer names like `"USERNAME_INPUT_CHANGED"`. Your actions should be a log of _facts_ and should not have any opinions about how those facts are interperated.
|
||||
|
||||
## Actions and action creators are a global concern
|
||||
|
||||
Reduces should know about actions, but actions should not know about your reducers.
|
||||
|
||||
## Your state is a cache
|
||||
|
||||
In principle, your state could simply be an array of every action that has been dispatched, and you could derive the current value by running your reducer in your selector.
|
||||
|
||||
### What would this redux look like? How could you optimize it?
|
||||
|
||||
- Often you need to refer to some other portion of your state within your reducer. How would you do this?
|
||||
|
||||
##
|
||||
19
yarn.lock
19
yarn.lock
|
|
@ -733,6 +733,10 @@
|
|||
version "23.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.2.tgz#07b90f6adf75d42c34230c026a2529e56c249dbb"
|
||||
|
||||
"@types/lodash@^4.14.116":
|
||||
version "4.14.116"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.116.tgz#5ccf215653e3e8c786a58390751033a9adca0eb9"
|
||||
|
||||
"@types/node@*":
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.0.tgz#2783ee1b6c47cbd4044f4a233976c1ac5fa9e942"
|
||||
|
|
@ -4150,6 +4154,13 @@ icss-utils@^2.1.0:
|
|||
dependencies:
|
||||
postcss "^6.0.1"
|
||||
|
||||
idb-kv-store@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/idb-kv-store/-/idb-kv-store-4.4.0.tgz#22c56a8d5f90bd88f818a859db9c58627de278be"
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
promisize "^1.1.2"
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
|
||||
|
|
@ -5402,6 +5413,10 @@ lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5,
|
|||
version "4.17.5"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
|
||||
|
||||
lodash@^4.17.11:
|
||||
version "4.17.11"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
||||
|
||||
loglevel@^1.4.1:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
|
||||
|
|
@ -6720,6 +6735,10 @@ promise@^7.1.1:
|
|||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
promisize@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/promisize/-/promisize-1.1.2.tgz#9b47e2cb2ae497eb1ebadc2c4191d64d15c949d1"
|
||||
|
||||
prompts@^0.1.9:
|
||||
version "0.1.14"
|
||||
resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.14.tgz#a8e15c612c5c9ec8f8111847df3337c9cbd443b2"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue