Compare commits

..

28 Commits

Author SHA1 Message Date
cv bf8170a947 version bump 2021-11-22 22:53:39 +01:00
Carlos Valente eb2abb7026 fix: bug in osc goto (#44) 2021-11-22 22:48:55 +01:00
Carlos Valente f3320efc5c Fix/ios (#43)
* remove tiny keys

* fix: replace library with code

* cleanup

* add lodash throttle

add dependency to handle event throttling

* Revert "add lodash throttle"

This reverts commit abcbd3556b.
2021-11-17 22:50:54 +01:00
cv 17d5c84133 version bump 2021-11-16 21:02:37 +01:00
Carlos Valente 09a5aca25a Chore/qol (#41)
* style: prevent timer from resizing container

* style: review half and third screens

* style: prevent delay time from resizing blocks

* style: show now / next titles when container is collapsed

* docs: link to documentation in readme

* Update README.md

* Update README.md

* Update README.md

* style: new image assets

* cleanup

* style: prevent long titles from overflowing containers

* fix: send titles on change

fixed issue that prevented the next title from being broadcast on change
2021-11-16 20:54:36 +01:00
Carlos Valente 885d965db1 Fix/roll (#39)
* roll locks to first

in case there are events with overlapping time, roll will play the one that started first

* sanitize data entry

fixes issue where input field would not return seconds if not modified

* update chakra

* fix time input in chrome

fix issue in chrome where value of input does not contain seconds
2021-11-14 21:13:47 +01:00
Carlos Valente 26cb8a5bfc Feat/seconds (#38)
* change directory structure

move test file to __tests__ at the module directory

* test function

Add more tests to ensure the function remains safe

* fix coverage filter

* change directory structure

move to utilities folder

* refract formatDisplay with tst

* test millis

* test millis to seconds

* refract millis functions

* refractor timeStringToMillis

* create utility consts

* style component

* fixed bug with hidezero parameter

* fix issue with cancelling input edit

* style tweak
2021-11-13 22:39:57 +01:00
cv 8520d08927 version bump 2021-11-09 22:33:05 +01:00
Carlos Valente 8d8bbef546 small QOL improvements (#37)
* feat: selected event if is first

* style: fix wrap

- remove wrap tag (broken in firefox)

* feat: add more text to notes

this is a quick fix and does not replace the real deal.
using a text input makes it difficult to edit, this should be revised with a textarea on note field

* upgrade electron

* Stage timer in /sm has placeholder when not running

* fix style issue where clock would move depending on whether there are events selected

* ensure ontime logo and text have a minimum readable size

* fix scrollbar styling

* clean debugging logs

* link to latest release

using gitreleases.dev service for this.
this is a temporary fix until we have a release (in opposition to pre-release) so we can use githubs latest link

* scale gracefully

* fixed error with naming of maxChar
2021-11-09 22:31:55 +01:00
Carlos Valente 207612e3b8 Feat/osc feedback (#35)
* Add roll function call to OSC

* send finished message

* request presenter

* handling badly formatted messages

* loadeventbyindex

add step so that we can pause the titles when loading a new event via an API call (eg. OSC)

* broadcast title data

* handle empty title fields

* send finished message in roll mode

* calling stop is same as calling unload
2021-11-08 22:15:18 +01:00
Carlos Valente 98e5cddad5 Fix/roll titles (#33)
Roll is now calculated with awareness of public and backstage titles
Style improvements on providing feedback on roll mode
2021-11-07 13:13:06 +01:00
Carlos Valente f6edb88944 Fixed issues with style on small windows (#29) 2021-11-04 21:12:53 +01:00
cv 3d2a79f92a docs
- bump minor version
- review issues list
2021-10-31 22:25:34 +01:00
Carlos Valente f73d8ae8f4 Merge pull request #21 from cpvalente/fix/roll
Fix/roll
2021-10-31 22:16:56 +01:00
Carlos Valente 1138b4dd98 Merge pull request #20 from cpvalente/fix/DST
fix: time from midnight
2021-10-31 22:16:15 +01:00
cv ad89a780be fix: time from midnight
fix issue where time from midnight was wrong on days where DST changes
2021-10-31 22:04:19 +01:00
cv 9bc2923586 fix: handle midnight
fixed issue where roll mode wouldnt recognise a end time lower than start time (go through midnight)
2021-10-31 13:38:28 +01:00
cv 93580554bd fix: roll state
fix issue when the call of roll() was resetting the state after trying to load titles
2021-10-31 13:26:32 +01:00
cv 03ef3e58ed style: button disable
disable playback buttons while rolling (interface only)
2021-10-31 13:12:22 +01:00
cv 4d2769dee4 fix: recalculate timers
- recalculate timers in same update call if necessary
- use new variable to keep track of upcoming rolling events without recalculating titles
2021-10-31 12:53:46 +01:00
cv 0bb728d76c refract: broadcast state
- ability to broadcast state without recalculating timer
2021-10-31 11:20:35 +01:00
cv 5a68d292e1 fix: reset timers
reset timers as needed with roll mode
2021-10-31 11:16:32 +01:00
Carlos Valente 418f8018fe Merge pull request #19 from cpvalente/refract/update
Refract/update
2021-10-29 23:17:04 +02:00
cv 89d3bc967c revert upgrade electron
- GL issue with electron version
- re build yarn lock
2021-10-29 23:16:28 +02:00
cv 144ce1c046 upgrade electron-builder 2021-10-29 22:52:00 +02:00
cv 513e50a998 upgrade electron 2021-10-29 22:48:33 +02:00
cv f0ecb85c29 upgrade react 2021-10-29 22:32:20 +02:00
Carlos Valente c0e0631cd2 Update README.md
update download link
2021-10-29 14:07:00 +02:00
50 changed files with 3483 additions and 1940 deletions
View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

+1 -1
View File
@@ -6,7 +6,7 @@ node_modules/
.pnp.js
# testing
/coverage
coverage/
# production
build/
+10 -7
View File
@@ -1,16 +1,17 @@
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-green.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-green.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Documentation in Gitbook](https://badges.aleen42.com/src/gitbook_2.svg)](https://cpvalente.gitbook.io/ontime/)
Download the latest releases here
- [Windows](https://github.com/cpvalente/ontime/releases/download/v0.10-beta/ontime-win64.exe)
- [Windows](https://gitreleases.dev/gh/cpvalente/ontime/latest/ontime-win64.exe)
# Ontime
Ontime is an application for managing event rundowns and running stage timers.
It allows a center application to be able to distribute event information in the local network. This minimises needs for using Media Server outputs or expensive video distribution while allowing easy integration in workflows including OBS and d3.
![App Window](https://github.com/cpvalente/ontime/blob/master/01_app.png)
![App Window](https://github.com/cpvalente/ontime/blob/master/.github/01_app.png)
![Views](https://github.com/cpvalente/ontime/blob/master/02_screentypes.png)
![Views](https://github.com/cpvalente/ontime/blob/master/.github/02_screentypes.png)
## Using Ontime
Once installed and running, ontime starts a background server that is the heart of all processes.
@@ -43,7 +44,7 @@ More documentation available [here](https://cpvalente.gitbook.io/ontime/)
- [x] Ability to differentiate between backstage and public data
- [x] Manage delays workflow
- [x] OSC Control and Feedback
- [x] Roll mode: run independently from system clock
- [x] Roll mode: run independently using the system clock
## Unopinionated
We are not interested in forcing workflows and have made ontime so it is flexible to whichever way you would like to work.
@@ -69,15 +70,17 @@ Almost reaching a feature set that we can call v1. Before that:
- [ ] URL Aliases (define configurable aliases to ease onsite setup)
- [ ] Logging view
- [ ] Reach Schedule: way to speedup timer to meet a deadline
- [ ] Excel Import
- [ ] vMix integration
### Issues
The app is still in pre-release and there are a few issues, mainly concerning style.
This will be receiving attention as we near v1 release
#### Style
- [ ] App appears visually broken: Please ensure that windows settings have no display zoom (it is 125% by default)
- [ ] app is needs improvement on handling zoomed interfaces
- [ ] app needs improvement on handling zoomed interfaces
- [ ] Very long titles might cause interface to shift
- [ ] App sometimes hides itself when window is not visible. Ontime is always running in the background, find it in the tray or press Alt+1 to bring it to the foreground
# Help
Help is underway! ... and can be viewed [here](https://cpvalente.gitbook.io/ontime/)
+2 -3
View File
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@chakra-ui/react": "^1.6.10",
"@chakra-ui/react": "^1.7.1",
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@testing-library/jest-dom": "^5.11.4",
@@ -12,7 +12,7 @@
"axios": "^0.21.1",
"framer-motion": "^4.1.6",
"jotai": "^0.16.5",
"react": "^17.0.1",
"react": "17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.1",
"react-fast-compare": "^3.2.0",
@@ -23,7 +23,6 @@
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"socket.io-client": "^4.3.2",
"tinykeys": "^1.1.2",
"typeface-open-sans": "^1.1.13",
"web-vitals": "^1.0.1"
},
+24
View File
@@ -1,6 +1,8 @@
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
scrollbar-color: rgba(255, 255, 255, 0.15) rgba(255, 255, 255, 0.01);
scrollbar-width: thin;
}
body,
@@ -18,3 +20,25 @@ html,
option {
color: black;
}
/* width */
::-webkit-scrollbar {
width: 0.5em;
}
/* Track */
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.01);
border-radius: 4px;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.15);
border-radius: 4px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.35);
}
+17 -9
View File
@@ -1,10 +1,9 @@
import { lazy, Suspense, useEffect } from 'react';
import { lazy, Suspense, useCallback, useEffect } from 'react';
import { Route, Switch } from 'react-router-dom';
import './App.css';
import { QueryClient, QueryClientProvider } from 'react-query';
import SocketProvider from 'app/context/socketContext';
import withSocket from 'features/viewers/ViewWrapper';
import tinykeys from 'tinykeys';
const Editor = lazy(() => import('features/editors/Editor'));
const PresenterView = lazy(() =>
@@ -37,21 +36,30 @@ const SLowerThird = withSocket(Lower);
const SPip = withSocket(Pip);
function App() {
useEffect(() => {
let unsubscribe = tinykeys(window, {
'Alt+t': () => {
// Handle keyboard shortcuts
const handleKeyPress = useCallback((e) => {
// check if the alt key is pressed
if (e.altKey) {
if (e.key === 't' || e.key === 'T') {
// if we are in electron
if (window.process?.type === undefined) return;
if (window.process.type === 'renderer') {
// ask to see debug
window.ipcRenderer.send('set-window', 'show-dev');
}
},
});
}
}
}, []);
useEffect(() => {
// attach the event listener
document.addEventListener('keydown', handleKeyPress);
// remove the event listener
return () => {
unsubscribe();
document.removeEventListener('keydown', handleKeyPress);
};
});
}, [handleKeyPress]);
return (
<SocketProvider>
@@ -6,8 +6,8 @@ export default function UnloadIconBtn(props) {
return (
<IconButton
icon={<FiXOctagon />}
colorScheme='whiteAlpha'
backgroundColor='#ffffff05'
colorScheme='red'
backgroundColor='#ff000022'
variant='outline'
onClick={clickhandler}
width={90}
@@ -1,13 +1,14 @@
import { memo } from 'react';
import { formatDisplay } from 'common/dateConfig';
import { formatDisplay } from 'common/utils/dateConfig';
import styles from './Countdown.module.css';
const Countdown = ({ time, small, negative, hideZeroHours }) => {
let display = '-- : -- : --';
// prepare display string
if (time != null && !isNaN(time))
display = formatDisplay(Math.abs(time), hideZeroHours);
const display =
time != null && !isNaN(time)
? formatDisplay(time, hideZeroHours)
: '-- : -- : --';
const colour = negative ? '#ff7597' : '#fffffa';
return (
@@ -1,6 +1,6 @@
import EditableTimer from 'common/input/EditableTimer';
import { showWarningToast } from 'common/helpers/toastManager';
import { stringFromMillis } from 'common/dateConfig';
import { stringFromMillis } from 'common/utils/dateConfig';
const label = {
fontSize: '0.75em',
+16 -11
View File
@@ -1,8 +1,7 @@
import { Link, Redirect } from 'react-router-dom';
import { Image } from '@chakra-ui/react';
import { AnimatePresence, motion } from 'framer-motion';
import { useState, useEffect } from 'react';
import tinykeys from 'tinykeys';
import { useState, useEffect, useCallback } from 'react';
import navlogo from 'assets/images/logos/LOGO-72.png';
import style from './NavLogo.module.css';
@@ -14,17 +13,23 @@ export default function NavLogo() {
};
// Handle keyboard shortcuts
useEffect(() => {
let unsubscribe = tinykeys(window, {
Space: () => {
setShowNav((s) => !s);
},
});
return () => {
unsubscribe();
};
const handleKeyPress = useCallback((e) => {
// Space bar
if (e.keyCode === 32) {
setShowNav((s) => !s);
}
}, []);
useEffect(() => {
// attach the event listener
document.addEventListener('keydown', handleKeyPress);
// remove the event listener
return () => {
document.removeEventListener('keydown', handleKeyPress);
};
}, [handleKeyPress]);
return (
<motion.div
initial={{ opacity: 0.5 }}
@@ -4,12 +4,12 @@
display: flex;
flex-direction: column;
align-items: flex-end;
font-size: 1vw;
font-size: max(1vw, 16px);
z-index: 10;
}
.logo {
width: 3vw;
width: max(3vw, 32px);
opacity: 0.5;
}
@@ -1,4 +1,4 @@
import { stringFromMillis } from 'common/dateConfig';
import { stringFromMillis } from 'common/utils/dateConfig';
import style from './Paginator.module.css';
export default function TodayItem(props) {
const { selected, timeStart, timeEnd, title, backstageEvent } = props;
-49
View File
@@ -1,49 +0,0 @@
export const timeFormat = 'HH:mm';
export const timeFormatSeconds = 'HH:mm:ss';
// time string from miliseconds
export const stringFromMillis = (
ms,
showSeconds = true,
delim = ':',
ifNull = '...'
) => {
if (ms === null || isNaN(ms)) return ifNull;
const showWith0 = (value) => (value < 10 ? `0${value}` : value);
const hours = showWith0(Math.floor(((ms / (1000 * 60 * 60)) % 60) % 24));
const minutes = showWith0(Math.floor((ms / (1000 * 60)) % 60));
const seconds = showWith0(Math.floor((ms / 1000) % 60));
return showSeconds
? `${
parseInt(hours) ? `${hours}${delim}` : `00${delim}`
}${minutes}${delim}${seconds}`
: `${parseInt(hours) ? `${hours}` : '00'}${delim}${minutes}`;
};
// another go at simpler string formatting (counters)
export function formatDisplay(seconds, hideZero) {
const format = (val) => `0${Math.floor(val)}`.slice(-2);
const hours = seconds / 3600;
const minutes = (seconds % 3600) / 60;
if (hideZero && hours < 1)
return [minutes, seconds % 60].map(format).join(':');
else return [hours, minutes, seconds % 60].map(format).join(':');
}
// millis to seconds
export const millisToSeconds = (millis) => {
return Math.floor(millis / 1000);
};
// millis to minutes
export const millisToMinutes = (millis) => {
return Math.floor(millis / 60000);
};
// timeStringToMillis
export const timeStringToMillis = (string) => {
let time = string.split(':');
return time[0] * 3600000 + time[1] * 60000;
};
+2 -1
View File
@@ -5,6 +5,7 @@ import style from './EditableText.module.css';
export default function EditableText(props) {
const { label, defaultValue, placeholder, submitHandler, ...rest } = props;
const [text, setText] = useState(defaultValue || '');
const maxchar = props.maxchar || 40;
useEffect(() => {
if (defaultValue == null) setText('');
@@ -18,7 +19,7 @@ export default function EditableText(props) {
};
const handleChange = (val) => {
if (val.length < 40) setText(val);
if (val.length < maxchar) setText(val);
};
return (
+17 -13
View File
@@ -1,10 +1,6 @@
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
import { useEffect, useState } from 'react';
import {
timeFormat,
stringFromMillis,
timeStringToMillis,
} from '../dateConfig';
import { stringFromMillis, timeStringToMillis } from '../utils/dateConfig';
import { showErrorToast } from '../helpers/toastManager';
import style from './EditableTimer.module.css';
@@ -16,7 +12,7 @@ export default function EditableTimer(props) {
useEffect(() => {
if (time == null) return;
try {
setValue(stringFromMillis(time + delay, false));
setValue(stringFromMillis(time + delay));
} catch (error) {
showErrorToast('Error parsing date', error.text);
}
@@ -24,23 +20,25 @@ export default function EditableTimer(props) {
const validateValue = (value) => {
const success = handleSubmit(value);
if (success) setValue(value);
else setValue(stringFromMillis(time + delay, false));
else setValue(stringFromMillis(time + delay, true));
};
const handleSubmit = (value) => {
// Check if there is anything there
if (value === '') return false;
// ensure we have seconds
const val = value.split(':').length === 3 ? value : `${value}:00`;
// Time now and time submitedVal
const original = stringFromMillis(time + delay, false);
const original = stringFromMillis(time + delay, true);
// check if time is different from before
if (value === original) return false;
if (val === original) return false;
// convert to millis object
const millis = timeStringToMillis(value, timeFormat);
const millis = timeStringToMillis(val);
// validate with parent
if (!validate(name, millis)) return false;
@@ -55,12 +53,18 @@ export default function EditableTimer(props) {
<Editable
onChange={(v) => setValue(v)}
onSubmit={(v) => validateValue(v)}
onCancel={() => setValue(stringFromMillis(time + delay, true))}
value={value}
placeholder='--:--'
className={delay > 0 ? style.delayedEditable : style.editable}
>
<EditablePreview />
<EditableInput type='time' min='00:00' max='23:59' />
<EditableInput
type='time'
placeholder='--:--:--'
min='00:00:00'
max='23:59:59'
step='1'
/>
</Editable>
);
}
@@ -1,9 +1,10 @@
.editable,
.delayedEditable {
background-color: rgba(255, 255, 255, 0.05);
background-color: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 4px;
width: 5em;
width: 6.5em;
letter-spacing: 1px;
height: fit-content;
text-align: center;
@@ -0,0 +1,216 @@
import {
formatDisplay,
millisToMinutes,
millisToSeconds,
timeStringToMillis,
} from '../dateConfig';
describe('test string from formatDisplay function', () => {
it('test with null values', () => {
const t = { val: null, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with valid millis', () => {
const t = { val: 3600, result: '01:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with negative millis', () => {
const t = { val: -3600, result: '01:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with 0', () => {
const t = { val: 0, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with -0', () => {
const t = { val: -0, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with 86400 (24 hours)', () => {
const t = { val: 86400, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with 86401 (24 hours and 1 second)', () => {
const t = { val: 86401, result: '00:00:01' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with -86401 (-24 hours and 1 second)', () => {
const t = { val: -86401, result: '00:00:01' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
});
describe('test string from formatDisplay function with hidezero', () => {
it('test with null values', () => {
const t = { val: null, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with valid millis', () => {
const t = { val: 3600, result: '01:00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with negative millis', () => {
const t = { val: -3600, result: '01:00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with 0', () => {
const t = { val: 0, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with -0', () => {
const t = { val: -0, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with 86400 (24 hours)', () => {
const t = { val: 86400, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with 86401 (24 hours and 1 second)', () => {
const t = { val: 86401, result: '00:01' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with -86401 (-24 hours and 1 second)', () => {
const t = { val: -86401, result: '00:01' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
});
describe('test millisToSeconds function', () => {
it('test with null values', () => {
const t = { val: null, result: 0 };
expect(millisToSeconds(t.val, false)).toBe(t.result);
});
it('test with valid millis', () => {
const t = { val: 3600000, result: 3600 };
expect(millisToSeconds(t.val, false)).toBe(t.result);
});
it('test with negative millis', () => {
const t = { val: -3600000, result: -3600 };
expect(millisToSeconds(t.val, false)).toBe(t.result);
});
it('test with 0', () => {
const t = { val: 0, result: 0 };
expect(millisToSeconds(t.val, false)).toBe(t.result);
});
it('test with -0', () => {
const t = { val: -0, result: -0 };
expect(millisToSeconds(t.val, false)).toBe(t.result);
});
it('test with 86401000 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: 86401 };
expect(millisToSeconds(t.val, false)).toBe(t.result);
});
it('test with -86401000 (-24 hours and 1 second)', () => {
const t = { val: -86401000, result: -86401 };
expect(millisToSeconds(t.val, false)).toBe(t.result);
});
});
describe('test millisToMinutes function', () => {
it('test with null values', () => {
const t = { val: null, result: 0 };
expect(millisToMinutes(t.val, false)).toBe(t.result);
});
it('test with valid millis', () => {
const t = { val: 3600000, result: 60 };
expect(millisToMinutes(t.val, false)).toBe(t.result);
});
it('test with negative millis', () => {
const t = { val: -3600000, result: -60 };
expect(millisToMinutes(t.val, false)).toBe(t.result);
});
it('test with 0', () => {
const t = { val: 0, result: 0 };
expect(millisToMinutes(t.val, false)).toBe(t.result);
});
it('test with -0', () => {
const t = { val: -0, result: -0 };
expect(millisToMinutes(t.val, false)).toBe(t.result);
});
it('test with 86401000 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: 1440 };
expect(millisToMinutes(t.val, false)).toBe(t.result);
});
it('test with -86401000 (-24 hours and 1 second)', () => {
const t = { val: -86401000, result: -1440 };
expect(millisToMinutes(t.val, false)).toBe(t.result);
});
});
describe('test timeStringToMillis function', () => {
it('test with null', () => {
const t = { val: null, result: 0 };
expect(timeStringToMillis(t.val)).toBe(t.result);
});
it('test with 00:00:00', () => {
const t = { val: '00:00:00', result: 0 };
expect(timeStringToMillis(t.val)).toBe(t.result);
});
it('test with -00:00:00', () => {
const t = { val: '-00:00:00', result: 0 };
expect(timeStringToMillis(t.val)).toBe(t.result);
});
it('test with 00:00:01', () => {
const t = { val: '00:00:01', result: 1000 };
expect(timeStringToMillis(t.val)).toBe(t.result);
});
it('test with -00:00:01', () => {
const t = { val: '-00:00:01', result: 1000 };
expect(timeStringToMillis(t.val)).toBe(t.result);
});
it('test with 01:00:01', () => {
const t = { val: '01:00:01', result: 3601000 };
expect(timeStringToMillis(t.val)).toBe(t.result);
});
it('test with 24:00:01', () => {
const t = { val: '24:00:01', result: 86401000 };
expect(timeStringToMillis(t.val)).toBe(t.result);
});
it('test with 00:00:5', () => {
const t = { val: '00:00:5', result: 5000 };
expect(timeStringToMillis(t.val)).toBe(t.result);
});
it('test with 00:1:00', () => {
const t = { val: '00:1:00', result: 60000 };
expect(timeStringToMillis(t.val)).toBe(t.result);
});
it('test with 1:00:00', () => {
const t = { val: '1:00:00', result: 3600000 };
expect(timeStringToMillis(t.val)).toBe(t.result);
});
});
+97
View File
@@ -0,0 +1,97 @@
export const timeFormat = 'HH:mm';
export const timeFormatSeconds = 'HH:mm:ss';
const mts = 1000; // millis to seconds
const mtm = 1000 * 60; // millis to minutes
const mth = 1000 * 60 * 60; // millis to hours
const mtd = 1000 * 60 * 60 * 24; // millis to days
/**
* @description Converts milliseconds to string representing time
* @param {number} ms - time in milliseconds
* @param {boolean} showSeconds - wether to show the seconds
* @param {string} delim - character between HH MM SS
* @param {string} ifNull - what to return if value is null
* @returns {string} String representing time 00:12:02
*/
// This is shared and tested in backend in time.js
export const stringFromMillis = (
ms,
showSeconds = true,
delim = ':',
ifNull = '...'
) => {
if (ms === null || isNaN(ms)) return ifNull;
const isNegative = ms < 0 ? '-' : '';
const showWith0 = (value) => (value < 10 ? `0${value}` : value);
const hours = showWith0(Math.floor(((ms / mth) % 60) % 24));
const minutes = showWith0(Math.floor((ms / mtm) % 60));
const seconds = showWith0(Math.floor((ms / mts) % 60));
return showSeconds
? `${isNegative}${
parseInt(hours) ? `${hours}${delim}` : `00${delim}`
}${minutes}${delim}${seconds}`
: `${isNegative}${parseInt(hours) ? `${hours}` : '00'}${delim}${minutes}`;
};
/**
* another go at simpler string formatting (counters)
* @description Converts seconds to string representing time
* @param {number} seconds - time in seconds
* @param {boolean} hideZero - wether to show hours in case its 00
* @returns {string} String representing absolute time 00:12:02
*/
export function formatDisplay(seconds, hideZero) {
// add an extra 0 if necessary
const format = (val) => `0${Math.floor(val)}`.slice(-2);
const s = Math.abs(seconds);
const hours = Math.floor((s / 3600) % 24);
const minutes = Math.floor((s % 3600) / 60);
if (hideZero && hours < 1) return [minutes, s % 60].map(format).join(':');
else return [hours, minutes, s % 60].map(format).join(':');
}
/**
* @description Converts milliseconds to seconds
* @param {number} millis - time in seconds
* @param {boolean} hideZero - wether to show hours in case its 00
* @returns {number} Amount in seconds
*/
// millis to seconds
export const millisToSeconds = (millis) => {
return millis < 0 ? Math.ceil(millis / mts) : Math.floor(millis / mts);
};
/**
* @description Converts milliseconds to seconds
* @param {number} millis - time in seconds
* @param {boolean} hideZero - wether to show hours in case its 00
* @returns {number} Amount in seconds
*/
// millis to minutes
export const millisToMinutes = (millis) => {
return millis < 0 ? Math.ceil(millis / mtm) : Math.floor(millis / mtm);
};
/**
* @description Converts timestring to milliseconds
* @param {string} string - time string "23:00:12"
* @returns {number} Amount in milliseconds
*/
// timeStringToMillis
export const timeStringToMillis = (string) => {
if (typeof string !== 'string') return 0;
const time = string.split(':');
if (time.length === 2) return Math.abs(time[0]) * mth + time[1];
if (time.length === 3)
return Math.abs(time[0]) * mth + time[1] * mtm + time[2] * mts;
else return 0;
};
@@ -16,17 +16,18 @@ const areEqual = (prevProps, nextProps) => {
};
const Playback = ({ playback, selectedId, playbackControl }) => {
const isRolling = playback === 'roll';
return (
<div className={style.playbackContainer}>
<StartIconBtn
active={playback === 'start'}
clickhandler={() => playbackControl('start')}
disabled={!selectedId}
disabled={!selectedId || isRolling}
/>
<PauseIconBtn
active={playback === 'pause'}
clickhandler={() => playbackControl('pause')}
disabled={!selectedId}
disabled={!selectedId || isRolling}
/>
<RollIconBtn
active={playback === 'roll'}
@@ -38,6 +39,7 @@ const Playback = ({ playback, selectedId, playbackControl }) => {
};
const Transport = ({ playback, selectedId, playbackControl }) => {
const isRolling = playback === 'roll';
return (
<div className={style.playbackContainer}>
<PrevIconBtn
@@ -50,9 +52,12 @@ const Transport = ({ playback, selectedId, playbackControl }) => {
/>
<ReloadIconButton
clickhandler={() => playbackControl('reload')}
disabled={!selectedId}
disabled={!selectedId || isRolling}
/>
<UnloadIconBtn
clickhandler={() => playbackControl('unload')}
disabled={!selectedId && !isRolling}
/>
<UnloadIconBtn clickhandler={() => playbackControl('unload')} />
</div>
);
};
@@ -20,13 +20,15 @@
'ind clk clk btn'
'... sta fin btn';
grid-template-rows: 1fr auto;
grid-template-columns: 1.5em 2fr 2fr 22%;
grid-template-columns: 1.5em 2fr 2fr 6em;
gap: 5px;
justify-items: start;
}
.timer {
grid-area: clk;
white-space: nowrap;
max-width: 300px;
}
.indicators {
@@ -1,6 +1,6 @@
import style from './PlaybackControl.module.css';
import Countdown from 'common/components/countdown/Countdown';
import { stringFromMillis } from 'common/dateConfig';
import { stringFromMillis } from 'common/utils/dateConfig';
import { Tooltip } from '@chakra-ui/react';
import { Button } from '@chakra-ui/button';
import { memo } from 'react';
+50 -40
View File
@@ -4,23 +4,23 @@
height: 100%;
margin: auto;
color: #fffd;
padding: 2vh;
padding: max(16px, 2vh);
display: grid;
grid-template-rows: 38vh 1fr;
grid-template-columns: 40px 48em 27vw 1fr;
grid-template-rows: auto 1fr;
grid-template-columns: 40px 48em auto auto;
grid-template-areas:
'sett even play info'
'sett even mess info';
gap: 2vh;
gap: max(16px, 2vh);
}
/* 2/3 window, hide previews */
@media (max-width: 1250px) and (min-height: 700px) {
/* 2/3 window, hide info */
@media (max-width: 1450px) and (min-height: 700px) {
.mainContainer {
height: 100%;
grid-template-rows: 1fr 1fr;
grid-template-columns: 40px 48em 1fr 1fr;
grid-template-rows: auto 1fr;
grid-template-columns: 40px 48em auto;
/* grid-template-areas:
'even play sett'
'even mess sett'; */
@@ -31,19 +31,38 @@
}
}
/* 1/2 window, event list only */
@media (max-width: 1100px) {
.mainContainer {
height: 100%;
grid-template-rows: 1fr auto;
grid-template-columns: 40px 48em;
/* grid-template-areas:
'sett even '
'sett play '; */
}
.info,
.messages,
.playback {
visibility: hidden;
}
}
/* 1/3 window, show control only */
@media (max-width: 750px) and (min-height: 500px) {
@media (max-width: 850px) and (min-height: 500px) {
.mainContainer {
grid-template-rows: auto 1fr;
grid-template-columns: 100%;
column-gap: 0;
grid-template-areas:
'mess'
'play';
'play'
'mess';
}
.messages,
.playback {
min-width: 31em;
.playback,
.messages {
visibility: visible;
}
.editor,
@@ -54,13 +73,17 @@
}
/* 1/3 corner window, playback only */
@media (max-width: 900px) and (max-height: 500px) {
@media (max-width: 850px) and (max-height: 500px) {
.mainContainer {
grid-template-rows: 1fr;
grid-template-columns: 1fr;
grid-template-rows: 100%;
grid-template-columns: 100%;
grid-template-areas: 'play';
}
.playback {
visibility: visible;
}
.editor,
.messages,
.info,
@@ -70,7 +93,7 @@
}
h1 {
font-size: 2.5vh;
font-size: max(1.5em, 16px);
color: rgba(255, 255, 255, 0.63);
padding-bottom: 0.25em;
}
@@ -91,6 +114,8 @@ h1 {
.info {
grid-area: info;
min-width: 17em;
max-width: 32em;
}
.messages {
@@ -99,6 +124,13 @@ h1 {
.playback {
grid-area: play;
min-height: 320px;
max-height: 320px;
}
.messages,
.playback {
min-width: 31em;
}
.mainContainer > .settings {
@@ -129,25 +161,3 @@ h1 {
display: flex;
justify-content: flex-end;
}
/* width */
::-webkit-scrollbar {
width: 0.5em;
}
/* Track */
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.01);
border-radius: 4px;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.15);
border-radius: 4px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.35);
}
@@ -1,6 +1,6 @@
import { Draggable } from 'react-beautiful-dnd';
import { FiMoreVertical } from 'react-icons/fi';
import { millisToMinutes } from 'common/dateConfig';
import { millisToMinutes } from 'common/utils/dateConfig';
import ActionButtons from './ActionButtons';
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
import ApplyIconBtn from 'common/components/buttons/ApplyIconBtn';
@@ -8,7 +8,7 @@ import EditableText from 'common/input/EditableText';
import ActionButtons from './ActionButtons';
import PublicIconBtn from 'common/components/buttons/PublicIconBtn';
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
import { millisToMinutes } from 'common/dateConfig';
import { millisToMinutes } from 'common/utils/dateConfig';
import style from './EventBlock.module.css';
import { SelectCollapse, HandleCollapse } from 'app/context/collapseAtom';
import { useAtom } from 'jotai';
@@ -78,6 +78,7 @@ const ExpandedBlock = (props) => {
defaultValue={data.note}
placeholder='Add Note'
style={{ color: '#d69e2e' }}
maxchar={160}
submitHandler={(v) =>
actionHandler('update', { field: 'note', value: v })
}
@@ -162,7 +163,6 @@ export default function EventBlock(props) {
const delayValue = delay > 0 ? millisToMinutes(delay) : null;
const handleCollapse = (isCollapsed) => {
console.log('setting collapsed', isCollapsed);
setCollapsed({ [data.id]: isCollapsed });
};
@@ -30,6 +30,7 @@
grid-template-columns: 2em 2em auto auto 1fr auto 3.7em;
grid-template-areas: 'drag indi time time text more btns';
justify-content: center;
align-items: center;
}
.expanded {
@@ -59,6 +60,7 @@
align-self: flex-start;
font-size: 0.75em;
overflow: hidden;
min-height: 3em;
min-width: 0;
justify-content: center;
}
@@ -95,6 +97,7 @@
.time {
grid-area: time;
align-self: center;
}
.start {
@@ -113,7 +116,6 @@
grid-area: time;
display: flex;
flex-direction: column;
flex-wrap: wrap;
align-items: flex-start;
justify-items: flex-start;
@@ -137,7 +139,6 @@
.titleContainer {
grid-area: text;
height: 100%;
background-color: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.05);
@@ -149,6 +150,13 @@
overflow: hidden;
}
.collapsed .titleContasiner {
height: fit-content;
}
.expanded .titleContainer {
height: 100%;
}
.oscLabel {
color: #4bffabcc;
font-size: 0.8em;
@@ -166,6 +174,7 @@
color: #fff;
grid-area: more;
margin-top: 0.2em;
align-self: baseline;
}
.moreExpanded {
+45 -29
View File
@@ -1,7 +1,6 @@
import style from './List.module.css';
import { createRef, useEffect, useMemo, useState } from 'react';
import { createRef, useCallback, useEffect, useMemo, useState } from 'react';
import { useSocket } from 'app/context/socketContext';
import tinykeys from 'tinykeys';
import Empty from 'common/state/Empty';
import EventListItem from './EventListItem';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
@@ -19,37 +18,54 @@ export default function EventList(props) {
const cursorRef = createRef();
// Handle keyboard shortcuts
const handleKeyPress = useCallback(
(e) => {
// Check if the alt key is pressed
if (e.altKey) {
// Arrow down
if (e.keyCode === 40) {
if (cursor == null) setCursor(0);
else if (cursor < events.length - 1) setCursor(cursor + 1);
}
// Arrow up
if (e.keyCode === 38) {
if (cursor == null) setCursor(0);
else if (cursor > 0) setCursor(cursor - 1);
}
// E
if (e.key === 'e' || e.key === 'E') {
e.preventDefault();
if (cursor == null) return;
eventsHandler('add', { type: 'event', order: cursor + 1 });
}
// D
if (e.key === 'd' || e.key === 'D') {
e.preventDefault();
if (cursor == null) return;
eventsHandler('add', { type: 'delay', order: cursor + 1 });
}
// B
if (e.key === 'b' || e.key === 'B') {
e.preventDefault();
if (cursor == null) return;
eventsHandler('add', { type: 'block', order: cursor + 1 });
}
}
},
[cursor, events, eventsHandler]
);
useEffect(() => {
let unsubscribe = tinykeys(window, {
'Alt+ArrowDown': () => {
if (cursor == null) setCursor(0);
else if (cursor < events.length - 1) setCursor(cursor + 1);
},
'Alt+ArrowUp': () => {
if (cursor == null) setCursor(0);
else if (cursor > 0) setCursor(cursor - 1);
},
'Alt+KeyE': (event) => {
event.preventDefault();
if (cursor == null) return;
eventsHandler('add', { type: 'event', order: cursor + 1 });
},
'Alt+KeyD': (event) => {
event.preventDefault();
if (cursor == null) return;
eventsHandler('add', { type: 'delay', order: cursor + 1 });
},
'Alt+KeyB': (event) => {
event.preventDefault();
if (cursor == null) return;
eventsHandler('add', { type: 'block', order: cursor + 1 });
},
});
// attach the event listener
document.addEventListener('keydown', handleKeyPress);
if (cursor > events.length - 1) setCursor(events.length - 1);
// remove the event listener
return () => {
unsubscribe();
document.removeEventListener('keydown', handleKeyPress);
};
}, [cursor, events, eventsHandler]);
}, [handleKeyPress, cursor, events]);
// handle incoming messages
useEffect(() => {
+9 -4
View File
@@ -18,10 +18,9 @@ export default function Info() {
noteNext: '',
});
const [selected, setSelected] = useState('No events');
const [playback, setPlayback] = useState(null);
const logData = [];
console.log(`titles`, titles);
// handle incoming messages
useEffect(() => {
if (socket == null) return;
@@ -34,6 +33,11 @@ export default function Info() {
setTitles(data);
});
// Handle playstate
socket.on('playstate', (data) => {
setPlayback(data);
});
// Ask for selection data
socket.emit('get-selected');
@@ -53,6 +57,7 @@ export default function Info() {
return () => {
socket.off('titles');
socket.off('selected');
socket.off('playstate');
};
}, [socket]);
@@ -80,8 +85,8 @@ export default function Info() {
</div>
{/* <InfoLogger logData={logData} /> */}
<InfoNif />
<InfoTitle title={'Now'} data={titlesNow} />
<InfoTitle title={'Next'} data={titlesNext} />
<InfoTitle title={'Now'} data={titlesNow} roll={playback === 'roll'} />
<InfoTitle title={'Next'} data={titlesNext} roll={playback === 'roll'} />
</>
);
}
+19 -1
View File
@@ -15,7 +15,8 @@
justify-content: space-between;
}
.header {
.header,
.headerRoll {
padding: 0;
margin: 0;
font-size: 0.9em;
@@ -24,6 +25,23 @@
justify-content: space-between;
}
.header {
color: #ccc;
}
.headerRoll {
color: #2b6cb0;
}
.collapsedTitle {
font-size: inherit;
padding-left: 1em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.labelContainer {
white-space: nowrap;
overflow: hidden;
+5 -2
View File
@@ -6,7 +6,7 @@ import style from './Info.module.css';
export default function InfoTitle(props) {
const [collapsed, setCollapsed] = useState(false);
const { title, data } = props;
const { title, data, roll } = props;
const noTitl = data.title == null || data.title === '';
const noPres = data.presenter == null || data.presenter === '';
@@ -15,8 +15,11 @@ export default function InfoTitle(props) {
return (
<div className={style.container}>
<div className={style.header}>
<div className={roll ? style.headerRoll : style.header}>
{title}
{collapsed && (
<span className={style.collapsedTitle}>{data.title}</span>
)}
<Icon
className={collapsed ? style.moreCollapsed : style.moreExpanded}
as={FiChevronUp}
+1 -1
View File
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { fetchAllEvents } from 'app/api/eventsApi';
import { fetchEvent } from 'app/api/eventApi';
import { useSocket } from 'app/context/socketContext';
import { stringFromMillis } from 'common/dateConfig';
import { stringFromMillis } from 'common/utils/dateConfig';
import { useFetch } from 'app/hooks/useFetch';
import { EVENTS_TABLE, EVENT_TABLE } from 'app/api/apiConstants';
@@ -1,5 +1,5 @@
import QRCode from 'react-qr-code';
import { formatDisplay } from 'common/dateConfig';
import { formatDisplay } from 'common/utils/dateConfig';
import style from './StageManager.module.css';
import Paginator from 'common/components/views/Paginator';
import NavLogo from 'common/components/nav/NavLogo';
@@ -40,9 +40,16 @@ export default function StageManager(props) {
}, [backstageEvents]);
// Format messages
const showPubl = publ.text !== '' && publ.visible;
let stageTimer = formatDisplay(Math.abs(time.running), true);
if (time.running < 0) stageTimer = `-${stageTimer}`;
let stageTimer;
if (time.running === null) {
stageTimer = '- - : - -';
} else {
stageTimer = formatDisplay(Math.abs(time.running), true);
if (time.running < 0) stageTimer = `-${stageTimer}`;
}
// motion
const titleVariants = {
@@ -10,7 +10,7 @@
color: #fffd;
display: grid;
grid-template-columns: 1fr 1fr 5vw 1fr 1fr;
grid-template-rows: auto 1fr auto auto;
grid-template-rows: auto 1fr auto minmax(25vh, auto);
grid-template-areas:
' clck .... .... .... ....'
' timr timr timr timr timr'
@@ -46,6 +46,7 @@
background-color: rgba(255, 255, 255, 0.05);
padding: 1vh 2vw;
border-radius: 1vw;
max-width: 100%;
}
.nowContainer {
@@ -4,7 +4,7 @@ import Paginator from 'common/components/views/Paginator';
import NavLogo from 'common/components/nav/NavLogo';
import { AnimatePresence, motion } from 'framer-motion';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { formatDisplay } from 'common/dateConfig';
import { formatDisplay } from 'common/utils/dateConfig';
import { ReactComponent as Emptyimage } from 'assets/images/empty.svg';
export default function Pip(props) {
@@ -55,7 +55,6 @@
font-size: 4vh;
}
/* =================== TITLES ===================*/
.infoContainer > div {
@@ -111,6 +110,8 @@
display: grid;
grid-template-rows: 5vh 1fr 3vh;
height: 100%;
overflow: hidden;
max-width: 100%;
}
/* =================== MAIN ===================*/
+483 -497
View File
File diff suppressed because it is too large Load Diff
-16
View File
@@ -1,16 +0,0 @@
import { stringFromMillis } from '../../src/utils/time';
const t1 = { val: null, result: '...' };
test('test stringFromMillis() on null values', () => {
expect(stringFromMillis(t1.val)).toBe(t1.result);
});
const t2 = { val: 3600000, result: '01:00:00' };
test('test stringFromMillis() on valid millis', () => {
expect(stringFromMillis(t2.val)).toBe(t2.result);
});
const t3 = { val: -3600000, result: '-01:00:00' };
test('test stringFromMillis() on negative millis', () => {
expect(stringFromMillis(t3.val)).toBe(t3.result);
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

+2 -2
View File
@@ -92,8 +92,8 @@ function createWindow() {
win = new BrowserWindow({
width: 1920,
height: 1000,
minWidth: 500,
minHeight: 530,
minWidth: 575,
minHeight: 405,
maxWidth: 1920,
maxHeight: 1440,
backgroundColor: '#202020',
+7 -3
View File
@@ -1,14 +1,18 @@
{
"name": "ontime",
"version": "0.1.1",
"version": "0.3.2",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
"keywords": [ "lighdev", "ontime", "timer" ],
"keywords": [
"lighdev",
"ontime",
"timer"
],
"license": "AGPL-3.0-only",
"main": "main.js",
"devDependencies": {
"electron": "^13.1.2",
"electron": "^13.6.1",
"electron-builder": "^22.11.3",
"eslint": "^7.26.0",
"eslint-config-airbnb": "^18.2.1",
+137 -70
View File
@@ -1,5 +1,6 @@
import { Timer } from './Timer.js';
import { Server } from 'socket.io';
import { getSelectionByRoll } from './classUtils.js';
/*
* EventTimer adds functions specific to APP
@@ -114,9 +115,9 @@ export class EventTimer extends Timer {
const reload = 'reload';
const finished = 'finished';
const time = this.timeTag;
// TODO: Should this be boolean?
const overtime = this.current > 0 ? 0 : 1;
const title = this.titles.titleNow;
const title = this.titles?.titleNow || '';
const presenter = this.titles?.presenterNow || '';
switch (event) {
case 'time':
@@ -137,11 +138,16 @@ export class EventTimer extends Timer {
if (err) console.error(err);
});
break;
case 'title':
case 'titles':
// Send Title of current event
this.oscClient.send(add + '/title', title, (err) => {
if (err) console.error(err);
});
// Send presenter data on current event
this.oscClient.send(add + '/presenter', presenter, (err) => {
if (err) console.error(err);
});
break;
case 'play':
// Play Message
@@ -199,12 +205,13 @@ export class EventTimer extends Timer {
if (this.state === 'start' || this.state === 'roll') {
this.sendOSC('time');
this.sendOSC('overtime');
this.sendOSC('titles');
}
}
// broadcast state
broadcastState() {
this.io.emit('timer', this.getObject());
broadcastState(update = true) {
this.io.emit('timer', this.getObject(update));
this.io.emit('playstate', this.state);
this.io.emit('selected', {
id: this.selectedEventId,
@@ -227,26 +234,54 @@ export class EventTimer extends Timer {
update() {
// if there is nothing selected, do nothing
if (this.selectedEventId == null && this.state !== 'roll') return;
const now = this._getCurrentTime();
// only implement roll here
if (this.state !== 'roll') {
super.update();
} else {
// get current time
const now = this._getCurrentTime();
// update timer as usual
this.clock = now;
if (this.selectedEventId && this.current > 0) {
// update timer as usual
// something is running, update
this.current = this._finishAt - now;
} else {
// look for event if none is loaded
if (this.current <= 0 || this.secondaryTimer <= 0) this.rollLoad();
} else if (this.secondaryTimer > 0) {
// waiting to start, update secondary
this.secondaryTimer = this._secondaryTarget - now;
}
// look for event if none is loaded
const currentRunning = this.current <= 0 && this.current !== null;
const secondaryRunning =
this.secondaryTimer <= 0 && this.secondaryTimer !== null;
if (currentRunning) {
// finished an event
this.sendOSC('finished');
this._finishedFlag = true;
}
if (currentRunning || secondaryRunning) {
// look for events
this.rollLoad();
// broadcast state without recalculating timer
this.broadcastState(false);
}
}
// sendOSC on reaching 0
if (this.current === 0 && this.state === 'start') this.sendOSC('finished');
// if event is finished
if (
this.current <= 0 &&
(this.state === 'start' || this.state === 'roll') &&
!this._finishedFlag
) {
if (this._finishedAt === null) {
this._finishedAt = now;
}
this.sendOSC('finished');
this._finishedFlag = true;
}
}
_setterManager(action, payload) {
@@ -511,6 +546,9 @@ export class EventTimer extends Timer {
const events = eventlist.filter((e) => e.type === 'event');
const numEvents = events.length;
// is this the first event
let first = this.numEvents === 0;
// set general
this._eventlist = events;
this.numEvents = numEvents;
@@ -521,8 +559,11 @@ export class EventTimer extends Timer {
return;
}
// handle reload selected
if (this.selectedEventId != null) {
// auto load if is the only event
if (first) {
this.loadEvent(0);
} else if (this.selectedEventId != null) {
// handle reload selected
// Look for event (order might have changed)
const eventIndex = this._eventlist.findIndex(
(e) => e.id === this.selectedEventId
@@ -539,6 +580,7 @@ export class EventTimer extends Timer {
const type = this._startedAt != null ? 'reload' : 'load';
this.loadEvent(eventIndex, type);
}
this.broadcastState();
}
@@ -561,7 +603,15 @@ export class EventTimer extends Timer {
? 'reload'
: 'load';
this.loadEvent(this.selectedEventIndex, type);
} else if ('title' in e || 'subtitle' in e || 'presenter') {
} else if (e.id === this.nextEventId) {
// roll needs to recalculate
if (this.state === 'roll') {
this.rollLoad();
}
}
// load titles
if ('title' in e || 'subtitle' in e || 'presenter' in e) {
// TODO: should be more selective on the need to load titles
this._loadTitlesNext();
this._loadTitlesNow();
@@ -604,13 +654,19 @@ export class EventTimer extends Timer {
}
loadEventById(eventId) {
let eventIndex = this._eventlist.findIndex((e) => e.id === eventId);
const eventIndex = this._eventlist.findIndex((e) => e.id === eventId);
if (eventIndex === -1) return;
this.pause();
this.loadEvent(eventIndex, 'load', true);
}
loadEventByIndex(eventIndex) {
if (eventIndex === -1 || eventIndex > this.numEvents) return;
this.pause();
this.loadEvent(eventIndex, 'load', true);
}
// Loads a given event
// load timers
// load selectedEventIndex
@@ -808,7 +864,7 @@ export class EventTimer extends Timer {
noteNext: null,
};
this.publicTitles = {
this.titlesPublic = {
titleNow: null,
subtitleNow: null,
presenterNow: null,
@@ -944,67 +1000,78 @@ export class EventTimer extends Timer {
const now = this._getCurrentTime();
// maybe roll has already been loaded
if (this.secondaryTimer == null) {
if (this.secondaryTimer === null) {
this._resetTimers(true);
this._resetSelection();
}
let foundNow = null;
let nextIndex = null;
let nextStart = null;
const {
nowIndex,
nowId,
publicIndex,
nextIndex,
publicNextIndex,
timers,
timeToNext,
} = getSelectionByRoll(this._eventlist, now);
// loop through events, look for where we should be
for (const [index, e] of this._eventlist.entries()) {
if (!foundNow) {
if (e.timeStart <= now && now < e.timeEnd) {
// set flag
foundNow = true;
// set timers
this._startedAt = e.timeStart;
this._finishAt = e.timeEnd;
this.duration = e.timeEnd - e.timeStart;
this.current = e.timeEnd - now;
// set selection
this.selectedEventId = e.id;
this.selectedEventIndex = index;
// set titles
this._loadTitlesNow();
// skip this entry for next
continue;
}
}
// check how far the start is from now
let wait = e.timeStart - now;
if (wait > 0) {
if (nextStart == null || wait < nextStart) {
nextStart = wait;
nextIndex = index;
}
}
}
// nothing to play next, unload
if (foundNow == null && nextIndex == null) {
// nothing to play, unload
if (nowIndex === null && nextIndex === null) {
this.unload();
console.log('Roll: no events found');
return;
}
if (nextIndex != null) {
// load titles
const e = this._eventlist[nextIndex];
this._loadThisTitles(e, 'next');
// there is something running, load
if (nowIndex !== null) {
// clear secondary timers
this.secondaryTimer = null;
this._secondaryTarget = null;
if (foundNow == null) {
if (this.secondaryTimer == null)
// set timers
this._startedAt = timers._startedAt;
this._finishAt = timers._finishAt;
this.duration = timers.duration;
this.current = timers.current;
// set selection
this.selectedEventId = nowId;
this.selectedEventIndex = nowIndex;
}
// found something to run next
if (nextIndex != null) {
// Set running timers
if (nowIndex === null) {
// only warn the first time
if (this.secondaryTimer === null)
console.log('Roll: waiting for event start');
// timer counts to nextStart
this.secondaryTimer = nextStart;
// reset running timer
// ??? should this not have been reset?
this.current = null;
// timer counts to next event
this.secondaryTimer = timeToNext;
this._secondaryTarget = this._eventlist[nextIndex].timeStart;
}
// TITLES: Load next private
this._loadThisTitles(this._eventlist[nextIndex], 'next-private');
}
// TITLES: Load next public
if (publicNextIndex !== null) {
this._loadThisTitles(this._eventlist[publicNextIndex], 'next-public');
}
// TITLES: Load now private
if (nowIndex !== null) {
this._loadThisTitles(this._eventlist[nowIndex], 'now-private');
}
// TITLES: Load now public
if (publicIndex !== null) {
this._loadThisTitles(this._eventlist[publicIndex], 'now-public');
}
}
@@ -1012,12 +1079,12 @@ export class EventTimer extends Timer {
// do we need to change
if (this.state === 'roll') return;
// load into event
this.rollLoad();
// set state
this.state = 'roll';
// load into event
this.rollLoad();
this.broadcastState();
}
+13 -9
View File
@@ -12,8 +12,10 @@ export class Timer {
current = null;
timeTag = null;
secondaryTimer = null;
_secondaryTarget = null;
_finishAt = null;
_finishedAt = null;
_finishedFlag = false;
_startedAt = null;
_pausedAt = null;
_pausedInterval = null;
@@ -83,9 +85,6 @@ export class Timer {
console.error('Timer: no playstate on update call', this.state);
break;
}
// Cleanup
if (this.current <= 0 && this._finishedAt == null) this._finishedAt = now;
}
// helpers
@@ -96,12 +95,15 @@ export class Timer {
// get current time in epoc
_getCurrentTime() {
// date today at midnight
const now = new Date();
const midnight = new Date(now).setHours(0, 0, 0);
// return diffence
return now - midnight;
// extract milliseconds since midnight
let elapsed = now.getHours() * 3600000;
elapsed += now.getMinutes() * 60000;
elapsed += now.getSeconds() * 1000;
elapsed += now.getMilliseconds();
return elapsed;
}
_getExpectedFinish() {
@@ -122,8 +124,10 @@ export class Timer {
this.current = this.duration;
this.running = null;
this.secondaryTimer = null;
this._secondaryTarget = null;
this._finishAt = null;
this._finishedAt = null;
this._finishedFlag = false;
this._startedAt = null;
this._pausedAt = null;
this._pausedInterval = null;
@@ -136,9 +140,9 @@ export class Timer {
}
// getObject
getObject() {
getObject(update = true) {
// update timer
this.update();
if (update) this.update();
// update timetag
this.timeTag = stringFromMillis(this.current);
@@ -0,0 +1,384 @@
import { getSelectionByRoll, sortArrayByProperty } from '../classUtils.js';
// test sortArrayByProperty()
describe('sort simple arrays of objects', () => {
it('sort array 1-5', () => {
const arr1 = [
{ timeStart: 1 },
{ timeStart: 5 },
{ timeStart: 3 },
{ timeStart: 2 },
{ timeStart: 4 },
];
const arr1Expected = [
{ timeStart: 1 },
{ timeStart: 2 },
{ timeStart: 3 },
{ timeStart: 4 },
{ timeStart: 5 },
];
const sorted = sortArrayByProperty(arr1, 'timeStart');
expect(sorted).toStrictEqual(arr1Expected);
});
it('sort array 1-5 with null', () => {
const arr1 = [
{ timeStart: 1 },
{ timeStart: 5 },
{ timeStart: 3 },
{ timeStart: 2 },
{ timeStart: 4 },
{ timeStart: null },
];
const arr1Expected = [
{ timeStart: null },
{ timeStart: 1 },
{ timeStart: 2 },
{ timeStart: 3 },
{ timeStart: 4 },
{ timeStart: 5 },
];
const sorted = sortArrayByProperty(arr1, 'timeStart');
expect(sorted).toStrictEqual(arr1Expected);
});
});
// test getSelectionByRoll()
describe('test that roll loads selection in right order', () => {
const eventlist = [
{
id: 1,
timeStart: 0,
timeEnd: 10,
isPublic: false,
},
{
id: 2,
timeStart: 10,
timeEnd: 20,
isPublic: true,
},
{
id: 3,
timeStart: 20,
timeEnd: 30,
isPublic: false,
},
{
id: 4,
timeStart: 30,
timeEnd: 40,
isPublic: false,
},
{
id: 5,
timeStart: 40,
timeEnd: 50,
isPublic: true,
},
{
id: 6,
timeStart: 50,
timeEnd: 60,
isPublic: false,
},
{
id: 7,
timeStart: 60,
timeEnd: 70,
isPublic: true,
},
{
id: 8,
timeStart: 70,
timeEnd: 80,
isPublic: false,
},
];
it('if timer is at 0', () => {
const now = 0;
const expected = {
nowIndex: 0,
nowId: 1,
publicIndex: null,
nextIndex: 1,
publicNextIndex: 1,
timers: {
_finishAt: 10,
_startedAt: 0,
current: 10,
duration: 10,
},
timeToNext: 10,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 15', () => {
const now = 15;
const expected = {
nowIndex: 1,
nowId: 2,
publicIndex: 1,
nextIndex: 2,
publicNextIndex: 4,
timers: {
_finishAt: 20,
_startedAt: 10,
current: 5,
duration: 10,
},
timeToNext: 5,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 20', () => {
const now = 20;
const expected = {
nowIndex: 2,
nowId: 3,
publicIndex: 1,
nextIndex: 3,
publicNextIndex: 4,
timers: {
_startedAt: 20,
_finishAt: 30,
current: 10,
duration: 10,
},
timeToNext: 10,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 49', () => {
const now = 49;
const expected = {
nowIndex: 4,
nowId: 5,
publicIndex: 4,
nextIndex: 5,
publicNextIndex: 6,
timers: {
_startedAt: 40,
_finishAt: 50,
current: 1,
duration: 10,
},
timeToNext: 1,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 63', () => {
const now = 63;
const expected = {
nowIndex: 6,
nowId: 7,
publicIndex: 6,
nextIndex: 7,
publicNextIndex: null,
timers: {
_startedAt: 60,
_finishAt: 70,
current: 7,
duration: 10,
},
timeToNext: 7,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 75', () => {
const now = 75;
const expected = {
nowIndex: 7,
nowId: 8,
publicIndex: 6,
nextIndex: null,
publicNextIndex: null,
timers: {
_startedAt: 70,
_finishAt: 80,
current: 5,
duration: 10,
},
timeToNext: null,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 100', () => {
const now = 100;
const expected = {
nowIndex: null,
nowId: null,
publicIndex: null,
nextIndex: null,
publicNextIndex: null,
timers: null,
timeToNext: null,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
});
// test getSelectionByRoll()
describe('test that roll behaviour with overlapping times', () => {
const eventlist = [
{
id: 1,
timeStart: 10,
timeEnd: 10,
isPublic: false,
},
{
id: 2,
timeStart: 10,
timeEnd: 20,
isPublic: true,
},
{
id: 3,
timeStart: 10,
timeEnd: 30,
isPublic: false,
},
];
it('if timer is at 0', () => {
const now = 0;
const expected = {
nowIndex: null,
nowId: null,
publicIndex: null,
nextIndex: 0,
publicNextIndex: 1,
timers: null,
timeToNext: 10,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 10', () => {
const now = 10;
const expected = {
nowIndex: 1,
nowId: 2,
publicIndex: 1,
nextIndex: 2,
publicNextIndex: null,
timers: {
_finishAt: 20,
_startedAt: 10,
current: 10,
duration: 10,
},
timeToNext: 0,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 15', () => {
const now = 15;
const expected = {
nowIndex: 1,
nowId: 2,
publicIndex: 1,
nextIndex: 2,
publicNextIndex: null,
timers: {
_startedAt: 10,
_finishAt: 20,
current: 5,
duration: 10,
},
timeToNext: -5,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 20', () => {
const now = 20;
const expected = {
nowIndex: 2,
nowId: 3,
publicIndex: 1,
nextIndex: null,
publicNextIndex: null,
timers: {
_startedAt: 10,
_finishAt: 30,
current: 10,
duration: 20,
},
timeToNext: null,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 25', () => {
const now = 25;
const expected = {
nowIndex: 2,
nowId: 3,
publicIndex: 1,
nextIndex: null,
publicNextIndex: null,
timers: {
_startedAt: 10,
_finishAt: 30,
current: 5,
duration: 20,
},
timeToNext: null,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
it('if timer is at 31', () => {
const now = 31;
const expected = {
nowIndex: null,
nowId: null,
publicIndex: null,
nextIndex: null,
publicNextIndex: null,
timers: null,
timeToNext: null,
};
const state = getSelectionByRoll(eventlist, now);
expect(state).toStrictEqual(expected);
});
});
+120
View File
@@ -0,0 +1,120 @@
/**
* @description Sorts an array of objects by given property
* @param {array} arr - array to be sorted
* @param {string} property - property to compare
* @returns {array} copy of array sorted in ascending order
*/
export const sortArrayByProperty = (arr, property) => {
return [...arr].sort((a, b) => {
return a[property] - b[property];
});
};
/**
* @description Used in roll mode, returns selection variables from array
* @param {array} arr - event list
* @param {number} now - time now in millis
* @returns {object} object with selection variables
*/
export const getSelectionByRoll = (arr, now) => {
// Events now
let nowIndex = null; // index of event now
let nowId = null; // id of event now
let publicIndex = null; // index of public event now
let publicTime = -1; // counter:
// Events next
let nextIndex = null; // index of next event
let publicNextIndex = null; // index of next public event
let timeToNext = null; // counter: time for next event
let publicTimeToNext = null; // counter: time for next public event
// current timer
let timers = null;
// Order events by startTime
const orderedEvents = sortArrayByProperty(arr, 'timeStart');
// flags: select first event if several overlapping
let nowFound = false;
// exit early if we are past the events
const lastEventEnd = orderedEvents[orderedEvents.length - 1].timeEnd;
if (now > lastEventEnd) {
return {
nowIndex,
nowId,
publicIndex,
nextIndex,
publicNextIndex,
timers,
timeToNext,
};
}
// loop through events, look for where we should be
for (const e of orderedEvents) {
// When does the event end (handle midnight)
const normalEnd =
e.timeEnd < e.timeStart ? (e.timeEnd += this.DAYMS) : e.timeEnd;
if (normalEnd <= now) {
// event ran already
// public event might not be the one running
if (e.isPublic && normalEnd > publicTime) {
publicTime = normalEnd;
publicIndex = arr.findIndex((a) => a.id === e.id);
}
} else if (normalEnd > now && now >= e.timeStart && !nowFound) {
// event is running
// it could also be public
if (e.isPublic) {
publicTime = normalEnd;
publicIndex = arr.findIndex((a) => a.id === e.id);
}
nowIndex = arr.findIndex((a) => a.id === e.id);
nowId = e.id;
// set timers
timers = {
_startedAt: e.timeStart,
_finishAt: normalEnd,
duration: normalEnd - e.timeStart,
current: normalEnd - now,
};
nowFound = true;
} else if (normalEnd > now) {
// event will run
// no need to look after found first
if (nextIndex !== null && publicNextIndex !== null) continue;
// look for next events
// check how far the start is from now
const wait = e.timeStart - now;
if (nextIndex === null || wait < timeToNext) {
timeToNext = wait;
nextIndex = arr.findIndex((a) => a.id === e.id);
}
if ((publicNextIndex === null || wait < publicTimeToNext) && e.isPublic) {
publicTimeToNext = wait;
publicNextIndex = arr.findIndex((a) => a.id === e.id);
}
}
}
return {
nowIndex,
nowId,
publicIndex,
nextIndex,
publicNextIndex,
timers,
timeToNext,
};
};
+31 -9
View File
@@ -26,7 +26,15 @@ export const initiateOSC = (config) => {
const args = msg[1];
// get first part before (ontime)
if (address !== 'ontime') return;
if (address !== 'ontime') {
console.error(`OSC IN: Message address ${address} not recognised`);
return;
}
if (path == null) {
console.error('OSC IN: No path found');
return;
}
// get second part (command)
switch (path.toLowerCase()) {
@@ -48,6 +56,7 @@ export const initiateOSC = (config) => {
global.timer.next();
break;
case 'unload':
case 'stop':
console.log('calling unload');
global.timer.unload();
break;
@@ -57,12 +66,17 @@ export const initiateOSC = (config) => {
break;
case 'roll':
console.log('calling roll');
global.timer.roll();
break;
case 'delay':
console.log('calling delay with', args);
try {
let t = parseInt(args);
if (!isNaN(t)) global.timer.increment(t * 1000 * 60);
const t = parseInt(args);
if (isNaN(t)) {
console.error(`OSC IN: delay time not recognised ${args}`);
return;
}
global.timer.increment(t * 1000 * 60);
} catch (error) {
console.log('error parsing: ', error);
}
@@ -70,17 +84,25 @@ export const initiateOSC = (config) => {
case 'goto':
console.log('calling goto with', args);
try {
let eventIndex = parseInt(args);
if (isNaN(eventIndex) || eventIndex <= 0 || eventIndex == null)
return;
global.timer.loadEvent(eventIndex - 1, undefined, true);
const eventIndex = parseInt(args);
if (isNaN(eventIndex) || eventIndex <= 0 || eventIndex == null) {
console.error(
`OSC IN: event index not recognised or out of range ${eventIndex}`
);
}
global.timer.loadEventByIndex(eventIndex - 1);
} catch (error) {
console.log('error calling goto: ', error);
}
break;
case 'gotoid':
console.log('calling gotoid with', args);
if (args == null) return;
if (args == null) {
console.error(
`OSC IN: event id not recognised or out of range ${args}`
);
return;
}
try {
global.timer.loadEventById(args.toString().toLowerCase());
} catch (error) {
@@ -89,7 +111,7 @@ export const initiateOSC = (config) => {
break;
default:
console.log('Error: not recognised');
console.log(`Error: unhandled message ${path}`);
break;
}
});
+58
View File
@@ -0,0 +1,58 @@
import { stringFromMillis } from '../time.js';
describe('test string to milis function', () => {
it('test with null values', () => {
const t = { val: null, result: '...' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with valid millis', () => {
const t = { val: 3600000, result: '01:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with negative millis', () => {
const t = { val: -3600000, result: '-01:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with -1', () => {
const t = { val: -1, result: '-00:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 0', () => {
const t = { val: 0, result: '00:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with -0', () => {
const t = { val: -0, result: '00:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 999', () => {
const t = { val: 999, result: '00:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 1000', () => {
const t = { val: 1000, result: '00:00:01' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 86400000 (24 hours)', () => {
const t = { val: 86400000, result: '00:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 86401000 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: '00:00:01' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with -86401000 (-24 hours and 1 second)', () => {
const t = { val: -86401000, result: '-00:00:01' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
});
+1097 -1133
View File
File diff suppressed because it is too large Load Diff
+563
View File
@@ -2,3 +2,566 @@
# yarn lockfile v1
"@electron/get@^1.13.0":
version "1.13.1"
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.1.tgz#42a0aa62fd1189638bd966e23effaebb16108368"
integrity sha512-U5vkXDZ9DwXtkPqlB45tfYnnYBN8PePp1z/XDCupnSpdrxT8/ThCv9WCwPLf9oqiSGZTkH6dx2jDUPuoXpjkcA==
dependencies:
debug "^4.1.1"
env-paths "^2.2.0"
fs-extra "^8.1.0"
got "^9.6.0"
progress "^2.0.3"
semver "^6.2.0"
sumchecker "^3.0.1"
optionalDependencies:
global-agent "^3.0.0"
global-tunnel-ng "^2.7.1"
"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@szmarczak/http-timer@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==
dependencies:
defer-to-connect "^1.0.1"
"@types/node@^14.6.2":
version "14.17.32"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.32.tgz#2ca61c9ef8c77f6fa1733be9e623ceb0d372ad96"
integrity sha512-JcII3D5/OapPGx+eJ+Ik1SQGyt6WvuqdRfh9jUwL6/iHGjmyOriBDciBUu7lEIBTL2ijxwrR70WUnw5AEDmFvQ==
boolean@^3.0.1:
version "3.1.4"
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.1.4.tgz#f51a2fb5838a99e06f9b6ec1edb674de67026435"
integrity sha512-3hx0kwU3uzG6ReQ3pnaFQPSktpBw6RHN3/ivDKEuU8g1XSfafowyvDnadjv1xp8IZqhtSukxlwv9bF6FhX8m0w==
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
cacheable-request@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==
dependencies:
clone-response "^1.0.2"
get-stream "^5.1.0"
http-cache-semantics "^4.0.0"
keyv "^3.0.0"
lowercase-keys "^2.0.0"
normalize-url "^4.1.0"
responselike "^1.0.2"
clone-response@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
dependencies:
mimic-response "^1.0.0"
concat-stream@^1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
dependencies:
buffer-from "^1.0.0"
inherits "^2.0.3"
readable-stream "^2.2.2"
typedarray "^0.0.6"
config-chain@^1.1.11:
version "1.1.13"
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
dependencies:
ini "^1.3.4"
proto-list "~1.2.1"
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
debug@^4.1.0, debug@^4.1.1:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies:
ms "2.1.2"
decompress-response@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
dependencies:
mimic-response "^1.0.0"
defer-to-connect@^1.0.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
dependencies:
object-keys "^1.0.12"
detect-node@^2.0.4:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
electron@^15.3.0:
version "15.3.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-15.3.0.tgz#f9803c5a05b2dac12efc6d4203492c7e204b4819"
integrity sha512-YLzaKCFmSniNlz9+NUTNs7ssPyDc+bYOCYZ0b/D6DjVkOeIFz4SR8EYKqlOc8TcqlDNu18BbWqz6zbJPyAAURg==
dependencies:
"@electron/get" "^1.13.0"
"@types/node" "^14.6.2"
extract-zip "^1.0.3"
encodeurl@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
dependencies:
once "^1.4.0"
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
es6-error@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
extract-zip@^1.0.3:
version "1.7.0"
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
dependencies:
concat-stream "^1.6.2"
debug "^2.6.9"
mkdirp "^0.5.4"
yauzl "^2.10.0"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
dependencies:
pend "~1.2.0"
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"
get-stream@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
dependencies:
pump "^3.0.0"
get-stream@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
dependencies:
pump "^3.0.0"
global-agent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6"
integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==
dependencies:
boolean "^3.0.1"
es6-error "^4.1.1"
matcher "^3.0.0"
roarr "^2.15.3"
semver "^7.3.2"
serialize-error "^7.0.1"
global-tunnel-ng@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f"
integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==
dependencies:
encodeurl "^1.0.2"
lodash "^4.17.10"
npm-conf "^1.1.3"
tunnel "^0.0.6"
globalthis@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b"
integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==
dependencies:
define-properties "^1.1.3"
got@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==
dependencies:
"@sindresorhus/is" "^0.14.0"
"@szmarczak/http-timer" "^1.1.2"
cacheable-request "^6.0.0"
decompress-response "^3.3.0"
duplexer3 "^0.1.4"
get-stream "^4.1.0"
lowercase-keys "^1.0.1"
mimic-response "^1.0.1"
p-cancelable "^1.0.0"
to-readable-stream "^1.0.0"
url-parse-lax "^3.0.0"
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
http-cache-semantics@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
inherits@^2.0.3, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ini@^1.3.4:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
json-buffer@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
json-stringify-safe@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
optionalDependencies:
graceful-fs "^4.1.6"
keyv@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
dependencies:
json-buffer "3.0.0"
lodash@^4.17.10:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
lowercase-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
matcher@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==
dependencies:
escape-string-regexp "^4.0.0"
mimic-response@^1.0.0, mimic-response@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
mkdirp@^0.5.4:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
dependencies:
minimist "^1.2.5"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
normalize-url@^4.1.0:
version "4.5.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
npm-conf@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9"
integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==
dependencies:
config-chain "^1.1.11"
pify "^3.0.0"
object-keys@^1.0.12:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
p-cancelable@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
prepend-http@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
progress@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
readable-stream@^2.2.2:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
responselike@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
dependencies:
lowercase-keys "^1.0.0"
roarr@^2.15.3:
version "2.15.4"
resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd"
integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==
dependencies:
boolean "^3.0.1"
detect-node "^2.0.4"
globalthis "^1.0.1"
json-stringify-safe "^5.0.1"
semver-compare "^1.0.0"
sprintf-js "^1.1.2"
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
semver@^6.2.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.2:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
serialize-error@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==
dependencies:
type-fest "^0.13.1"
sprintf-js@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
sumchecker@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42"
integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==
dependencies:
debug "^4.1.0"
to-readable-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
tunnel@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
type-fest@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
url-parse-lax@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
dependencies:
prepend-http "^2.0.0"
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yauzl@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"