Serialize state to indexdb

This commit is contained in:
Jordan Eldredge 2018-09-19 21:43:55 -07:00
parent 379aa70184
commit 60429b280a
11 changed files with 171 additions and 9 deletions

View file

@ -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";

View file

@ -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
View 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));
}

View file

@ -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];
};

View file

@ -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;

View file

@ -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)
};
}

View file

@ -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;
}

View file

@ -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++;

View file

@ -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 = {};

View file

@ -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",

View file

@ -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"