Convert all reducers to typescript

This commit is contained in:
Jordan Eldredge 2018-09-17 08:32:51 -07:00
parent 565e8d0f7e
commit 6c17184488
7 changed files with 339 additions and 76 deletions

View file

@ -1,5 +1,17 @@
import { Band, MediaTagRequestStatus } from "./types";
import * as baseSkin from "./baseSkin.json";
export const BANDS = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000];
export const BANDS: Band[] = [
60,
170,
310,
600,
1000,
3000,
6000,
12000,
14000,
16000
];
export const WINDOWS = {
MAIN: "main",
@ -12,7 +24,11 @@ export const LOAD_STYLE = {
PLAY: "PLAY"
};
export const MEDIA_TAG_REQUEST_STATUS = {
// TODO: Make this an enum?
export const MEDIA_TAG_REQUEST_STATUS: Record<
MediaTagRequestStatus,
MediaTagRequestStatus
> = {
INITIALIZED: "INITIALIZED",
FAILED: "FAILED",
COMPLETE: "COMPLETE",

View file

@ -1,3 +1,5 @@
import { EqualizerState, Action } from "./../types";
import { BANDS } from "../constants";
import {
SET_BAND_VALUE,
@ -6,20 +8,28 @@ import {
SET_EQ_OFF
} from "../actionTypes";
const equalizer = (state, action) => {
if (!state) {
state = {
on: true,
auto: false,
sliders: {
preamp: 50
}
};
BANDS.forEach(band => {
state.sliders[band] = 50;
});
return state;
const defaultState = {
on: true,
auto: false,
sliders: {
preamp: 50,
60: 50,
170: 50,
310: 50,
600: 50,
1000: 50,
3000: 50,
6000: 50,
12000: 50,
14000: 50,
16000: 50
}
};
const equalizer = (
state: EqualizerState = defaultState,
action: Action
): EqualizerState => {
switch (action.type) {
case SET_BAND_VALUE:
const newSliders = { ...state.sliders, [action.band]: action.value };

View file

@ -1,3 +1,4 @@
import { AppState, Action } from "../types";
import { combineReducers } from "redux";
import playlist from "./playlist";
@ -9,7 +10,7 @@ import equalizer from "./equalizer";
import network from "./network";
import settings from "./settings";
const reducer = combineReducers({
const reducer = combineReducers<AppState, Action>({
userInput,
windows,
display,

View file

@ -17,26 +17,28 @@ import {
} from "../actionTypes";
import { TIME_MODE, MEDIA_STATUS } from "../constants";
const media = (state: MediaState, action: Action): MediaState => {
if (!state) {
return {
timeMode: TIME_MODE.ELAPSED,
timeElapsed: 0,
length: null, // Consider renaming to "duration"
kbps: null,
khz: null,
// The winamp ini file declares the default volume as "200".
// The UI seems to show a default volume near 78, which would
// math with the default value being 200 out of 255.
volume: Math.round((200 / 255) * 100),
balance: 0,
channels: null,
shuffle: false,
repeat: false,
// TODO: Enforce possible values
status: MEDIA_STATUS.STOPPED
};
}
const defaultState = {
timeMode: TIME_MODE.ELAPSED,
timeElapsed: 0,
length: null, // Consider renaming to "duration"
kbps: null,
khz: null,
// The winamp ini file declares the default volume as "200".
// The UI seems to show a default volume near 78, which would
// math with the default value being 200 out of 255.
volume: Math.round((200 / 255) * 100),
balance: 0,
channels: null,
shuffle: false,
repeat: false,
// TODO: Enforce possible values
status: MEDIA_STATUS.STOPPED
};
const media = (
state: MediaState = defaultState,
action: Action
): MediaState => {
switch (action.type) {
// TODO: Make these constants
case PLAY:
@ -53,7 +55,7 @@ const media = (state: MediaState, action: Action): MediaState => {
const newMode =
state.timeMode === TIME_MODE.REMAINING
? TIME_MODE.ELAPSED
: TIME_MODE.TIME_REMAINING;
: TIME_MODE.REMAINING;
return { ...state, timeMode: newMode };
case UPDATE_TIME_ELAPSED:
return { ...state, timeElapsed: action.elapsed };

View file

@ -1,3 +1,4 @@
import { PlaylistState, PlaylistTrack, Action } from "../types";
import {
SET_MEDIA,
CLICKED_TRACK,
@ -25,6 +26,8 @@ import { MEDIA_TAG_REQUEST_STATUS } from "../constants";
import { filenameFromUrl } from "../fileUtils";
import { shuffle, moveSelected, objectMap, objectFilter } from "../utils";
const foo = objectMap({ foo: 1 }, String);
const defaultPlaylistState = {
trackOrder: [],
currentTrack: null,
@ -32,16 +35,22 @@ const defaultPlaylistState = {
lastSelectedIndex: null
};
const playlist = (state = defaultPlaylistState, action) => {
const playlist = (
state: PlaylistState = defaultPlaylistState,
action: Action
): PlaylistState => {
switch (action.type) {
case CLICKED_TRACK:
const clickedId = String(state.trackOrder[action.index]);
return {
...state,
tracks: objectMap(state.tracks, (track, id) => ({
...track,
selected: id === clickedId
})),
tracks: objectMap<PlaylistTrack, PlaylistTrack>(
state.tracks,
(track, id) => ({
...track,
selected: id === clickedId
})
),
lastSelectedIndex: action.index
};
case CTRL_CLICKED_TRACK:
@ -68,31 +77,43 @@ const playlist = (state = defaultPlaylistState, action) => {
const selected = new Set(state.trackOrder.slice(start, end + 1));
return {
...state,
tracks: objectMap(state.tracks, (track, trackId) => ({
...track,
selected: selected.has(Number(trackId))
}))
tracks: objectMap<PlaylistTrack, PlaylistTrack>(
state.tracks,
(track, trackId) => ({
...track,
selected: selected.has(Number(trackId))
})
)
};
case SELECT_ALL:
return {
...state,
tracks: objectMap(state.tracks, track => ({ ...track, selected: true }))
tracks: objectMap<PlaylistTrack, PlaylistTrack>(
state.tracks,
track => ({ ...track, selected: true })
)
};
case SELECT_ZERO:
return {
...state,
tracks: objectMap(state.tracks, track => ({
...track,
selected: false
}))
tracks: objectMap<PlaylistTrack, PlaylistTrack>(
state.tracks,
track => ({
...track,
selected: false
})
)
};
case INVERT_SELECTION:
return {
...state,
tracks: objectMap(state.tracks, track => ({
...track,
selected: !track.selected
}))
tracks: objectMap<PlaylistTrack, PlaylistTrack>(
state.tracks,
track => ({
...track,
selected: !track.selected
})
)
};
case REMOVE_ALL_TRACKS:
// TODO: Consider disposing of ObjectUrls
@ -112,7 +133,9 @@ const playlist = (state = defaultPlaylistState, action) => {
trackOrder: state.trackOrder.filter(
trackId => !actionIds.includes(trackId)
),
currentTrack: actionIds.includes(currentTrack) ? null : currentTrack,
currentTrack: actionIds.includes(Number(currentTrack))
? null
: currentTrack,
tracks: objectFilter(
state.tracks,
(track, trackId) => !action.ids.includes(trackId)
@ -159,18 +182,20 @@ const playlist = (state = defaultPlaylistState, action) => {
// TODO: This could probably be made to work, but we clear it just to be safe.
lastSelectedIndex: null
};
case SET_MEDIA:
case SET_MEDIA: {
const newTrack = {
...state.tracks[action.id],
duration: action.length
};
return {
...state,
tracks: {
...state.tracks,
[action.id]: {
...state.tracks[action.id],
duration: action.length
}
[action.id]: newTrack
}
};
case SET_MEDIA_TAGS:
}
case SET_MEDIA_TAGS: {
return {
...state,
tracks: {
@ -184,6 +209,7 @@ const playlist = (state = defaultPlaylistState, action) => {
}
}
};
}
case MEDIA_TAG_REQUEST_INITIALIZED:
return {
...state,
@ -206,17 +232,19 @@ const playlist = (state = defaultPlaylistState, action) => {
}
}
};
case SET_MEDIA_DURATION:
case SET_MEDIA_DURATION: {
const newTrack = {
...state.tracks[action.id],
duration: action.duration
};
return {
...state,
tracks: {
...state.tracks,
[action.id]: {
...state.tracks[action.id],
duration: action.duration
}
[action.id]: newTrack
}
};
}
case PLAY_TRACK:
case BUFFER_TRACK:
return {
@ -241,7 +269,10 @@ const playlist = (state = defaultPlaylistState, action) => {
export default playlist;
export const getTrackDisplayName = (state, id) => {
export const getTrackDisplayName = (
state: PlaylistState,
id: number
): string | null => {
const track = state.tracks[id];
if (track == null) {
return null;

View file

@ -1,3 +1,4 @@
import { WindowState, Action } from "../types";
import { WINDOWS } from "../constants";
import {
SET_FOCUSED_WINDOW,
@ -10,7 +11,7 @@ import {
TOGGLE_WINDOW_SHADE_MODE
} from "../actionTypes";
const defaultWindowsState = {
const defaultWindowsState: WindowState = {
focused: WINDOWS.MAIN,
genWindows: {
// TODO: Remove static capabilites and derive them from ids/generic
@ -54,7 +55,10 @@ const defaultWindowsState = {
positions: {}
};
const windows = (state = defaultWindowsState, action) => {
const windows = (
state: WindowState = defaultWindowsState,
action: Action
): WindowState => {
switch (action.type) {
case SET_FOCUSED_WINDOW:
return { ...state, focused: action.window };
@ -62,8 +66,9 @@ const windows = (state = defaultWindowsState, action) => {
const { canShade } = state.genWindows[action.windowId];
if (!canShade) {
throw new Error(
"Tried to shade/unshade a window that cannot be shaded:",
action.windowId
`Tried to shade/unshade a window that cannot be shaded: ${
action.windowId
}`
);
}
return {
@ -133,8 +138,7 @@ const windows = (state = defaultWindowsState, action) => {
const { canResize } = state.genWindows[action.windowId];
if (!canResize) {
throw new Error(
"Tried to resize a window that cannot be resized:",
action.windowId
`Tried to resize a window that cannot be resized: ${action.windowId}`
);
}
return {

View file

@ -3,7 +3,18 @@ type Skin = {
name: string;
};
type Band = null; // TODO: Use a real type here.
export type Band =
| 60
| 170
| 310
| 600
| 1000
| 3000
| 6000
| 12000
| 14000
| 16000;
type Slider = Band | "preamp";
// TODO: Use a type to ensure these keys mirror the CURSORS constant in
// skinParser.js
@ -20,6 +31,8 @@ type SkinImages = { [sprite: string]: string };
// TODO: type these keys
type SkinRegion = { [windowName: string]: string[] };
type WindowId = string;
// TODO: Fill these out once we actually use them.
type SkinData = {
skinImages: SkinImages;
@ -69,9 +82,15 @@ export type Action =
}
| {
type: "ADD_TRACK_FROM_URL";
atIndex: number | null;
id: string;
defaultName: string;
duration: number | null;
url: string;
}
| {
type: "SET_MEDIA";
id: number;
length: number;
kbps: number;
khz: number;
@ -163,6 +182,127 @@ export type Action =
| {
type: "SET_DUMMY_VIZ_DATA";
data: null;
}
| {
type: "SET_BAND_VALUE";
band: Slider;
value: number;
}
| {
type: "SET_EQ_ON";
}
| {
type: "SET_EQ_OFF";
}
| {
type: "SET_EQ_AUTO";
value: boolean;
}
| {
type: "SET_FOCUSED_WINDOW";
window: WindowId;
}
| {
type: "TOGGLE_WINDOW_SHADE_MODE";
windowId: WindowId;
}
| {
type: "TOGGLE_WINDOW";
windowId: WindowId;
}
| {
type: "CLOSE_WINDOW";
windowId: WindowId;
}
| {
type: "SET_WINDOW_VISIBILITY";
windowId: WindowId;
hidden: boolean;
}
| {
type: "ADD_GEN_WINDOW";
windowId: WindowId;
title: string;
open: boolean;
}
| {
type: "WINDOW_SIZE_CHANGED";
windowId: WindowId;
size: [number, number];
}
| {
type: "UPDATE_WINDOW_POSITIONS";
positions: {};
}
| {
type: "CLICKED_TRACK";
index: number;
}
| {
type: "CTRL_CLICKED_TRACK";
index: number;
}
| {
type: "SHIFT_CLICKED_TRACK";
index: number;
}
| {
type: "SELECT_ALL";
}
| {
type: "SELECT_ZERO";
}
| {
type: "INVERT_SELECTION";
}
| {
type: "REMOVE_ALL_TRACKS";
}
| {
type: "REMOVE_TRACKS";
ids: string[];
}
| {
type: "REVERSE_LIST";
}
| {
type: "RANDOMIZE_LIST";
}
| {
type: "SET_TRACK_ORDER";
trackOrder: number[];
}
| {
type: "SET_MEDIA_TAGS";
id: number;
title: string;
artist: string;
albumArtUrl: string;
}
| {
type: "MEDIA_TAG_REQUEST_INITIALIZED";
id: number;
}
| {
type: "MEDIA_TAG_REQUEST_FAILED";
id: number;
}
| {
type: "SET_MEDIA_DURATION";
id: number;
duration: number;
}
| {
type: "PLAY_TRACK";
id: number;
}
| {
type: "BUFFER_TRACK";
id: number;
}
| {
type: "DRAG_SELECTED";
offset: number;
};
export interface SettingsState {
@ -189,7 +329,7 @@ export interface MediaState {
export interface UserInputState {
focus: string | null; // TODO: Convert this to an enum?
bandFocused: Band;
bandFocused: Band | null;
scrubPosition: number;
userMessage: string | null;
}
@ -214,3 +354,62 @@ export interface DisplayState {
zIndex: number;
dummyVizData: null; // TODO: Figure out what kind of data this actually is.
}
export interface EqualizerState {
on: boolean;
auto: boolean;
sliders: Record<Slider, number>;
}
export interface Window {
title: string;
size: [number, number];
open: boolean;
hidden: boolean;
shade?: boolean;
canResize: boolean;
canShade: boolean;
canDouble: boolean;
generic: boolean;
hotkey?: string;
}
export interface WindowState {
focused: string;
genWindows: { [name: string]: Window };
positions: {}; // TODO: Make this more strict
}
export type MediaTagRequestStatus =
| "INITIALIZED"
| "FAILED"
| "COMPLETE"
| "NOT_REQUESTED";
export interface PlaylistTrack {
artist: string;
title: string;
url: string;
defaultName: string;
albumArtUrl: string | null;
selected: boolean;
mediaTagsRequestStatus: MediaTagRequestStatus;
}
export interface PlaylistState {
trackOrder: number[];
tracks: { [id: number]: PlaylistTrack };
lastSelectedIndex: number | null;
currentTrack: number | null;
}
export interface AppState {
userInput: UserInputState;
windows: WindowState;
display: DisplayState;
settings: SettingsState;
equalizer: EqualizerState;
playlist: PlaylistState;
media: MediaState;
network: NetworkState;
}