mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-25 19:13:54 +00:00
Convert all reducers to typescript
This commit is contained in:
parent
565e8d0f7e
commit
6c17184488
7 changed files with 339 additions and 76 deletions
|
|
@ -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",
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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,
|
||||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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 {
|
||||
203
js/types.ts
203
js/types.ts
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue