Change how actions are typed

If we want to use the new version of react-redux we need to be compatible with their types.

Sadly it does not seem to be possible to create a Dispatchable type which can be eiher a plain action or a thunk and still be compatible with react-redux's types becuase they use redux's Store type under the hood which assumes you have an object with a string type property.
This commit is contained in:
Jordan Eldredge 2019-04-13 11:21:57 -07:00
parent 9c7d6e2bda
commit c106ac2a52
10 changed files with 120 additions and 136 deletions

View file

@ -6,7 +6,7 @@ import {
SET_BAND_VALUE,
SET_EQ_AUTO,
} from "../actionTypes";
import { Band, Dispatchable } from "../types";
import { Band, Thunk, Action } from "../types";
const BAND_SNAP_DISTANCE = 5;
const BAND_MID_POINT_VALUE = 50;
@ -21,11 +21,11 @@ function _snapBandValue(value: number): number {
return value;
}
export function setEqBand(band: Band, value: number): Dispatchable {
export function setEqBand(band: Band, value: number): Action {
return { type: SET_BAND_VALUE, band, value: _snapBandValue(value) };
}
function _setEqTo(value: number): Dispatchable {
function _setEqTo(value: number): Thunk {
return dispatch => {
Object.values(BANDS).forEach(band => {
dispatch({
@ -37,23 +37,23 @@ function _setEqTo(value: number): Dispatchable {
};
}
export function setEqToMax(): Dispatchable {
export function setEqToMax(): Thunk {
return _setEqTo(100);
}
export function setEqToMid(): Dispatchable {
export function setEqToMid(): Thunk {
return _setEqTo(50);
}
export function setEqToMin(): Dispatchable {
export function setEqToMin(): Thunk {
return _setEqTo(0);
}
export function setPreamp(value: number): Dispatchable {
export function setPreamp(value: number): Action {
return { type: SET_BAND_VALUE, band: "preamp", value: _snapBandValue(value) };
}
export function toggleEq(): Dispatchable {
export function toggleEq(): Thunk {
return (dispatch, getState) => {
if (getState().equalizer.on) {
dispatch({ type: SET_EQ_OFF });
@ -63,7 +63,7 @@ export function toggleEq(): Dispatchable {
};
}
export function toggleEqAuto(): Dispatchable {
export function toggleEqAuto(): Thunk {
return (dispatch, getState) => {
dispatch({ type: SET_EQ_AUTO, value: !getState().equalizer.auto });
};

View file

@ -33,7 +33,7 @@ import LoadQueue from "../loadQueue";
import { removeAllTracks } from "./playlist";
import { setPreamp, setEqBand } from "./equalizer";
import { LoadStyle, Dispatchable, Track, EqfPreset } from "../types";
import { LoadStyle, Thunk, Track, EqfPreset, SkinData } from "../types";
// Lower is better
const DURATION_VISIBLE_PRIORITY = 5;
@ -47,7 +47,7 @@ export function addTracksFromReferences(
fileReferences: FileList,
loadStyle: LoadStyle,
atIndex: number | undefined
): Dispatchable {
): Thunk {
const tracks: Track[] = Array.from(fileReferences).map(file => ({
blob: file,
defaultName: file.name,
@ -61,7 +61,7 @@ export function loadFilesFromReferences(
fileReferences: FileList,
loadStyle: LoadStyle = LOAD_STYLE.PLAY,
atIndex: number | undefined = undefined
): Dispatchable {
): Thunk {
return dispatch => {
if (fileReferences.length < 1) {
return;
@ -79,9 +79,7 @@ export function loadFilesFromReferences(
};
}
export function setSkinFromBlob(
arrayBuffer: Blob | Promise<Blob>
): Dispatchable {
export function setSkinFromBlob(blob: Blob | Promise<Blob>): Thunk {
return async (dispatch, getState, { requireJSZip }) => {
if (!requireJSZip) {
alert("Webamp has not been configured to support custom skins.");
@ -98,8 +96,7 @@ export function setSkinFromBlob(
return;
}
try {
const skinData = await skinParser(arrayBuffer, JSZip);
// @ts-ignore TODO: We still need to type skinParser.
const skinData = await skinParser(blob, JSZip);
dispatch({
type: SET_SKIN_DATA,
data: {
@ -110,7 +107,7 @@ export function setSkinFromBlob(
skinRegion: skinData.region,
skinGenLetterWidths: skinData.genLetterWidths,
skinGenExColors: skinData.genExColors,
},
} as SkinData,
});
} catch (e) {
console.error(e);
@ -120,7 +117,7 @@ export function setSkinFromBlob(
};
}
export function setSkinFromUrl(url: string): Dispatchable {
export function setSkinFromUrl(url: string): Thunk {
return async dispatch => {
dispatch({ type: LOADING });
try {
@ -140,18 +137,18 @@ export function setSkinFromUrl(url: string): Dispatchable {
// This function is private, since Winamp consumers can provide means for
// opening files via other methods. Only use the file type specific
// versions below, since they can defer to the user-defined behavior.
function _openFileDialog(accept: string | null): Dispatchable {
function _openFileDialog(accept: string | null): Thunk {
return async dispatch => {
const fileReferences = await promptForFileReferences({ accept });
dispatch(loadFilesFromReferences(fileReferences));
};
}
export function openEqfFileDialog(): Dispatchable {
export function openEqfFileDialog(): Thunk {
return _openFileDialog(".eqf");
}
export function openMediaFileDialog(): Dispatchable {
export function openMediaFileDialog(): Thunk {
return _openFileDialog(null);
}
@ -159,7 +156,7 @@ export function openSkinFileDialog() {
return _openFileDialog(".zip, .wsz");
}
export function fetchMediaDuration(url: string, id: number): Dispatchable {
export function fetchMediaDuration(url: string, id: number): Thunk {
return (dispatch, getState) => {
loadQueue.push(
async () => {
@ -185,7 +182,7 @@ export function loadMedia(
e: React.DragEvent<HTMLDivElement>,
loadStyle: LoadStyle = LOAD_STYLE.NONE,
atIndex = 0
): Dispatchable {
): Thunk {
const { files } = e.dataTransfer;
return async (dispatch, getState, { handleTrackDropEvent }) => {
if (handleTrackDropEvent) {
@ -204,7 +201,7 @@ export function loadMediaFiles(
tracks: Track[],
loadStyle: LoadStyle = LOAD_STYLE.NONE,
atIndex = 0
): Dispatchable {
): Thunk {
return dispatch => {
if (loadStyle === LOAD_STYLE.PLAY) {
// I'm the worst. It just so happens that in every case that we autoPlay,
@ -222,7 +219,7 @@ export function loadMediaFile(
track: Track,
priority: LoadStyle = LOAD_STYLE.NONE,
atIndex = 0
): Dispatchable {
): Thunk {
return dispatch => {
const id = Utils.uniqueId();
const { defaultName, metaData, duration } = track;
@ -285,7 +282,7 @@ export function loadMediaFile(
};
}
function queueFetchingMediaTags(id: number): Dispatchable {
function queueFetchingMediaTags(id: number): Thunk {
return (dispatch, getState) => {
const track = getTracks(getState())[id];
loadQueue.push(
@ -300,7 +297,7 @@ function queueFetchingMediaTags(id: number): Dispatchable {
};
}
export function fetchMediaTags(file: string | Blob, id: number): Dispatchable {
export function fetchMediaTags(file: string | Blob, id: number): Thunk {
return async (dispatch, getState, { requireMusicMetadata }) => {
dispatch({ type: MEDIA_TAG_REQUEST_INITIALIZED, id });
@ -332,7 +329,7 @@ export function fetchMediaTags(file: string | Blob, id: number): Dispatchable {
};
}
export function setEqFromFileReference(fileReference: File): Dispatchable {
export function setEqFromFileReference(fileReference: File): Thunk {
return async dispatch => {
const arrayBuffer = await genArrayBufferFromFileReference(fileReference);
const eqf = parser(arrayBuffer);
@ -341,7 +338,7 @@ export function setEqFromFileReference(fileReference: File): Dispatchable {
};
}
export function setEqFromObject(preset: EqfPreset): Dispatchable {
export function setEqFromObject(preset: EqfPreset): Thunk {
return dispatch => {
dispatch(setPreamp(Utils.normalizeEqBand(preset.preamp)));
BANDS.forEach(band => {
@ -351,7 +348,7 @@ export function setEqFromObject(preset: EqfPreset): Dispatchable {
};
}
export function downloadPreset(): Dispatchable {
export function downloadPreset(): Thunk {
return (dispatch, getState) => {
const state = getState();
const data = getEqfData(state);
@ -362,7 +359,7 @@ export function downloadPreset(): Dispatchable {
};
}
export function downloadHtmlPlaylist(): Dispatchable {
export function downloadHtmlPlaylist(): Thunk {
return (dispatch, getState) => {
const uri = getPlaylistURL(getState());
Utils.downloadURI(uri, "Winamp Playlist.html");

View file

@ -13,7 +13,7 @@ import {
SET_MILKDROP_FULLSCREEN,
} from "../actionTypes";
import { WINDOWS } from "../constants";
import { Dispatchable } from "../types";
import { Thunk, Action } from "../types";
import { SerializedStateV1 } from "../serializedStates/v1Types";
import * as Selectors from "../selectors";
import { ensureWindowsAreOnScreen, showWindow, hideWindow } from "./windows";
@ -112,7 +112,7 @@ export {
scheduleMilkdropMessage,
} from "./milkdrop";
export function close(): Dispatchable {
export function close(): Thunk {
return dispatch => {
// TODO: This could probably be improved by adding a "PREVENT_CLOSE" action
// or something, but this works okay for now.
@ -128,41 +128,41 @@ export function close(): Dispatchable {
};
}
export function open(): Dispatchable {
export function open(): Action {
return { type: OPEN_WINAMP };
}
export function toggleVisualizerStyle(): Dispatchable {
export function toggleVisualizerStyle(): Action {
return { type: TOGGLE_VISUALIZER_STYLE };
}
export function minimize(): Dispatchable {
export function minimize(): Action {
return { type: MINIMIZE_WINAMP };
}
export function setFocus(input: string): Dispatchable {
export function setFocus(input: string): Action {
return { type: SET_FOCUS, input };
}
export function unsetFocus(): Dispatchable {
export function unsetFocus(): Action {
return { type: UNSET_FOCUS };
}
export function loadSerializedState(
// In the future this type should be the union of all versioned types.
serializedState: SerializedStateV1
): Dispatchable {
): Thunk {
return dispatch => {
dispatch({ type: LOAD_SERIALIZED_STATE, serializedState });
dispatch(ensureWindowsAreOnScreen());
};
}
export function loadDefaultSkin(): Dispatchable {
export function loadDefaultSkin(): Action {
return { type: LOAD_DEFAULT_SKIN };
}
export function toggleMilkdropDesktop(): Dispatchable {
export function toggleMilkdropDesktop(): Thunk {
return (dispatch, getState) => {
if (Selectors.getMilkdropDesktopEnabled(getState())) {
dispatch(showWindow(WINDOWS.MILKDROP));
@ -174,10 +174,10 @@ export function toggleMilkdropDesktop(): Dispatchable {
};
}
export function setMilkdropFullscreen(enabled: boolean): Dispatchable {
export function setMilkdropFullscreen(enabled: boolean): Action {
return { type: SET_MILKDROP_FULLSCREEN, enabled };
}
export function toggleMilkdropFullscreen(): Dispatchable {
export function toggleMilkdropFullscreen(): Thunk {
return (dispatch, getState) => {
dispatch(
setMilkdropFullscreen(!Selectors.getMilkdropFullscreenEnabled(getState()))

View file

@ -16,10 +16,10 @@ import {
import { MEDIA_STATUS } from "../constants";
import { openMediaFileDialog } from "./";
import { GetState, Dispatch, Dispatchable } from "../types";
import { GetState, Dispatch, Thunk, Action } from "../types";
import * as Selectors from "../selectors";
export function playTrack(id: number): Dispatchable {
export function playTrack(id: number): Thunk {
return (dispatch, getState) => {
const state = getState();
const isStopped = Selectors.getMediaStatus(state) === MEDIA_STATUS.STOPPED;
@ -31,7 +31,7 @@ export function playTrack(id: number): Dispatchable {
};
}
export function play(): Dispatchable {
export function play(): Thunk {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
if (
@ -46,7 +46,7 @@ export function play(): Dispatchable {
};
}
export function pause(): Dispatchable {
export function pause(): Thunk {
return (dispatch, getState) => {
const { status } = getState().media;
if (status === MEDIA_STATUS.PLAYING) {
@ -57,11 +57,11 @@ export function pause(): Dispatchable {
};
}
export function stop(): Dispatchable {
export function stop(): Action {
return { type: STOP };
}
export function nextN(n: number): Dispatchable {
export function nextN(n: number): Thunk {
return (dispatch, getState) => {
const nextTrackId = Selectors.getNextTrackId(getState(), n);
if (nextTrackId == null) {
@ -72,15 +72,15 @@ export function nextN(n: number): Dispatchable {
};
}
export function next(): Dispatchable {
export function next(): Thunk {
return nextN(1);
}
export function previous(): Dispatchable {
export function previous(): Thunk {
return nextN(-1);
}
export function seekToTime(seconds: number): Dispatchable {
export function seekToTime(seconds: number): Thunk {
return function(dispatch, getState) {
const state = getState();
const duration = Selectors.getDuration(state);
@ -93,34 +93,32 @@ export function seekToTime(seconds: number): Dispatchable {
});
};
}
export function seekForward(seconds: number): Dispatchable {
export function seekForward(seconds: number): Thunk {
return function(dispatch, getState) {
const timeElapsed = Selectors.getTimeElapsed(getState());
dispatch(seekToTime(timeElapsed + seconds));
};
}
export function seekBackward(seconds: number): Dispatchable {
export function seekBackward(seconds: number): Thunk {
return seekForward(-seconds);
}
export function setVolume(volume: number): Dispatchable {
export function setVolume(volume: number): Action {
return {
type: SET_VOLUME,
volume: clamp(volume, 0, 100),
};
}
export function adjustVolume(volumeDiff: number): Dispatchable {
export function adjustVolume(volumeDiff: number): Thunk {
return (dispatch, getState) => {
const currentVolume = getState().media.volume;
return dispatch(setVolume(currentVolume + volumeDiff));
};
}
export function scrollVolume(
e: React.WheelEvent<HTMLDivElement>
): Dispatchable {
export function scrollVolume(e: React.WheelEvent<HTMLDivElement>): Thunk {
e.preventDefault();
return (dispatch, getState) => {
const currentVolume = getState().media.volume;
@ -129,7 +127,7 @@ export function scrollVolume(
};
}
export function setBalance(balance: number): Dispatchable {
export function setBalance(balance: number): Action {
balance = clamp(balance, -100, 100);
// The balance clips to the center
if (Math.abs(balance) < 25) {
@ -141,14 +139,14 @@ export function setBalance(balance: number): Dispatchable {
};
}
export function toggleRepeat(): Dispatchable {
export function toggleRepeat(): Action {
return { type: TOGGLE_REPEAT };
}
export function toggleShuffle(): Dispatchable {
export function toggleShuffle(): Action {
return { type: TOGGLE_SHUFFLE };
}
export function toggleTimeMode(): Dispatchable {
export function toggleTimeMode(): Action {
return { type: TOGGLE_TIME_MODE };
}

View file

@ -11,11 +11,12 @@ import {
} from "../actionTypes";
import * as Selectors from "../selectors";
import {
Dispatchable,
TransitionType,
Preset,
ButterchurnOptions,
StatePreset,
Thunk,
Action,
} from "../types";
import * as FileUtils from "../fileUtils";
@ -46,9 +47,7 @@ function normalizePresetTypes(preset: Preset): StatePreset {
throw new Error("Invalid preset object");
}
export function initializePresets(
presetOptions: ButterchurnOptions
): Dispatchable {
export function initializePresets(presetOptions: ButterchurnOptions): Thunk {
return async dispatch => {
const { getPresets, importButterchurn } = presetOptions;
importButterchurn().then(butterchurn => {
@ -61,7 +60,7 @@ export function initializePresets(
};
}
export function loadPresets(presets: StatePreset[]): Dispatchable {
export function loadPresets(presets: StatePreset[]): Thunk {
return (dispatch, getState) => {
const state = getState();
const presetsLength = state.milkdrop.presets.length;
@ -76,7 +75,7 @@ export function loadPresets(presets: StatePreset[]): Dispatchable {
};
}
export function appendPresetFileList(fileList: FileList): Dispatchable {
export function appendPresetFileList(fileList: FileList): Thunk {
return async (dispatch, getState, { convertPreset }) => {
const presets: StatePreset[] = Array.from(fileList)
.map(file => {
@ -114,7 +113,7 @@ export function appendPresetFileList(fileList: FileList): Dispatchable {
export function selectNextPreset(
transitionType: TransitionType = TransitionType.DEFAULT
): Dispatchable {
): Thunk {
return (dispatch, getState) => {
const state = getState();
if (Selectors.getRandomizePresets(state)) {
@ -131,7 +130,7 @@ export function selectNextPreset(
export function selectPreviousPreset(
transitionType: TransitionType = TransitionType.DEFAULT
): Dispatchable {
): Thunk {
return (dispatch, getState) => {
const state = getState();
const { presetHistory } = state.milkdrop;
@ -147,7 +146,7 @@ export function selectPreviousPreset(
export function selectRandomPreset(
transitionType: TransitionType = TransitionType.DEFAULT
): Dispatchable {
): Thunk {
return (dispatch, getState) => {
const state = getState();
// TODO: Make this a selector.
@ -164,7 +163,7 @@ export function requestPresetAtIndex(
index: number,
transitionType: TransitionType,
addToHistory: boolean
): Dispatchable {
): Thunk {
return async (dispatch, getState) => {
const state = getState();
const preset = state.milkdrop.presets[index];
@ -187,22 +186,22 @@ export function requestPresetAtIndex(
};
}
export function handlePresetDrop(e: React.DragEvent): Dispatchable {
export function handlePresetDrop(e: React.DragEvent): Thunk {
return appendPresetFileList(e.dataTransfer.files);
}
export function togglePresetOverlay(): Dispatchable {
export function togglePresetOverlay(): Action {
return { type: TOGGLE_PRESET_OVERLAY };
}
export function toggleRandomizePresets(): Dispatchable {
export function toggleRandomizePresets(): Action {
return { type: TOGGLE_RANDOMIZE_PRESETS };
}
export function togglePresetCycling(): Dispatchable {
export function togglePresetCycling(): Action {
return { type: TOGGLE_PRESET_CYCLING };
}
export function scheduleMilkdropMessage(message: string): Dispatchable {
export function scheduleMilkdropMessage(message: string): Action {
return { type: SCHEDULE_MILKDROP_MESSAGE, message };
}

View file

@ -12,9 +12,9 @@ import {
SET_PLAYLIST_SCROLL_POSITION,
DRAG_SELECTED,
} from "../actionTypes";
import { Dispatchable } from "../types";
import { Thunk, Action } from "../types";
export function cropPlaylist(): Dispatchable {
export function cropPlaylist(): Thunk {
return (dispatch, getState) => {
const state = getState();
if (Selectors.getSelectedTrackObjects(state).length === 0) {
@ -33,7 +33,7 @@ export function cropPlaylist(): Dispatchable {
};
}
export function removeSelectedTracks(): Dispatchable {
export function removeSelectedTracks(): Thunk {
return (dispatch, getState) => {
dispatch({
type: REMOVE_TRACKS,
@ -44,7 +44,7 @@ export function removeSelectedTracks(): Dispatchable {
};
}
export function removeAllTracks(): Dispatchable {
export function removeAllTracks(): Thunk {
return dispatch => {
// It's a bit funky that we need to do both of these.
dispatch({ type: STOP });
@ -52,15 +52,15 @@ export function removeAllTracks(): Dispatchable {
};
}
export function reverseList(): Dispatchable {
export function reverseList(): Action {
return { type: REVERSE_LIST };
}
export function randomizeList(): Dispatchable {
export function randomizeList(): Action {
return { type: RANDOMIZE_LIST };
}
export function sortListByTitle(): Dispatchable {
export function sortListByTitle(): Thunk {
return (dispatch, getState) => {
const state = getState();
const tracks = Selectors.getTracks(state);
@ -71,11 +71,11 @@ export function sortListByTitle(): Dispatchable {
};
}
export function setPlaylistScrollPosition(position: number): Dispatchable {
export function setPlaylistScrollPosition(position: number): Action {
return { type: SET_PLAYLIST_SCROLL_POSITION, position };
}
export function scrollNTracks(n: number): Dispatchable {
export function scrollNTracks(n: number): Thunk {
return (dispatch, getState) => {
const state = getState();
const overflow = Selectors.getOverflowTrackCount(state);
@ -90,7 +90,7 @@ export function scrollNTracks(n: number): Dispatchable {
export function scrollPlaylistByDelta(
e: React.WheelEvent<HTMLDivElement>
): Dispatchable {
): Thunk {
e.preventDefault();
return (dispatch, getState) => {
const state = getState();
@ -110,15 +110,15 @@ export function scrollPlaylistByDelta(
};
}
export function scrollUpFourTracks(): Dispatchable {
export function scrollUpFourTracks(): Thunk {
return scrollNTracks(-4);
}
export function scrollDownFourTracks(): Dispatchable {
export function scrollDownFourTracks(): Thunk {
return scrollNTracks(4);
}
export function dragSelected(offset: number): Dispatchable {
export function dragSelected(offset: number): Thunk {
return (dispatch, getState) => {
const state = getState();
const tracks = Selectors.getTracks(state);

View file

@ -17,13 +17,7 @@ import {
import { getPositionDiff, SizeDiff } from "../resizeUtils";
import { applyDiff } from "../snapUtils";
import {
Action,
Dispatchable,
WindowId,
WindowPositions,
Dispatch,
} from "../types";
import { Action, Thunk, WindowId, WindowPositions, Dispatch } from "../types";
// Dispatch an action and, if needed rearrange the windows to preserve
// the existing edge relationship.
@ -31,7 +25,7 @@ import {
// Works by checking the edges before the action is dispatched. Then,
// after disatching, calculating what position change would be required
// to restore those relationships.
function withWindowGraphIntegrity(action: Action): Dispatchable {
function withWindowGraphIntegrity(action: Action): Thunk {
return (dispatch, getState) => {
const state = getState();
const graph = Selectors.getWindowGraph(state);
@ -61,70 +55,70 @@ function withWindowGraphIntegrity(action: Action): Dispatchable {
};
}
export function toggleDoubleSizeMode(): Dispatchable {
export function toggleDoubleSizeMode(): Thunk {
return withWindowGraphIntegrity({ type: TOGGLE_DOUBLESIZE_MODE });
}
export function toggleLlamaMode(): Dispatchable {
export function toggleLlamaMode(): Action {
return { type: TOGGLE_LLAMA_MODE };
}
export function toggleEqualizerShadeMode(): Dispatchable {
export function toggleEqualizerShadeMode(): Thunk {
return withWindowGraphIntegrity({
type: TOGGLE_WINDOW_SHADE_MODE,
windowId: "equalizer",
});
}
export function toggleMainWindowShadeMode(): Dispatchable {
export function toggleMainWindowShadeMode(): Thunk {
return withWindowGraphIntegrity({
type: TOGGLE_WINDOW_SHADE_MODE,
windowId: "main",
});
}
export function togglePlaylistShadeMode(): Dispatchable {
export function togglePlaylistShadeMode(): Thunk {
return withWindowGraphIntegrity({
type: TOGGLE_WINDOW_SHADE_MODE,
windowId: "playlist",
});
}
export function closeWindow(windowId: WindowId): Dispatchable {
export function closeWindow(windowId: WindowId): Action {
return { type: CLOSE_WINDOW, windowId };
}
export function hideWindow(windowId: WindowId): Dispatchable {
export function hideWindow(windowId: WindowId): Action {
return { type: SET_WINDOW_VISIBILITY, windowId, hidden: true };
}
export function showWindow(windowId: WindowId): Dispatchable {
export function showWindow(windowId: WindowId): Action {
return { type: SET_WINDOW_VISIBILITY, windowId, hidden: false };
}
export function setFocusedWindow(window: WindowId): Dispatchable {
export function setFocusedWindow(window: WindowId): Action {
return { type: SET_FOCUSED_WINDOW, window };
}
export function setWindowSize(
windowId: WindowId,
size: [number, number]
): Dispatchable {
): Action {
return { type: WINDOW_SIZE_CHANGED, windowId, size };
}
export function toggleWindow(windowId: WindowId): Dispatchable {
export function toggleWindow(windowId: WindowId): Action {
return { type: TOGGLE_WINDOW, windowId };
}
export function updateWindowPositions(
positions: WindowPositions,
absolute?: boolean
): Dispatchable {
): Action {
return { type: UPDATE_WINDOW_POSITIONS, positions, absolute };
}
export function centerWindowsInContainer(container: HTMLElement): Dispatchable {
export function centerWindowsInContainer(container: HTMLElement): Thunk {
return (dispatch, getState) => {
if (!Selectors.getPositionsAreRelative(getState())) {
return;
@ -135,7 +129,7 @@ export function centerWindowsInContainer(container: HTMLElement): Dispatchable {
};
}
export function centerWindowsInView(): Dispatchable {
export function centerWindowsInView(): Thunk {
return centerWindows({
left: window.scrollX,
top: window.scrollY,
@ -149,7 +143,7 @@ export function centerWindows(box: {
top: number;
width: number;
height: number;
}): Dispatchable {
}): Thunk {
return (dispatch, getState) => {
const state = getState();
const windowsInfo = Selectors.getWindowsInfo(state);
@ -188,18 +182,18 @@ export function centerWindows(box: {
export function browserWindowSizeChanged(size: {
height: number;
width: number;
}) {
}): Thunk {
return (dispatch: Dispatch) => {
dispatch({ type: BROWSER_WINDOW_SIZE_CHANGED, ...size });
dispatch(ensureWindowsAreOnScreen());
};
}
export function resetWindowSizes(): Dispatchable {
export function resetWindowSizes(): Action {
return { type: RESET_WINDOW_SIZES };
}
export function stackWindows(): Dispatchable {
export function stackWindows(): Thunk {
return (dispatch, getState) => {
dispatch(
updateWindowPositions(Selectors.getStackedLayoutPositions(getState()))
@ -207,7 +201,7 @@ export function stackWindows(): Dispatchable {
};
}
export function ensureWindowsAreOnScreen(): Dispatchable {
export function ensureWindowsAreOnScreen(): Thunk {
return (dispatch, getState) => {
const state = getState();

View file

@ -9,7 +9,7 @@ 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";
import { AppState, Action } from "./types";
function fixturePath(name: string) {
return path.join(
@ -29,7 +29,7 @@ function readFixture(name: string): SerializedStateV1 {
interface SerializationTestParams<T> {
name: string;
action: Dispatchable;
action: Action;
selector(state: AppState): T;
expected: T;
}
@ -132,6 +132,7 @@ describe("can serialize", () => {
/* Equalizer */
testSerialization({
name: "equalizer on",
// @ts-ignore
action: Actions.toggleEq(),
selector: Selectors.getEqualizerEnabled,
expected: false,
@ -139,6 +140,7 @@ describe("can serialize", () => {
testSerialization({
name: "equalizer auto",
// @ts-ignore
action: Actions.toggleEqAuto(),
selector: Selectors.getEqualizerAuto,
expected: true,
@ -161,6 +163,7 @@ describe("can serialize", () => {
/* Display */
testSerialization({
name: "double mode",
// @ts-ignore
action: Actions.toggleDoubleSizeMode(),
selector: Selectors.getDoubled,
expected: true,
@ -207,8 +210,9 @@ describe("can serialize", () => {
testSerialization({
name: "window shade",
// @ts-ignore
action: Actions.toggleEqualizerShadeMode(),
selector: state => Selectors.getWindowShade(state)("equalizer"),
selector: state => Boolean(Selectors.getWindowShade(state)("equalizer")),
expected: true,
});

View file

@ -2,6 +2,7 @@ import { PlaylistState } from "./reducers/playlist";
import { SettingsState } from "./reducers/settings";
import { UserInputState } from "./reducers/userInput";
import { MediaState } from "./reducers/media";
import { ThunkDispatch, ThunkAction } from "redux-thunk";
import { DisplayState } from "./reducers/display";
import { WindowsState, WindowPositions } from "./reducers/windows";
import { EqualizerState } from "./reducers/equalizer";
@ -110,7 +111,7 @@ export interface SkinGenExColors {
export type WindowId = string;
// TODO: Fill these out once we actually use them.
type SkinData = {
export type SkinData = {
skinImages: SkinImages;
skinColors: string[];
skinPlaylistStyle: PlaylistStyle;
@ -661,19 +662,9 @@ export interface Extras {
export type GetState = () => AppState;
export type Thunk = (
dispatch: Dispatch,
getState: GetState,
extras: Extras
) => void | Promise<void>;
export type Thunk = ThunkAction<void, AppState, Extras, Action>;
export type Dispatchable = Action | Thunk;
export interface DispatchObject {
[prop: string]: (...args: any[]) => Dispatchable;
}
export type Dispatch = (action: Dispatchable) => void;
export type Dispatch = ThunkDispatch<AppState, Extras, Action>;
export type Reducer = (state: AppState, action: Action) => AppState;

View file

@ -10,6 +10,7 @@ import {
Middleware,
WindowPosition,
ButterchurnOptions,
Action,
} from "./types";
import getStore from "./store";
import App from "./components/App";
@ -418,7 +419,7 @@ class Winamp {
});
ReactDOM.render(
<Provider store={this.store}>
<Provider<Action> store={this.store}>
<App
media={this.media}
container={node}