mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-28 20:40:39 +00:00
Serialize state to indexdb
This commit is contained in:
parent
379aa70184
commit
60429b280a
11 changed files with 171 additions and 9 deletions
|
|
@ -71,3 +71,4 @@ 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";
|
||||
|
|
|
|||
10
js/index.js
10
js/index.js
|
|
@ -36,6 +36,8 @@ import {
|
|||
disableMarquee
|
||||
} from "./config";
|
||||
|
||||
import { bindToIndexDB } from "./indexdb";
|
||||
|
||||
const requireJSZip = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
require.ensure(
|
||||
|
|
@ -153,7 +155,7 @@ function genAudioFileUrlsFromDropbox() {
|
|||
});
|
||||
}
|
||||
|
||||
Raven.context(() => {
|
||||
Raven.context(async () => {
|
||||
window.Raven = Raven;
|
||||
if (screenshot) {
|
||||
document.getElementsByClassName("about")[0].style.visibility = "hidden";
|
||||
|
|
@ -291,8 +293,10 @@ Raven.context(() => {
|
|||
});
|
||||
document.body.appendChild(fileInput);
|
||||
|
||||
webamp.renderWhenReady(document.getElementById("app"));
|
||||
|
||||
// Expose webamp instance for debugging and integration tests.
|
||||
window.__webamp = webamp;
|
||||
|
||||
await bindToIndexDB(webamp);
|
||||
|
||||
await webamp.renderWhenReady(document.getElementById("app"));
|
||||
});
|
||||
|
|
|
|||
29
js/indexdb.js
Normal file
29
js/indexdb.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import IdbKvStore from "idb-kv-store";
|
||||
import { throttle } from "./utils";
|
||||
const LOCAL_STORAGE_KEY = "webamp_state";
|
||||
|
||||
export async function bindToIndexDB(webamp) {
|
||||
const localStore = new IdbKvStore("webamp_state_database");
|
||||
|
||||
let previousSerializedState = null;
|
||||
try {
|
||||
previousSerializedState = await localStore.get(LOCAL_STORAGE_KEY);
|
||||
} catch (e) {
|
||||
console.error("Failed to load the saves state from IndexDB", 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 IndexDB", e);
|
||||
}
|
||||
}
|
||||
|
||||
webamp.onStateChange(throttle(persist, 1000));
|
||||
}
|
||||
|
|
@ -23,7 +23,8 @@ import {
|
|||
SET_Z_INDEX,
|
||||
DISABLE_MARQUEE,
|
||||
SET_DUMMY_VIZ_DATA,
|
||||
LOADING
|
||||
LOADING,
|
||||
LOAD_SERIALIZED_STATE
|
||||
} from "../actionTypes";
|
||||
import { DEFAULT_SKIN, VISUALIZER_ORDER } from "../constants";
|
||||
|
||||
|
|
@ -48,6 +49,19 @@ export interface DisplayState {
|
|||
dummyVizData: null; // TODO: Figure out what kind of data this actually is.
|
||||
}
|
||||
|
||||
export interface DisplaySerializedStateV1 {
|
||||
visualizerStyle: number;
|
||||
doubled: boolean;
|
||||
llama: boolean;
|
||||
marqueeStep: number;
|
||||
skinImages: SkinImages;
|
||||
skinCursors: Cursors | null;
|
||||
skinRegion: SkinRegion;
|
||||
skinGenLetterWidths: GenLetterWidths | null;
|
||||
skinColors: string[]; // Theoretically this could be a tuple of a specific length
|
||||
skinPlaylistStyle: PlaylistStyle | null;
|
||||
}
|
||||
|
||||
const defaultDisplayState = {
|
||||
doubled: false,
|
||||
marqueeStep: 0,
|
||||
|
|
@ -123,12 +137,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,7 +4,8 @@ import {
|
|||
SET_BAND_VALUE,
|
||||
SET_EQ_AUTO,
|
||||
SET_EQ_ON,
|
||||
SET_EQ_OFF
|
||||
SET_EQ_OFF,
|
||||
LOAD_SERIALIZED_STATE
|
||||
} from "../actionTypes";
|
||||
|
||||
export interface EqualizerState {
|
||||
|
|
@ -13,6 +14,12 @@ export interface EqualizerState {
|
|||
sliders: Record<Slider, number>;
|
||||
}
|
||||
|
||||
export interface EqualizerSerializedStateV1 {
|
||||
on: boolean;
|
||||
auto: boolean;
|
||||
sliders: Record<Slider, number>;
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
on: true,
|
||||
auto: false,
|
||||
|
|
@ -45,9 +52,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;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import {
|
|||
WebampWindow,
|
||||
WindowId,
|
||||
WindowInfo,
|
||||
LoadedURLTrack
|
||||
LoadedURLTrack,
|
||||
SerializedStateV1
|
||||
} from "./types";
|
||||
import { createSelector } from "reselect";
|
||||
import {
|
||||
|
|
@ -26,6 +27,7 @@ import {
|
|||
import { createPlaylistURL } from "./playlistHtml";
|
||||
import * as fromPlaylist from "./reducers/playlist";
|
||||
import * as fromDisplay from "./reducers/display";
|
||||
import * as fromEqualizer from "./reducers/equalizer";
|
||||
import { generateGraph } from "./resizeUtils";
|
||||
|
||||
export const getSliders = (state: AppState) => state.equalizer.sliders;
|
||||
|
|
@ -403,3 +405,10 @@ export const getVolume = (state: AppState) => state.media.volume;
|
|||
export const getBalance = (state: AppState) => state.media.balance;
|
||||
|
||||
export const getChannels = (state: AppState) => state.media.channels;
|
||||
export function getSerlializedState(state: AppState): SerializedStateV1 {
|
||||
return {
|
||||
version: 1,
|
||||
equalizer: fromEqualizer.getSerializedState(state.equalizer),
|
||||
display: fromDisplay.getSerializedState(state.display)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
21
js/types.ts
21
js/types.ts
|
|
@ -2,9 +2,12 @@ import { PlaylistState } from "./reducers/playlist";
|
|||
import { SettingsState } from "./reducers/settings";
|
||||
import { UserInputState } from "./reducers/userInput";
|
||||
import { MediaState } from "./reducers/media";
|
||||
import { DisplayState } from "./reducers/display";
|
||||
import { DisplayState, DisplaySerializedStateV1 } from "./reducers/display";
|
||||
import { WindowState } from "./reducers/windows";
|
||||
import { EqualizerState } from "./reducers/equalizer";
|
||||
import {
|
||||
EqualizerState,
|
||||
EqualizerSerializedStateV1
|
||||
} from "./reducers/equalizer";
|
||||
import { NetworkState } from "./reducers/network";
|
||||
|
||||
export type Skin = {
|
||||
|
|
@ -341,6 +344,10 @@ export type Action =
|
|||
| {
|
||||
type: "CLOSE_REQUESTED";
|
||||
cancel: () => void;
|
||||
}
|
||||
| {
|
||||
type: "LOAD_SERIALIZED_STATE";
|
||||
serializedState: SerializedStateV1;
|
||||
};
|
||||
|
||||
export interface WebampWindow {
|
||||
|
|
@ -491,3 +498,13 @@ export interface MiddlewareStore {
|
|||
dispatch: Dispatch;
|
||||
getState: GetState;
|
||||
}
|
||||
|
||||
export interface SerializedStateV1 {
|
||||
version: 1;
|
||||
/*
|
||||
windows: WindowSerializedState;
|
||||
settings: SettingsSerializedState;
|
||||
*/
|
||||
display: DisplaySerializedStateV1;
|
||||
equalizer: EqualizerSerializedStateV1;
|
||||
}
|
||||
|
|
|
|||
16
js/utils.ts
16
js/utils.ts
|
|
@ -268,6 +268,22 @@ export function debounce<F extends Procedure>(func: F, delay: number): F {
|
|||
} as any;
|
||||
}
|
||||
|
||||
// Trailing edge only throttle
|
||||
export function throttle<F extends Procedure>(func: F, delay: number): F {
|
||||
let timeout: NodeJS.Timer | null = null;
|
||||
let callbackArgs: any[] | null = null;
|
||||
|
||||
return function(this: any, ...args: any[]): void {
|
||||
callbackArgs = args;
|
||||
if (!timeout) {
|
||||
timeout = setTimeout(() => {
|
||||
func.apply(this, callbackArgs);
|
||||
timeout = null;
|
||||
}, delay);
|
||||
}
|
||||
} as any;
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
export function uniqueId() {
|
||||
return counter++;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ import {
|
|||
LOADED,
|
||||
REGISTER_VISUALIZER,
|
||||
SET_Z_INDEX,
|
||||
CLOSE_REQUESTED
|
||||
CLOSE_REQUESTED,
|
||||
LOAD_SERIALIZED_STATE
|
||||
} from "./actionTypes";
|
||||
import Emitter from "./emitter";
|
||||
|
||||
|
|
@ -231,6 +232,18 @@ class Winamp {
|
|||
return storeHas(this.store, state => !state.display.loading);
|
||||
}
|
||||
|
||||
loadSerializedState(serializedState) {
|
||||
this.store.dispatch({ type: LOAD_SERIALIZED_STATE, serializedState });
|
||||
}
|
||||
|
||||
getSerializedState() {
|
||||
return Selectors.getSerlializedState(this.store.getState());
|
||||
}
|
||||
|
||||
onStateChange(cb) {
|
||||
return this.store.subscribe(cb);
|
||||
}
|
||||
|
||||
async renderWhenReady(node) {
|
||||
await this.skinIsLoaded();
|
||||
const genWindowComponents = {};
|
||||
|
|
|
|||
|
|
@ -89,6 +89,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",
|
||||
|
|
|
|||
11
yarn.lock
11
yarn.lock
|
|
@ -4150,6 +4150,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"
|
||||
|
|
@ -6720,6 +6727,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