Fading and keyboard shortcuts

This commit is contained in:
Marks Polakovs 2020-03-19 22:24:00 +00:00
parent 7b8da70aea
commit 4151b753f5
6 changed files with 523 additions and 338 deletions

View file

@ -5,6 +5,7 @@
"dependencies": {
"@reduxjs/toolkit": "^1.0.4",
"@types/jest": "24.0.22",
"@types/keymaster": "^1.6.28",
"@types/lodash": "^4.14.149",
"@types/node": "12.12.7",
"@types/qs": "^6.9.0",
@ -14,6 +15,7 @@
"@types/react-redux": "^7.1.5",
"@types/webpack-env": "^1.14.1",
"between.js": "^0.1.2-fix.2",
"keymaster": "^1.6.2",
"lodash": "^4.17.15",
"qs": "^6.9.1",
"react": "^0.0.0-experimental-38dd17ab9",

628
src/lib/between.d.ts vendored
View file

@ -2,374 +2,376 @@
// Declare your modules properly, people!
declare module "between.js" {
/**
* Loop mode
*/
export type LoopMode = "repeat" | "bounce";
export type EventEmmit = "start" | "update" | "complete";
/**
* A collection of easing methods defining ease-in ease-out curves.
*/
export interface Easing extends Function {
declare namespace Between {
/**
* Linear easing.
*
* @class Easing.Linear
* Loop mode
*/
Linear: {
/**
* Ease-in.
*
* @method Easing.Linear#In
* @param {number} k - The value to be tweened.
* @returns {number} k^2.
*/
None: Function;
};
export type LoopMode = "repeat" | "bounce";
export type EventEmmit = "start" | "update" | "complete";
/**
* Quadratic easing.
*
* @class Easing.Quadratic
* A collection of easing methods defining ease-in ease-out curves.
*/
Quadratic: {
export interface Easing extends Function {
/**
* Ease-in.
* Linear easing.
*
* @method Easing.Quadratic#In
* @param {number} k - The value to be tweened.
* @returns {number} k^2.
* @class Easing.Linear
*/
In: Function;
Linear: {
/**
* Ease-in.
*
* @method Easing.Linear#In
* @param {number} k - The value to be tweened.
* @returns {number} k^2.
*/
None: Function;
};
/**
* Ease-out.
* Quadratic easing.
*
* @method Easing.Quadratic#Out
* @param {number} k - The value to be tweened.
* @returns {number} k* (2-k).
* @class Easing.Quadratic
*/
Out: Function;
Quadratic: {
/**
* Ease-in.
*
* @method Easing.Quadratic#In
* @param {number} k - The value to be tweened.
* @returns {number} k^2.
*/
In: Function;
/**
* Ease-out.
*
* @method Easing.Quadratic#Out
* @param {number} k - The value to be tweened.
* @returns {number} k* (2-k).
*/
Out: Function;
/**
* Ease-in/out.
*
* @method Easing.Quadratic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Ease-in/out.
* Cubic easing.
*
* @method Easing.Quadratic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
* @class Easing.Cubic
*/
InOut: Function;
};
Cubic: {
/**
* Cubic ease-in.
*
* @method Easing.Cubic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Cubic easing.
*
* @class Easing.Cubic
*/
Cubic: {
/**
* Cubic ease-in.
*
* @method Easing.Cubic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Cubic ease-out.
*
* @method Easing.Cubic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Cubic ease-in/out.
*
* @method Easing.Cubic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Cubic ease-out.
* Quartic easing.
*
* @method Easing.Cubic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
* @class Easing.Quartic
*/
Out: Function;
Quartic: {
/**
* Quartic ease-in.
*
* @method Easing.Quartic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Quartic ease-out.
*
* @method Easing.Quartic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Quartic ease-in/out.
*
* @method Easing.Quartic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Cubic ease-in/out.
* Quintic easing.
*
* @method Easing.Cubic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
* @class Easing.Quintic
*/
InOut: Function;
};
Quintic: {
/**
* Quintic ease-in.
*
* @method Easing.Quintic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Quartic easing.
*
* @class Easing.Quartic
*/
Quartic: {
/**
* Quartic ease-in.
*
* @method Easing.Quartic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Quintic ease-out.
*
* @method Easing.Quintic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Quintic ease-in/out.
*
* @method Easing.Quintic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Number;
};
/**
* Quartic ease-out.
* Sinusoidal easing.
*
* @method Easing.Quartic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
* @class Easing.Sinusoidal
*/
Out: Function;
Sinusoidal: {
/**
* Sinusoidal ease-in.
*
* @method Easing.Sinusoidal#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Sinusoidal ease-out.
*
* @method Easing.Sinusoidal#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Sinusoidal ease-in/out.
*
* @method Easing.Sinusoidal#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Quartic ease-in/out.
* Exponential easing.
*
* @method Easing.Quartic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
* @class Easing.Exponential
*/
InOut: Function;
};
Exponential: {
/**
* Exponential ease-in.
*
* @method Easing.Exponential#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Quintic easing.
*
* @class Easing.Quintic
*/
Quintic: {
/**
* Quintic ease-in.
*
* @method Easing.Quintic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Exponential ease-out.
*
* @method Easing.Exponential#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Exponential ease-in/out.
*
* @method Easing.Exponential#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Quintic ease-out.
* Circular easing.
*
* @method Easing.Quintic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
* @class Easing.Circular
*/
Out: Function;
Circular: {
/**
* Circular ease-in.
*
* @method Easing.Circular#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Circular ease-out.
*
* @method Easing.Circular#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Circular ease-in/out.
*
* @method Easing.Circular#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Quintic ease-in/out.
* Elastic easing.
*
* @method Easing.Quintic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
* @class Easing.Elastic
*/
InOut: Number;
};
Elastic: {
/**
* Elastic ease-in.
*
* @method Easing.Elastic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Sinusoidal easing.
*
* @class Easing.Sinusoidal
*/
Sinusoidal: {
/**
* Sinusoidal ease-in.
*
* @method Easing.Sinusoidal#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Elastic ease-out.
*
* @method Easing.Elastic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Elastic ease-in/out.
*
* @method Easing.Elastic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Sinusoidal ease-out.
* Back easing.
*
* @method Easing.Sinusoidal#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
* @class Easing.Back
*/
Out: Function;
Back: {
/**
* Back ease-in.
*
* @method Easing.Back#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Back ease-out.
*
* @method Easing.Back#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Back ease-in/out.
*
* @method Easing.Back#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Sinusoidal ease-in/out.
* Bounce easing.
*
* @method Easing.Sinusoidal#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
* @class Easing.Bounce
*/
InOut: Function;
};
Bounce: {
/**
* Bounce ease-in.
*
* @method Easing.Bounce#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Exponential easing.
*
* @class Easing.Exponential
*/
Exponential: {
/**
* Exponential ease-in.
*
* @method Easing.Exponential#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Bounce ease-out.
*
* @method Easing.Bounce#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Exponential ease-out.
*
* @method Easing.Exponential#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Exponential ease-in/out.
*
* @method Easing.Exponential#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Circular easing.
*
* @class Easing.Circular
*/
Circular: {
/**
* Circular ease-in.
*
* @method Easing.Circular#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Circular ease-out.
*
* @method Easing.Circular#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Circular ease-in/out.
*
* @method Easing.Circular#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Elastic easing.
*
* @class Easing.Elastic
*/
Elastic: {
/**
* Elastic ease-in.
*
* @method Easing.Elastic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Elastic ease-out.
*
* @method Easing.Elastic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Elastic ease-in/out.
*
* @method Easing.Elastic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Back easing.
*
* @class Easing.Back
*/
Back: {
/**
* Back ease-in.
*
* @method Easing.Back#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Back ease-out.
*
* @method Easing.Back#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Back ease-in/out.
*
* @method Easing.Back#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Bounce easing.
*
* @class Easing.Bounce
*/
Bounce: {
/**
* Bounce ease-in.
*
* @method Easing.Bounce#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Bounce ease-out.
*
* @method Easing.Bounce#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Bounce ease-in/out.
*
* @method Easing.Bounce#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Bounce ease-in/out.
*
* @method Easing.Bounce#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
}
}
export class Between {
declare class Between {
/**
* Creates a new Between instance
* @param from start
@ -421,4 +423,6 @@ declare module "between.js" {
*/
isPaused: boolean;
}
export = Between;
}

View file

@ -1,15 +1,27 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import * as Between from "between.js";
import {
createSlice,
PayloadAction,
Store,
Dispatch,
Action,
Middleware
} from "@reduxjs/toolkit";
import Between from "between.js";
import { PlanItem } from "../showplanner/state";
import Keys from "keymaster";
import { Track, MYRADIO_NON_API_BASE } from "../api";
import { AppThunk } from "../store";
import { RootState } from "../rootReducer";
console.log(Between);
const audioContext = new AudioContext();
const playerSources: MediaElementAudioSourceNode[] = [];
const playerGains: GainNode[] = [];
const playerGainTweens: Between.Between[] = [];
const playerGainTweens: Array<{
target: VolumePresetEnum;
tweens: Between[];
}> = [];
// TODO
// const destination = audioContext.createWebcastSource(4096, 2);
const destination = audioContext.createDynamicsCompressor();
@ -23,6 +35,7 @@ interface PlayerState {
loading: boolean;
state: PlayerStateEnum;
volume: number;
gain: number;
}
interface MixerState {
@ -37,19 +50,22 @@ const mixerState = createSlice({
loadedItem: null,
loading: false,
state: "stopped",
volume: 1
volume: 1,
gain: 1
},
{
loadedItem: null,
loading: false,
state: "stopped",
volume: 1
volume: 1,
gain: 1
},
{
loadedItem: null,
loading: false,
state: "stopped",
volume: 1
volume: 1,
gain: 1
}
]
} as MixerState,
@ -73,9 +89,21 @@ const mixerState = createSlice({
},
setPlayerVolume(
state,
action: PayloadAction<{ player: number; volume: number }>
action: PayloadAction<{
player: number;
volume: number;
}>
) {
state.players[action.payload.player].volume = action.payload.volume;
},
setPlayerGain(
state,
action: PayloadAction<{
player: number;
gain: number;
}>
) {
state.players[action.payload.player].gain = action.payload.gain;
}
}
});
@ -114,9 +142,14 @@ export const load = (player: number, item: PlanItem | Track): AppThunk => (
);
}
el.oncanplay = () => {
console.log("can play");
};
el.oncanplaythrough = () => {
console.log("can play through");
dispatch(mixerState.actions.itemLoadComplete({ player }));
};
el.load();
console.log("loading");
const sauce = audioContext.createMediaElementSource(el);
const gain = audioContext.createGain();
gain.gain.value = getState().mixer.players[player].volume;
@ -179,29 +212,158 @@ export const stop = (player: number): AppThunk => dispatch => {
}
};
const FADE_TIME_SECONDS = 1;
export const setVolume = (
player: number,
level: VolumePresetEnum
): AppThunk => (dispatch, getState) => {
let volume: number;
let uiLevel: number;
switch (level) {
case "off":
volume = 0;
uiLevel = 0;
break;
case "bed":
volume = 0.25;
uiLevel = 0.5;
break;
case "full":
volume = 1;
volume = uiLevel = 1;
break;
}
const currentLevel = getState().mixer.players[player].volume;
playerGainTweens[player] = new (Between as any)(currentLevel, volume)
.on("update", (value: number) => {
dispatch(mixerState.actions.setPlayerVolume({ player, volume }));
if (playerGains[player]) {
playerGains[player].gain.value = value;
}
// Right, okay, big fun is happen.
// To make the fade sound natural, we need to ramp it exponentially.
// To make the UI look sensible, we need to ramp it linearly.
// Time for cheating!
if (typeof playerGainTweens[player] !== "undefined") {
// We've interrupted a previous fade.
// If we've just hit the button/key to go to the same value as that fade,
// stop it and immediately cut to the target value.
// Otherwise, stop id and start a new fade.
playerGainTweens[player].tweens.forEach(tween => tween.pause());
if (playerGainTweens[player].target === level) {
delete playerGainTweens[player];
dispatch(
mixerState.actions.setPlayerVolume({ player, volume: uiLevel })
);
dispatch(
mixerState.actions.setPlayerGain({ player, gain: volume })
);
return;
}
}
const state = getState().mixer.players[player];
const currentLevel = state.volume;
const currentGain = state.gain;
const volumeTween = new Between(currentLevel, uiLevel)
.time(FADE_TIME_SECONDS * 1000)
.on("update", (val: number) => {
console.log(val);
dispatch(
mixerState.actions.setPlayerVolume({ player, volume: val })
);
});
const gainTween = new Between(currentGain, volume)
.time(FADE_TIME_SECONDS * 1000)
.easing((Between as any).Easing.Quadratic.InOut)
.on("update", (val: number) => {
console.log(val);
dispatch(mixerState.actions.setPlayerGain({ player, gain: val }));
})
.time(1000);
.on("complete", () => {
// clean up when done
delete playerGainTweens[player];
});
playerGainTweens[player] = {
target: level,
tweens: [volumeTween, gainTween]
};
};
export const mixerMiddleware: Middleware<
{},
RootState,
Dispatch<any>
> = store => next => action => {
const oldState = store.getState().mixer;
const result = next(action);
const newState = store.getState().mixer;
newState.players.forEach((state, index) => {
if (typeof playerGains[index] !== "undefined") {
if (oldState.players[index].gain !== newState.players[index].gain) {
playerGains[index].gain.value = state.gain;
}
}
});
return result;
};
export const mixerKeyboardShortcutsMiddleware: Middleware<
{},
RootState,
Dispatch<any>
> = store => {
Keys("q", () => {
store.dispatch(play(0));
});
Keys("w", () => {
store.dispatch(pause(0));
});
Keys("e", () => {
store.dispatch(stop(0));
});
Keys("r", () => {
store.dispatch(play(1));
});
Keys("t", () => {
store.dispatch(pause(1));
});
Keys("y", () => {
store.dispatch(stop(1));
});
Keys("u", () => {
store.dispatch(play(2));
});
Keys("i", () => {
store.dispatch(pause(2));
});
Keys("o", () => {
store.dispatch(stop(2));
});
Keys("a", () => {
store.dispatch(setVolume(0, "off"));
});
Keys("s", () => {
store.dispatch(setVolume(0, "bed"));
});
Keys("d", () => {
store.dispatch(setVolume(0, "full"));
});
Keys("f", () => {
store.dispatch(setVolume(1, "off"));
});
Keys("g", () => {
store.dispatch(setVolume(1, "bed"));
});
Keys("h", () => {
store.dispatch(setVolume(1, "full"));
});
Keys("j", () => {
store.dispatch(setVolume(2, "off"));
});
Keys("k", () => {
store.dispatch(setVolume(2, "bed"));
});
Keys("l", () => {
store.dispatch(setVolume(2, "full"));
});
return next => action => next(action);
};

View file

@ -72,7 +72,7 @@ function Item({
ref={provided.innerRef}
key={id}
className={`sp-track ${
column > 0 &&
column >= 0 &&
playerState.loadedItem !== null &&
itemId(playerState.loadedItem) === id
? "sp-track-active"
@ -98,6 +98,8 @@ function Item({
);
}
const USE_REAL_GAIN_VALUE = false;
function Player({ id }: { id: number }) {
const playerState = useSelector(
(state: RootState) => state.mixer.players[id]
@ -108,7 +110,7 @@ function Player({ id }: { id: number }) {
<div className="player">
{playerState.loadedItem == null && <div>No Media Selected</div>}
{playerState.loadedItem !== null && playerState.loading == false && (
<div>{playerState.loadedItem.title}</div>
<div style={{ height: "1.5em", overflowY: "hidden" }}>{playerState.loadedItem.title}</div>
)}
{playerState.loading && <b>LOADING</b>}
<div className="mediaButtons">
@ -134,7 +136,7 @@ function Player({ id }: { id: number }) {
<div className="sp-mixer-buttons">
<div
className="sp-mixer-buttons-backdrop"
style={{ width: playerState.volume * 100 + "%" }}
style={{ width: (USE_REAL_GAIN_VALUE ? playerState.gain : playerState.volume) * 100 + "%" }}
></div>
<button onClick={() => dispatch(PlayerState.setVolume(id, "off"))}>
Off
@ -268,7 +270,10 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
}, [timeslotId]);
async function onDragEnd(result: DropResult, provider: ResponderProvided) {
if (result.destination!.droppableId[0] === "$") {
if (!result.destination) {
return;
}
if (result.destination.droppableId[0] === "$") {
// pseudo-channel
return;
}
@ -278,8 +283,8 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
const newItem: TimeslotItem = {
type: "central",
timeslotitemid: "CHANGEME" + Math.random(),
channel: parseInt(result.destination!.droppableId, 10),
weight: result.destination!.index,
channel: parseInt(result.destination.droppableId, 10),
weight: result.destination.index,
...data
};
dispatch(addItem(timeslotId, newItem));
@ -287,8 +292,8 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
// this is a normal move (ghosts aren't draggable)
dispatch(
moveItem(timeslotId, result.draggableId, [
parseInt(result.destination!.droppableId, 10),
result.destination!.index
parseInt(result.destination.droppableId, 10),
result.destination.index
])
);
}

View file

@ -1,9 +1,11 @@
import rootReducer, { RootState } from "./rootReducer";
import { configureStore, Action } from "@reduxjs/toolkit";
import { configureStore, Action, getDefaultMiddleware } from "@reduxjs/toolkit";
import { ThunkAction } from "redux-thunk";
import { mixerMiddleware, mixerKeyboardShortcutsMiddleware } from "./mixer/state";
const store = configureStore({
reducer: rootReducer
reducer: rootReducer,
middleware: [...getDefaultMiddleware(), mixerMiddleware, mixerKeyboardShortcutsMiddleware]
});
if (process.env.NODE_ENV === "development" && module.hot) {

View file

@ -1320,6 +1320,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
"@types/keymaster@^1.6.28":
version "1.6.28"
resolved "https://registry.yarnpkg.com/@types/keymaster/-/keymaster-1.6.28.tgz#093fc6fe49deff4ee17d36935a49230edb1c935f"
integrity sha1-CT/G/kne/07hfTaTWkkjDtsck18=
"@types/lodash@^4.14.149":
version "4.14.149"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
@ -5938,6 +5943,11 @@ jsx-ast-utils@^2.1.0, jsx-ast-utils@^2.2.1:
array-includes "^3.0.3"
object.assign "^4.1.0"
keymaster@^1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/keymaster/-/keymaster-1.6.2.tgz#e1ae54d0ea9488f9f60b66b668f02e9a1946c6eb"
integrity sha1-4a5U0OqUiPn2C2a2aPAumhlGxus=
killable@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"