mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-11 10:23:54 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37786c3528 | |||
| c6d36d2497 | |||
| 2394eced93 | |||
| 13aeeb278d | |||
| af5d6e1aee | |||
| bc036bdae7 | |||
| 266a84e5d3 | |||
| 92c3148e49 | |||
| efd05bcc34 | |||
| 3c7e061e51 | |||
| 07074260a4 | |||
| c57b0423af | |||
| 52145d5e1e | |||
| b2668e61a5 | |||
| 7da3480dfe | |||
| e8cfdbe3b9 | |||
| fd052355c3 | |||
| 00f04fb78c | |||
| 6f80de58ca | |||
| 5977266332 |
@@ -128,57 +128,42 @@ Found a bug? [Open an issue](https://github.com/cpvalente/ontime/issues/new).
|
|||||||
|
|
||||||
When installing the app you would see warning screens from the Operating System like:
|
When installing the app you would see warning screens from the Operating System like:
|
||||||
|
|
||||||
|
in Windows
|
||||||
|
|
||||||
`Microsoft Defender SmartScreen prevented an unrecognised app from starting. Running this app might put your PC at risk.`
|
`Microsoft Defender SmartScreen prevented an unrecognised app from starting. Running this app might put your PC at risk.`
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
`Ontime can't be opened because it is from an unidentified developer`
|
|
||||||
|
|
||||||
or in Linux
|
or in Linux
|
||||||
|
|
||||||
`Could Not Display "ontime-linux.AppImage`
|
`Could Not Display "ontime-linux.AppImage`
|
||||||
|
|
||||||
|
We currently only sign MacOS releases. \
|
||||||
|
Purchasing the certificates for both Mac and Windows would mean a recurrent expense which we are not able to cover.
|
||||||
|
|
||||||
You can circumvent this by allowing the execution of the app manually.
|
You can circumvent this by allowing the execution of the app manually.
|
||||||
|
|
||||||
- In Windows: click more and select "Run Anyway"
|
- In Windows: click `more` -> `Run Anyway`
|
||||||
- in macOS: the solution in macOS is different across versions, please refer to
|
- In Linux: right-click the AppImage file: `Properties` -> `Permissions` -> `Allow Executing File as a Program`
|
||||||
the [apple documentation](https://support.apple.com/en-gb/guide/mac-help/mh40616/mac)
|
|
||||||
- In Linux: right-click the AppImage file -> Properties -> Permissions -> select Allow Executing
|
|
||||||
File as a Program
|
|
||||||
|
|
||||||
Long story short: Ontime app is unsigned. \
|
|
||||||
Purchasing the certificates for both Mac and Windows would mean a recurrent expense and is not a priority. \
|
|
||||||
This is unlikely to change in future. \
|
|
||||||
If you have tips on how to improve this or would like to sponsor the code signing,
|
If you have tips on how to improve this or would like to sponsor the code signing,
|
||||||
please [open an issue](https://github.com/cpvalente/ontime/issues/new)
|
please [open an issue](https://github.com/cpvalente/ontime/issues/new)
|
||||||
|
|
||||||
#### Safari
|
|
||||||
|
|
||||||
There are known issues with Safari versions lower than 13:
|
|
||||||
|
|
||||||
- Spacing and text styles might have small inconsistencies
|
|
||||||
- Table view does not work
|
|
||||||
|
|
||||||
There is no plan for any further work on this.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Looking to contribute? All types of help are appreciated, from coding to testing and feature specification.
|
Looking to contribute? All types of help are appreciated, from coding to testing and feature specification.
|
||||||
|
|
||||||
If you are a developer and would like to contribute with some code, please open an issue to discuss before opening a
|
If you are a developer and would like to contribute with code, please open an issue to discuss before opening a Pull Request.
|
||||||
Pull Request.
|
|
||||||
|
|
||||||
Information about the project setup can be found in the [development documentation](./DEVELOPMENT.md)
|
Information about the project setup can be found in the [development documentation](./DEVELOPMENT.md)
|
||||||
|
|
||||||
## Help
|
## Links
|
||||||
|
|
||||||
Help is underway! ... and can be found [here](https://docs.getontime.no)
|
See the [Ontime website](https://getontime.no) here and the link to the [documentation](https://docs.getontime.no)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the terms of the GNU GPL v3
|
This project is licensed under the terms of the GNU GPL v3
|
||||||
|
|
||||||
## SPONSOR
|
## Sponsor
|
||||||
|
|
||||||
You can help the development of this project or say thank you with a one time donation. \
|
You can help the development of this project or say thank you with a one time donation. \
|
||||||
See [the terms of donations](https://github.com/cpvalente/ontime/blob/master/.github/FUNDING.md).
|
See [the terms of donations](https://github.com/cpvalente/ontime/blob/master/.github/FUNDING.md).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-ui",
|
"name": "ontime-ui",
|
||||||
"version": "3.0.0",
|
"version": "3.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ const dbPath = `${apiEntryUrl}/db`;
|
|||||||
/**
|
/**
|
||||||
* HTTP request to the current DB
|
* HTTP request to the current DB
|
||||||
*/
|
*/
|
||||||
async function getDb(): Promise<AxiosResponse<DatabaseModel>> {
|
async function getDb(fileName?: string): Promise<AxiosResponse<DatabaseModel>> {
|
||||||
return axios.get(`${dbPath}/download`);
|
return axios.post(`${dbPath}/download/`, { fileName });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request download of the current project file
|
* Request download of the current project file
|
||||||
* @param fileName
|
* @param fileName
|
||||||
*/
|
*/
|
||||||
export async function downloadProject(fileName: string = 'ontime-project') {
|
export async function downloadProject(fileName: string) {
|
||||||
try {
|
try {
|
||||||
const { data, name } = await fileDownload(fileName);
|
const { data, name } = await fileDownload(fileName);
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ export async function getInfo(): Promise<GetInfo> {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async function fileDownload(fileName: string): Promise<{ data: DatabaseModel; name: string }> {
|
async function fileDownload(fileName: string): Promise<{ data: DatabaseModel; name: string }> {
|
||||||
const response = await getDb();
|
const response = await getDb(fileName);
|
||||||
|
|
||||||
const headerLine = response.headers['Content-Disposition'];
|
const headerLine = response.headers['Content-Disposition'];
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { MILLIS_PER_HOUR, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils';
|
||||||
|
|
||||||
import { forgivingStringToMillis, millisToDelayString } from '../dateConfig';
|
import { forgivingStringToMillis, millisToDelayString } from '../dateConfig';
|
||||||
|
|
||||||
describe('test forgivingStringToMillis()', () => {
|
describe('test forgivingStringToMillis()', () => {
|
||||||
@@ -6,22 +8,22 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
{ value: '', expect: 0 },
|
{ value: '', expect: 0 },
|
||||||
{ value: '0', expect: 0 },
|
{ value: '0', expect: 0 },
|
||||||
{ value: '-0', expect: 0 },
|
{ value: '-0', expect: 0 },
|
||||||
{ value: '1', expect: 60 * 1000 },
|
{ value: '1', expect: MILLIS_PER_MINUTE },
|
||||||
{ value: '-1', expect: 60 * 1000 },
|
{ value: '-1', expect: MILLIS_PER_MINUTE },
|
||||||
{ value: '0h0m0s', expect: 0 },
|
{ value: '0h0m0s', expect: 0 },
|
||||||
{ value: '0h0m1s', expect: 1000 },
|
{ value: '0h0m1s', expect: MILLIS_PER_SECOND },
|
||||||
{ value: '0h1m0s', expect: 1000 * 60 },
|
{ value: '0h1m0s', expect: MILLIS_PER_MINUTE },
|
||||||
{ value: '1h0m0s', expect: 1000 * 60 * 60 },
|
{ value: '1h0m0s', expect: MILLIS_PER_HOUR },
|
||||||
{ value: '23h0m0s', expect: 1000 * 60 * 60 * 23 },
|
{ value: '23h0m0s', expect: 23 * MILLIS_PER_HOUR },
|
||||||
{ value: '12h12m12s', expect: 12 * 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 },
|
{ value: '12h12m12s', expect: 12 * MILLIS_PER_SECOND + 12 * MILLIS_PER_MINUTE + 12 * MILLIS_PER_HOUR },
|
||||||
{ value: '12H12M12S', expect: 12 * 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 },
|
{ value: '12H12M12S', expect: 12 * MILLIS_PER_SECOND + 12 * MILLIS_PER_MINUTE + 12 * MILLIS_PER_HOUR },
|
||||||
{ value: '2m', expect: 2 * 60 * 1000 },
|
{ value: '2m', expect: 2 * MILLIS_PER_MINUTE },
|
||||||
{ value: '1h5s', expect: 1000 * 60 * 60 + 1000 * 5 },
|
{ value: '1h5s', expect: MILLIS_PER_HOUR + 5 * MILLIS_PER_SECOND },
|
||||||
{ value: '1h2m', expect: 1000 * 60 * 60 + 1000 * 60 * 2 },
|
{ value: '1h2m', expect: MILLIS_PER_HOUR + 2 * MILLIS_PER_MINUTE },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of testData) {
|
for (const s of testData) {
|
||||||
it(`handles ${s.value} to left`, () => {
|
it(`handles ${s.value}`, () => {
|
||||||
expect(typeof forgivingStringToMillis(s.value)).toBe('number');
|
expect(typeof forgivingStringToMillis(s.value)).toBe('number');
|
||||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||||
});
|
});
|
||||||
@@ -30,10 +32,10 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
|
|
||||||
describe('parses strings correctly', () => {
|
describe('parses strings correctly', () => {
|
||||||
const ts = [
|
const ts = [
|
||||||
{ value: '1.1.1', expect: 60 * 60 * 1000 + 60 * 1000 + 1000 },
|
{ value: '1.1.1', expect: MILLIS_PER_HOUR + MILLIS_PER_MINUTE + MILLIS_PER_SECOND },
|
||||||
{ value: '12.1.1', expect: 12 * 60 * 60 * 1000 + 60 * 1000 + 1000 },
|
{ value: '12.1.1', expect: 12 * MILLIS_PER_HOUR + MILLIS_PER_MINUTE + MILLIS_PER_SECOND },
|
||||||
{ value: '12.55.1', expect: 12 * 60 * 60 * 1000 + 55 * 60 * 1000 + 1000 },
|
{ value: '12.55.1', expect: 12 * MILLIS_PER_HOUR + 55 * MILLIS_PER_MINUTE + MILLIS_PER_SECOND },
|
||||||
{ value: '12.55.40', expect: 12 * 60 * 60 * 1000 + 55 * 60 * 1000 + 40 * 1000 },
|
{ value: '12.55.40', expect: 12 * MILLIS_PER_HOUR + 55 * MILLIS_PER_MINUTE + 40 * MILLIS_PER_SECOND },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of ts) {
|
for (const s of ts) {
|
||||||
@@ -45,15 +47,15 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
|
|
||||||
describe('parses time strings', () => {
|
describe('parses time strings', () => {
|
||||||
const ts = [
|
const ts = [
|
||||||
{ value: '1h2m3s', expect: 60 * 60 * 1000 + 2 * 60 * 1000 + 3 * 1000 },
|
{ value: '1h2m3s', expect: MILLIS_PER_HOUR + 2 * MILLIS_PER_MINUTE + 3 * MILLIS_PER_SECOND },
|
||||||
{ value: '1h3s', expect: 60 * 60 * 1000 + 3 * 1000 },
|
{ value: '1h3s', expect: MILLIS_PER_HOUR + 3 * MILLIS_PER_SECOND },
|
||||||
{ value: '1h2m', expect: 60 * 60 * 1000 + 2 * 60 * 1000 },
|
{ value: '1h2m', expect: MILLIS_PER_HOUR + 2 * MILLIS_PER_MINUTE },
|
||||||
{ value: '10h', expect: 10 * 60 * 60 * 1000 },
|
{ value: '10h', expect: 10 * MILLIS_PER_HOUR },
|
||||||
{ value: '10m', expect: 10 * 60 * 1000 },
|
{ value: '10m', expect: 10 * MILLIS_PER_MINUTE },
|
||||||
{ value: '10s', expect: 10 * 1000 },
|
{ value: '10s', expect: 10 * MILLIS_PER_SECOND },
|
||||||
{ value: '120h', expect: 120 * 60 * 60 * 1000 },
|
{ value: '120h', expect: 120 * MILLIS_PER_HOUR },
|
||||||
{ value: '120m', expect: 120 * 60 * 1000 },
|
{ value: '120m', expect: 120 * MILLIS_PER_MINUTE },
|
||||||
{ value: '120s', expect: 120 * 1000 },
|
{ value: '120s', expect: 120 * MILLIS_PER_SECOND },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of ts) {
|
for (const s of ts) {
|
||||||
@@ -65,20 +67,20 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
|
|
||||||
describe('handles am/pm', () => {
|
describe('handles am/pm', () => {
|
||||||
const ampm = [
|
const ampm = [
|
||||||
{ value: '9:10:11am', expect: 9 * 60 * 60 * 1000 + 10 * 60 * 1000 + 11 * 1000 },
|
{ value: '9:10:11am', expect: 9 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE + 11 * MILLIS_PER_SECOND },
|
||||||
{ value: '9:10:11a', expect: 9 * 60 * 60 * 1000 + 10 * 60 * 1000 + 11 * 1000 },
|
{ value: '9:10:11a', expect: 9 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE + 11 * MILLIS_PER_SECOND },
|
||||||
{ value: '9:10:11pm', expect: (12 + 9) * 60 * 60 * 1000 + 10 * 60 * 1000 + 11 * 1000 },
|
{ value: '9:10:11pm', expect: (12 + 9) * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE + 11 * MILLIS_PER_SECOND },
|
||||||
{ value: '9:10:11p', expect: (12 + 9) * 60 * 60 * 1000 + 10 * 60 * 1000 + 11 * 1000 },
|
{ value: '9:10:11p', expect: (12 + 9) * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE + 11 * MILLIS_PER_SECOND },
|
||||||
{ value: '9:10am', expect: 9 * 60 * 60 * 1000 + 10 * 60 * 1000 },
|
{ value: '9:10am', expect: 9 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE },
|
||||||
{ value: '9:10a', expect: 9 * 60 * 60 * 1000 + 10 * 60 * 1000 },
|
{ value: '9:10a', expect: 9 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE },
|
||||||
{ value: '9:10pm', expect: (12 + 9) * 60 * 60 * 1000 + 10 * 60 * 1000 },
|
{ value: '9:10pm', expect: (12 + 9) * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE },
|
||||||
{ value: '9:10p', expect: (12 + 9) * 60 * 60 * 1000 + 10 * 60 * 1000 },
|
{ value: '9:10p', expect: (12 + 9) * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE },
|
||||||
{ value: '9am', expect: 9 * 60 * 60 * 1000 },
|
{ value: '9am', expect: 9 * MILLIS_PER_HOUR },
|
||||||
{ value: '9a', expect: 9 * 60 * 60 * 1000 },
|
{ value: '9a', expect: 9 * MILLIS_PER_HOUR },
|
||||||
{ value: '9pm', expect: (12 + 9) * 60 * 60 * 1000 },
|
{ value: '9pm', expect: (12 + 9) * MILLIS_PER_HOUR },
|
||||||
{ value: '9p', expect: (12 + 9) * 60 * 60 * 1000 },
|
{ value: '9p', expect: (12 + 9) * MILLIS_PER_HOUR },
|
||||||
{ value: '12am', expect: 0 },
|
{ value: '12am', expect: 0 },
|
||||||
{ value: '12pm', expect: 12 * 60 * 60 * 1000 },
|
{ value: '12pm', expect: 12 * MILLIS_PER_HOUR },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of ampm) {
|
for (const s of ampm) {
|
||||||
@@ -90,12 +92,12 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
|
|
||||||
describe('it infers separators when non existent', () => {
|
describe('it infers separators when non existent', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{ value: '1', expect: 1000 * 60 }, // 00:01:00
|
{ value: '1', expect: MILLIS_PER_MINUTE }, // 00:01:00
|
||||||
{ value: '12', expect: 1000 * 60 * 12 }, // 00:12:00
|
{ value: '12', expect: 12 * MILLIS_PER_MINUTE }, // 00:12:00
|
||||||
{ value: '123', expect: 1000 * 60 * 23 + 1000 * 60 * 60 }, // 01:23:00
|
{ value: '123', expect: MILLIS_PER_HOUR + 23 * MILLIS_PER_MINUTE }, // 01:23:00
|
||||||
{ value: '1234', expect: 1000 * 60 * 34 + 1000 * 60 * 60 * 12 }, // 12:34:00
|
{ value: '1234', expect: 12 * MILLIS_PER_HOUR + 34 * MILLIS_PER_MINUTE }, // 12:34:00
|
||||||
{ value: '12345', expect: 1000 * 60 * 34 + 1000 * 60 * 60 * 12 + 5 * 1000 }, // 12:34:05
|
{ value: '12345', expect: 12 * MILLIS_PER_HOUR + 34 * MILLIS_PER_MINUTE + 5 * MILLIS_PER_SECOND }, // 12:34:05
|
||||||
{ value: '123456', expect: 1000 * 60 * 34 + 1000 * 60 * 60 * 12 + 56 * 1000 }, // 12:34:56
|
{ value: '123456', expect: 12 * MILLIS_PER_HOUR + 34 * MILLIS_PER_MINUTE + 56 * MILLIS_PER_SECOND }, // 12:34:56
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of testCases) {
|
for (const s of testCases) {
|
||||||
@@ -105,12 +107,12 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sixDigits = [
|
const sixDigits = [
|
||||||
{ value: '000000', expect: 0 },
|
{ value: '000000', expect: 0 }, // 00:00:00
|
||||||
{ value: '000001', expect: 1000 },
|
{ value: '000001', expect: MILLIS_PER_SECOND }, // 00:00:01
|
||||||
{ value: '000100', expect: 1000 * 60 },
|
{ value: '000100', expect: MILLIS_PER_MINUTE }, // 00:01:00
|
||||||
{ value: '010000', expect: 1000 * 60 * 60 },
|
{ value: '010000', expect: MILLIS_PER_HOUR }, // 01:00:00
|
||||||
{ value: '230000', expect: 1000 * 60 * 60 * 23 },
|
{ value: '230000', expect: MILLIS_PER_HOUR * 23 }, // 23:00:00
|
||||||
{ value: '121212', expect: 12 * 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 },
|
{ value: '121212', expect: 12 * MILLIS_PER_HOUR + 12 * MILLIS_PER_MINUTE + 12 * MILLIS_PER_SECOND }, // 12:12:12
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of sixDigits) {
|
for (const s of sixDigits) {
|
||||||
@@ -121,13 +123,13 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
|
|
||||||
const fiveDigits = [
|
const fiveDigits = [
|
||||||
{ value: '00000', expect: 0 },
|
{ value: '00000', expect: 0 },
|
||||||
{ value: '00001', expect: 1000 }, // 00:00:01
|
{ value: '00001', expect: MILLIS_PER_SECOND }, // 00:00:01
|
||||||
{ value: '00010', expect: 1000 * 60 }, // 00:01:00
|
{ value: '00010', expect: MILLIS_PER_MINUTE }, // 00:01:00
|
||||||
{ value: '00100', expect: 1000 * 60 * 10 }, // 00:10:00
|
{ value: '00100', expect: 10 * MILLIS_PER_MINUTE }, // 00:10:00
|
||||||
{ value: '01000', expect: 1000 * 60 * 60 }, // 01:00:00
|
{ value: '01000', expect: MILLIS_PER_HOUR }, // 01:00:00
|
||||||
{ value: '10000', expect: 1000 * 60 * 60 * 10 }, // 10:00:00
|
{ value: '10000', expect: 10 * MILLIS_PER_HOUR }, // 10:00:00
|
||||||
{ value: '23000', expect: 1000 * 60 * 60 * 23 }, // 23:00:00
|
{ value: '23000', expect: 23 * MILLIS_PER_HOUR }, // 23:00:00
|
||||||
{ value: '12121', expect: 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 }, // 12:12:01
|
{ value: '12121', expect: 12 * MILLIS_PER_HOUR + 12 * MILLIS_PER_MINUTE + 1 * MILLIS_PER_SECOND }, // 12:12:01
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of fiveDigits) {
|
for (const s of fiveDigits) {
|
||||||
@@ -138,12 +140,12 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
|
|
||||||
const fourDigits = [
|
const fourDigits = [
|
||||||
{ value: '0000', expect: 0 },
|
{ value: '0000', expect: 0 },
|
||||||
{ value: '0001', expect: 1000 * 60 }, // 00:01:00
|
{ value: '0001', expect: MILLIS_PER_MINUTE }, // 00:01:00
|
||||||
{ value: '0010', expect: 1000 * 60 * 10 }, // 00:10:00
|
{ value: '0010', expect: 10 * MILLIS_PER_MINUTE }, // 00:10:00
|
||||||
{ value: '0100', expect: 1000 * 60 * 60 }, // 01:00:00
|
{ value: '0100', expect: MILLIS_PER_HOUR }, // 01:00:00
|
||||||
{ value: '1000', expect: 1000 * 60 * 60 * 10 }, // 10:00:00
|
{ value: '1000', expect: 10 * MILLIS_PER_HOUR }, // 10:00:00
|
||||||
{ value: '2300', expect: 1000 * 60 * 60 * 23 }, // 23:00:00
|
{ value: '2300', expect: 23 * MILLIS_PER_HOUR }, // 23:00:00
|
||||||
{ value: '1212', expect: 12 * 60 * 1000 + 12 * 1000 * 60 * 60 }, // 12:12:00
|
{ value: '1212', expect: 12 * MILLIS_PER_HOUR + 12 * MILLIS_PER_MINUTE }, // 12:12:00
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of fourDigits) {
|
for (const s of fourDigits) {
|
||||||
@@ -154,11 +156,11 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
|
|
||||||
const threeDigits = [
|
const threeDigits = [
|
||||||
{ value: '000', expect: 0 },
|
{ value: '000', expect: 0 },
|
||||||
{ value: '001', expect: 1000 * 60 }, // 00:01:00
|
{ value: '001', expect: MILLIS_PER_MINUTE }, // 00:01:00
|
||||||
{ value: '010', expect: 1000 * 60 * 10 }, // 00:10:00
|
{ value: '010', expect: 10 * MILLIS_PER_MINUTE }, // 00:10:00
|
||||||
{ value: '100', expect: 1000 * 60 * 60 }, // 01:00:00
|
{ value: '100', expect: MILLIS_PER_HOUR }, // 01:00:00
|
||||||
{ value: '230', expect: 2 * 1000 * 60 * 60 + 30 * 1000 * 60 }, // 02:30:00
|
{ value: '230', expect: 2 * MILLIS_PER_HOUR + 30 * MILLIS_PER_MINUTE }, // 02:30:00
|
||||||
{ value: '121', expect: 21 * 60 * 1000 + 1000 * 60 * 60 }, // 01:21:00
|
{ value: '121', expect: MILLIS_PER_HOUR + 21 * MILLIS_PER_MINUTE }, // 01:21:00
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of threeDigits) {
|
for (const s of threeDigits) {
|
||||||
@@ -169,9 +171,9 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
|
|
||||||
const twoDigits = [
|
const twoDigits = [
|
||||||
{ value: '00', expect: 0 },
|
{ value: '00', expect: 0 },
|
||||||
{ value: '01', expect: 1000 * 60 }, // 00:01:00
|
{ value: '01', expect: MILLIS_PER_MINUTE }, // 00:01:00
|
||||||
{ value: '10', expect: 1000 * 60 * 10 }, // 00:10:00
|
{ value: '10', expect: 10 * MILLIS_PER_MINUTE }, // 00:10:00
|
||||||
{ value: '23', expect: 1000 * 60 * 23 }, // 00:23:00
|
{ value: '23', expect: 23 * MILLIS_PER_MINUTE }, // 00:23:00
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of twoDigits) {
|
for (const s of twoDigits) {
|
||||||
@@ -192,18 +194,17 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
describe('handles overflows', () => {
|
describe('handles overflows', () => {
|
||||||
const ts = [
|
const ts = [
|
||||||
// minutes overflow
|
// minutes overflow
|
||||||
{ value: '2.0.0', expect: 1000 * 60 * 120 },
|
{ value: '99', expect: 99 * MILLIS_PER_MINUTE },
|
||||||
{ value: '99', expect: 1000 * 60 * 99 },
|
{ value: '1.39.0', expect: 99 * MILLIS_PER_MINUTE },
|
||||||
{ value: '1.39.0', expect: 1000 * 60 * 99 },
|
|
||||||
// seconds overflow
|
// seconds overflow
|
||||||
{ value: '0.0.120', expect: 120 * 1000 },
|
{ value: '0.0.120', expect: 120 * MILLIS_PER_SECOND },
|
||||||
{ value: '0.2.0', expect: 120 * 1000 },
|
{ value: '0.2.0', expect: 120 * MILLIS_PER_SECOND },
|
||||||
{ value: '0.0.99', expect: 99 * 1000 },
|
{ value: '0.0.99', expect: 99 * MILLIS_PER_SECOND },
|
||||||
{ value: '0.1.39', expect: 99 * 1000 },
|
{ value: '0.1.39', expect: 99 * MILLIS_PER_SECOND },
|
||||||
// hours overflow
|
// hours overflow
|
||||||
{ value: '25.0.0', expect: 1000 * 60 * 60 * 25 },
|
{ value: '25.0.0', expect: 25 * MILLIS_PER_HOUR },
|
||||||
// hours overflow
|
// hours overflow
|
||||||
{ value: '50.0.0', expect: 1000 * 60 * 60 * 50 },
|
{ value: '50.0.0', expect: 50 * MILLIS_PER_HOUR },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of ts) {
|
for (const s of ts) {
|
||||||
@@ -216,10 +217,10 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
describe('test fillLeft', () => {
|
describe('test fillLeft', () => {
|
||||||
describe('function handles separators', () => {
|
describe('function handles separators', () => {
|
||||||
const testData = [
|
const testData = [
|
||||||
{ value: '1:2:3:10', expect: 3723000 },
|
{ value: '1:2:3:10', expect: MILLIS_PER_HOUR + 2 * MILLIS_PER_MINUTE + 3 * MILLIS_PER_SECOND }, // 01:02:03
|
||||||
{ value: '2,10', expect: 2 * 60 * 60 * 1000 + 60 * 10 * 1000 },
|
{ value: '2,10', expect: 2 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE },
|
||||||
{ value: '2.10', expect: 2 * 60 * 60 * 1000 + 60 * 10 * 1000 },
|
{ value: '2.10', expect: 2 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE },
|
||||||
{ value: '2 10', expect: 2 * 60 * 60 * 1000 + 60 * 10 * 1000 },
|
{ value: '2 10', expect: 2 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of testData) {
|
for (const s of testData) {
|
||||||
@@ -232,8 +233,8 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
|
|
||||||
describe('parses strings correctly', () => {
|
describe('parses strings correctly', () => {
|
||||||
const ts = [
|
const ts = [
|
||||||
{ value: '1.2', expect: 60 * 60 * 1000 + 2 * 60 * 1000 },
|
{ value: '1.2', expect: MILLIS_PER_HOUR + 2 * MILLIS_PER_MINUTE },
|
||||||
{ value: '1.70', expect: 60 * 60 * 1000 + 70 * 60 * 1000 },
|
{ value: '1.70', expect: MILLIS_PER_HOUR + 70 * MILLIS_PER_MINUTE },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of ts) {
|
for (const s of ts) {
|
||||||
@@ -246,8 +247,8 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
describe('handles overflows', () => {
|
describe('handles overflows', () => {
|
||||||
const ts = [
|
const ts = [
|
||||||
// minutes overflow
|
// minutes overflow
|
||||||
{ value: '0.120', expect: 120 * 60 * 1000 },
|
{ value: '0.120', expect: 120 * MILLIS_PER_MINUTE },
|
||||||
{ value: '0.99', expect: 99 * 60 * 1000 },
|
{ value: '0.99', expect: 99 * MILLIS_PER_MINUTE },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const s of ts) {
|
for (const s of ts) {
|
||||||
|
|||||||
+2
-2
@@ -93,10 +93,10 @@ export default function UrlPresetsForm() {
|
|||||||
<Panel.SubHeader>
|
<Panel.SubHeader>
|
||||||
URL presets
|
URL presets
|
||||||
<div className={style.actionButtons}>
|
<div className={style.actionButtons}>
|
||||||
<Button variant='ontime-ghosted' size='md' onClick={onReset} isDisabled={!canSubmit}>
|
<Button variant='ontime-ghosted' size='sm' onClick={onReset} isDisabled={!canSubmit}>
|
||||||
Revert to saved
|
Revert to saved
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='ontime-filled' size='md' type='submit' isDisabled={!canSubmit} isLoading={isSubmitting}>
|
<Button variant='ontime-filled' size='sm' type='submit' isDisabled={!canSubmit} isLoading={isSubmitting}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ function ActionMenu({
|
|||||||
<MenuItem onClick={handleRename}>Rename</MenuItem>
|
<MenuItem onClick={handleRename}>Rename</MenuItem>
|
||||||
<MenuItem onClick={handleDuplicate}>Duplicate</MenuItem>
|
<MenuItem onClick={handleDuplicate}>Duplicate</MenuItem>
|
||||||
<MenuItem onClick={handleDownload}>Download</MenuItem>
|
<MenuItem onClick={handleDownload}>Download</MenuItem>
|
||||||
{current && <MenuItem onClick={handleExportCSV}>Export CSV Rundown</MenuItem>}
|
<MenuItem onClick={handleExportCSV}>Export CSV Rundown</MenuItem>
|
||||||
<MenuItem isDisabled={current} onClick={handleDelete}>
|
<MenuItem isDisabled={current} onClick={handleDelete}>
|
||||||
Delete
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ function TitlesOverview() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TimerOverview() {
|
function TimerOverview() {
|
||||||
const {current} = useTimer();
|
const { current } = useTimer();
|
||||||
|
|
||||||
const display = millisToString(current);
|
const display = millisToString(current);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { dayInMs } from 'ontime-utils';
|
||||||
|
|
||||||
|
import { calculateEndAndDaySpan } from '../overviewUtils';
|
||||||
|
|
||||||
|
describe('calculateEndAndDaySpan', () => {
|
||||||
|
it('should return [null, 0] when end is null', () => {
|
||||||
|
const result = calculateEndAndDaySpan(null);
|
||||||
|
expect(result).toEqual([null, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return [end, 0] when end is less than or equal to dayInMs', () => {
|
||||||
|
const end = dayInMs / 2;
|
||||||
|
const result = calculateEndAndDaySpan(end);
|
||||||
|
expect(result).toEqual([end, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return [end % dayInMs, Math.floor(end / dayInMs)] when end is greater than dayInMs', () => {
|
||||||
|
const end = dayInMs * 1.5;
|
||||||
|
const result = calculateEndAndDaySpan(end);
|
||||||
|
expect(result).toEqual([end % dayInMs, Math.floor(end / dayInMs)]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -13,20 +13,14 @@ export function formatedTime(time: MaybeNumber) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates a day span from a number range
|
* Calculates how long a time is and how many days it spans
|
||||||
* @param end
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
export function calculateEndAndDaySpan(end: MaybeNumber): [MaybeNumber, number] {
|
export function calculateEndAndDaySpan(end: MaybeNumber): [MaybeNumber, number] {
|
||||||
let maybeEnd = end;
|
if (end !== null && end > dayInMs) {
|
||||||
let maybeDaySpan = 0;
|
return [end % dayInMs, Math.floor(end / dayInMs)];
|
||||||
if (end !== null) {
|
|
||||||
if (end > dayInMs) {
|
|
||||||
maybeEnd = end % dayInMs;
|
|
||||||
maybeDaySpan = Math.floor(end / dayInMs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return [maybeEnd, maybeDaySpan];
|
|
||||||
|
return [end, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -242,13 +242,7 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
id='event-block'
|
id='event-block'
|
||||||
>
|
>
|
||||||
<RundownIndicators
|
<RundownIndicators timeStart={timeStart} previousStart={previousStart} previousEnd={previousEnd} delay={delay} />
|
||||||
timeStart={timeStart}
|
|
||||||
timeEnd={timeEnd}
|
|
||||||
previousStart={previousStart}
|
|
||||||
previousEnd={previousEnd}
|
|
||||||
delay={delay}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={style.binder} style={{ ...binderColours }} tabIndex={-1}>
|
<div className={style.binder} style={{ ...binderColours }} tabIndex={-1}>
|
||||||
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
|
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
|
||||||
|
|||||||
@@ -10,24 +10,41 @@ export function formatDelay(timeStart: number, delay: number): string | undefine
|
|||||||
return `New start ${timeTag}`;
|
return `New start ${timeTag}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function checks whether a given event is the day after from its predecessor
|
||||||
|
* We consider an event to be the day after, if it begins before the start of the previous
|
||||||
|
* @example day after
|
||||||
|
* // 09:00 - 10:00
|
||||||
|
* // 08:00 - 10:30 <--- day after
|
||||||
|
* @example same day
|
||||||
|
* // 09:00 - 10:00
|
||||||
|
* // 09:30 - 10:30 <--- same day
|
||||||
|
*/
|
||||||
|
function checkIsNextDay(previousStart: number, timeStart: number): boolean {
|
||||||
|
return timeStart < previousStart;
|
||||||
|
}
|
||||||
|
|
||||||
export function formatOverlap(
|
export function formatOverlap(
|
||||||
previousStart: MaybeNumber,
|
previousStart: MaybeNumber,
|
||||||
previousEnd: MaybeNumber,
|
previousEnd: MaybeNumber,
|
||||||
timeStart: number,
|
timeStart: number,
|
||||||
timeEnd: number,
|
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (previousEnd === null) return;
|
const noPreviousElement = previousEnd === null || previousStart === null;
|
||||||
|
if (noPreviousElement) return;
|
||||||
|
|
||||||
const overlap = previousEnd - timeStart;
|
const overlap = previousEnd - timeStart;
|
||||||
if (overlap === 0) return;
|
if (overlap === 0) return;
|
||||||
|
|
||||||
if (previousStart && timeStart < previousEnd) {
|
const previousCrossMidnight = previousStart > previousEnd;
|
||||||
const overlap = timeEnd - previousStart;
|
const isNextDay = previousCrossMidnight
|
||||||
if (overlap > 0) {
|
? checkIsNextDay(previousEnd, timeStart) || previousEnd == 0 // exception for when previousEnd is precisely midnight
|
||||||
const overlapString = removeLeadingZero(millisToString(Math.abs(overlap)));
|
: checkIsNextDay(previousStart, timeStart);
|
||||||
return `Overlap ${overlapString}`;
|
|
||||||
}
|
const correctedPreviousEnd = previousCrossMidnight ? previousEnd + dayInMs : previousEnd;
|
||||||
const gap = timeStart + dayInMs - previousEnd;
|
|
||||||
|
if (isNextDay) {
|
||||||
|
const gap = dayInMs - correctedPreviousEnd + timeStart;
|
||||||
|
if (gap === 0) return;
|
||||||
const gapString = removeLeadingZero(millisToString(Math.abs(gap)));
|
const gapString = removeLeadingZero(millisToString(Math.abs(gap)));
|
||||||
return `Gap ${gapString} (next day)`;
|
return `Gap ${gapString} (next day)`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,15 @@ import style from './RundownIndicators.module.scss';
|
|||||||
|
|
||||||
interface RundownIndicatorProps {
|
interface RundownIndicatorProps {
|
||||||
timeStart: number;
|
timeStart: number;
|
||||||
timeEnd: number;
|
|
||||||
previousStart: MaybeNumber;
|
previousStart: MaybeNumber;
|
||||||
previousEnd: MaybeNumber;
|
previousEnd: MaybeNumber;
|
||||||
delay: number;
|
delay: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RundownIndicators(props: RundownIndicatorProps) {
|
export default function RundownIndicators(props: RundownIndicatorProps) {
|
||||||
const { timeStart, timeEnd, previousStart, previousEnd, delay } = props;
|
const { timeStart, previousStart, previousEnd, delay } = props;
|
||||||
|
|
||||||
const hasOverlap = formatOverlap(previousStart, previousEnd, timeStart, timeEnd);
|
const hasOverlap = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
const hasDelay = formatDelay(timeStart, delay);
|
const hasDelay = formatDelay(timeStart, delay);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { MILLIS_PER_HOUR } from 'ontime-utils';
|
||||||
|
|
||||||
import { formatDelay, formatOverlap } from '../EventBlock.utils';
|
import { formatDelay, formatOverlap } from '../EventBlock.utils';
|
||||||
|
|
||||||
describe('formatDelay()', () => {
|
describe('formatDelay()', () => {
|
||||||
@@ -14,35 +16,47 @@ describe('formatOverlap()', () => {
|
|||||||
const previousStart = 0;
|
const previousStart = 0;
|
||||||
const previousEnd = 60000; // 1 min
|
const previousEnd = 60000; // 1 min
|
||||||
const timeStart = 30000; // 30 sec
|
const timeStart = 30000; // 30 sec
|
||||||
const timeEnd = 90000; // 1:30 min
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
const result = formatOverlap(previousStart, previousEnd, timeStart, timeEnd);
|
|
||||||
expect(result).toEqual('Overlap 0:30');
|
expect(result).toEqual('Overlap 0:30');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('bug #949 recognises an overlap between two times', () => {
|
||||||
|
const previousStart = 46800000; // 13:00:00
|
||||||
|
const previousEnd = 48600000; // 13:30:00
|
||||||
|
const timeStart = 48300000; // 13:25:00
|
||||||
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
|
expect(result).toEqual('Overlap 5:00');
|
||||||
|
});
|
||||||
|
|
||||||
it('handles events the day after, without overlap', () => {
|
it('handles events the day after, without overlap', () => {
|
||||||
const previousStart = new Date(0).setUTCHours(11);
|
const previousStart = 11 * MILLIS_PER_HOUR;
|
||||||
const previousEnd = new Date(0).setUTCHours(12);
|
const previousEnd = 12 * MILLIS_PER_HOUR;
|
||||||
const timeStart = new Date(0).setUTCHours(6);
|
const timeStart = 6 * MILLIS_PER_HOUR;
|
||||||
const timeEnd = new Date(0).setUTCHours(10);
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
const result = formatOverlap(previousStart, previousEnd, timeStart, timeEnd);
|
|
||||||
expect(result).toBe('Gap 18:00:00 (next day)');
|
expect(result).toBe('Gap 18:00:00 (next day)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles events the day after, with overlap', () => {
|
|
||||||
const previousStart = new Date(0).setUTCHours(9);
|
|
||||||
const previousEnd = new Date(0).setUTCHours(10);
|
|
||||||
const timeStart = new Date(0).setUTCHours(6);
|
|
||||||
const timeEnd = new Date(0).setUTCHours(11);
|
|
||||||
const result = formatOverlap(previousStart, previousEnd, timeStart, timeEnd);
|
|
||||||
expect(result).toBe('Overlap 02:00:00');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles events the day after, with gap', () => {
|
it('handles events the day after, with gap', () => {
|
||||||
const previousStart = new Date(0).setUTCHours(17);
|
const previousStart = 17 * MILLIS_PER_HOUR;
|
||||||
const previousEnd = new Date(0).setUTCHours(23);
|
const previousEnd = 23 * MILLIS_PER_HOUR;
|
||||||
const timeStart = new Date(0).setUTCHours(9);
|
const timeStart = 9 * MILLIS_PER_HOUR;
|
||||||
const timeEnd = new Date(0).setUTCHours(11);
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
const result = formatOverlap(previousStart, previousEnd, timeStart, timeEnd);
|
|
||||||
expect(result).toBe('Gap 10:00:00 (next day)');
|
expect(result).toBe('Gap 10:00:00 (next day)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles events the day after, with previous ending at midnight', () => {
|
||||||
|
const previousStart = 23 * MILLIS_PER_HOUR; // 23:00:00
|
||||||
|
const previousEnd = 0; // 00:00:00
|
||||||
|
const timeStart = 1 * MILLIS_PER_HOUR; // 01:00:00
|
||||||
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
|
expect(result).toBe('Gap 01:00:00 (next day)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles events the day after, with previous ending over midnight', () => {
|
||||||
|
const previousStart = 23 * MILLIS_PER_HOUR;
|
||||||
|
const previousEnd = 1 * MILLIS_PER_HOUR;
|
||||||
|
const timeStart = 2 * MILLIS_PER_HOUR;
|
||||||
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
|
expect(result).toBe('Gap 01:00:00');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,3 +32,9 @@
|
|||||||
.prompt {
|
.prompt {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,49 +18,77 @@ function EventEditorEmpty() {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceAlt}</Kbd> + <Kbd>↑</Kbd> / <Kbd>↓</Kbd>
|
<Kbd>{deviceAlt}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>↑</Kbd>
|
||||||
|
<AuxKey>/</AuxKey>
|
||||||
|
<Kbd>↓</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Select entry</td>
|
<td>Select entry</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceAlt}</Kbd> + <Kbd>{deviceMod}</Kbd> + <Kbd>↑</Kbd> / <Kbd>↑</Kbd>
|
<Kbd>{deviceAlt}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>{deviceMod}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>↑</Kbd>
|
||||||
|
<AuxKey>/</AuxKey>
|
||||||
|
<Kbd>↑</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Reorder selected entry</td>
|
<td>Reorder selected entry</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceAlt}</Kbd> + <Kbd>E</Kbd>
|
<Kbd>{deviceAlt}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>E</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Add event below</td>
|
<td>Add event below</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceAlt}</Kbd> + <Kbd>↑</Kbd> + <Kbd>E</Kbd>
|
<Kbd>{deviceAlt}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>↑</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>E</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Add event above</td>
|
<td>Add event above</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceAlt}</Kbd> + <Kbd>B</Kbd>
|
<Kbd>{deviceAlt}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>B</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Add block below</td>
|
<td>Add block below</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceAlt}</Kbd> + <Kbd>↑</Kbd> + <Kbd>B</Kbd>
|
<Kbd>{deviceAlt}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>↑</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>B</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Add block above</td>
|
<td>Add block above</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceAlt}</Kbd> + <Kbd>D</Kbd>
|
<Kbd>{deviceAlt}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>D</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Add delay below</td>
|
<td>Add delay below</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceAlt}</Kbd> + <Kbd>↑</Kbd> + <Kbd>D</Kbd>
|
<Kbd>{deviceAlt}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>↑</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>D</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Add delay above</td>
|
<td>Add delay above</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -72,25 +100,35 @@ function EventEditorEmpty() {
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceMod}</Kbd> + <Kbd>⌫</Kbd>
|
<Kbd>{deviceMod}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>⌫</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Delete selected entry</td>
|
<td>Delete selected entry</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceMod}</Kbd> + <Kbd>C</Kbd>
|
<Kbd>{deviceMod}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>C</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Copy selected entry</td>
|
<td>Copy selected entry</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceMod}</Kbd> + <Kbd>↑</Kbd> + <Kbd>V</Kbd>
|
<Kbd>{deviceMod}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>↑</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>V</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Paste above</td>
|
<td>Paste above</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Kbd>{deviceMod}</Kbd> + <Kbd>V</Kbd>
|
<Kbd>{deviceMod}</Kbd>
|
||||||
|
<AuxKey>+</AuxKey>
|
||||||
|
<Kbd>V</Kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>Paste below</td>
|
<td>Paste below</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -100,3 +138,7 @@ function EventEditorEmpty() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AuxKey({ children }: { children: React.ReactNode }) {
|
||||||
|
return <span className={style.divider}>{children}</span>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -83,9 +83,13 @@ const theme = extendTheme({
|
|||||||
Kbd: {
|
Kbd: {
|
||||||
baseStyle: {
|
baseStyle: {
|
||||||
borderRadius: '2px',
|
borderRadius: '2px',
|
||||||
border: '1px solid rgba(255, 255, 255, 0.20)',
|
border: 'none',
|
||||||
background: 'rgba(255, 255, 255, 0.10)',
|
background: 'rgba(255, 255, 255, 0.10)',
|
||||||
color: 'white',
|
padding: '0.125rem 0.5rem',
|
||||||
|
color: '#f6f6f6', // $ui-white
|
||||||
|
fontWeight: 400,
|
||||||
|
boxShadow: '0px 0px 3px 0px rgba(0,0,0,0.4)',
|
||||||
|
fontSize: 'calc(1rem - 2px)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "3.0.0",
|
"version": "3.0.2",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "ontime-server",
|
"name": "ontime-server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"version": "3.0.0",
|
"version": "3.0.2",
|
||||||
"exports": "./src/index.js",
|
"exports": "./src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@googleapis/sheets": "^5.0.5",
|
"@googleapis/sheets": "^5.0.5",
|
||||||
@@ -10,9 +10,8 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-session": "^1.17.3",
|
|
||||||
"express-static-gzip": "^2.1.7",
|
"express-static-gzip": "^2.1.7",
|
||||||
"express-validator": "^6.14.2",
|
"express-validator": "^7.0.1",
|
||||||
"fast-equals": "^5.0.1",
|
"fast-equals": "^5.0.1",
|
||||||
"google-auth-library": "^9.4.2",
|
"google-auth-library": "^9.4.2",
|
||||||
"got": "^14.0.0",
|
"got": "^14.0.0",
|
||||||
|
|||||||
@@ -6,17 +6,19 @@ import {
|
|||||||
ProjectData,
|
ProjectData,
|
||||||
ProjectFileListResponse,
|
ProjectFileListResponse,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
|
import { getErrorMessage } from 'ontime-utils';
|
||||||
|
|
||||||
|
import { join } from 'path';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
|
|
||||||
import { failEmptyObjects } from '../../utils/routerUtils.js';
|
import { failEmptyObjects } from '../../utils/routerUtils.js';
|
||||||
import { resolveDbPath, resolveProjectsDirectory } from '../../setup/index.js';
|
import { resolveDbDirectory, resolveProjectsDirectory } from '../../setup/index.js';
|
||||||
|
|
||||||
import * as projectService from '../../services/project-service/ProjectService.js';
|
import * as projectService from '../../services/project-service/ProjectService.js';
|
||||||
import { ensureJsonExtension } from '../../utils/fileManagement.js';
|
import { ensureJsonExtension } from '../../utils/fileManagement.js';
|
||||||
import { generateUniqueFileName } from '../../utils/generateUniqueFilename.js';
|
import { generateUniqueFileName } from '../../utils/generateUniqueFilename.js';
|
||||||
import { appStateService } from '../../services/app-state-service/AppStateService.js';
|
import { appStateService } from '../../services/app-state-service/AppStateService.js';
|
||||||
import { getErrorMessage } from 'ontime-utils';
|
|
||||||
|
|
||||||
export async function patchPartialProjectFile(req: Request, res: Response<DatabaseModel | ErrorResponse>) {
|
export async function patchPartialProjectFile(req: Request, res: Response<DatabaseModel | ErrorResponse>) {
|
||||||
// all fields are optional in validation
|
// all fields are optional in validation
|
||||||
@@ -76,9 +78,30 @@ export async function createProjectFile(req: Request, res: Response<{ filename:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function projectDownload(_req: Request, res: Response) {
|
/**
|
||||||
const fileTitle = projectService.getProjectTitle();
|
* Utility function finds the correct project file to download
|
||||||
res.download(resolveDbPath, `${fileTitle}.json`, (error) => {
|
*/
|
||||||
|
function selectProjectFile(fileName?: string) {
|
||||||
|
const projectsDirectory = resolveDbDirectory;
|
||||||
|
const fileToDownload = fileName ? ensureJsonExtension(fileName) : projectService.getProjectTitle();
|
||||||
|
const pathToFile = join(projectsDirectory, fileToDownload);
|
||||||
|
|
||||||
|
return { pathToFile, name: fileToDownload };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows downloading of a optionally given project files
|
||||||
|
* If no {filename} is provided, loaded file will be served
|
||||||
|
*/
|
||||||
|
export async function projectDownload(req: Request, res: Response) {
|
||||||
|
const { pathToFile, name } = selectProjectFile(req.body?.fileName);
|
||||||
|
|
||||||
|
// Check if the file exists before attempting to download
|
||||||
|
if (!existsSync(pathToFile)) {
|
||||||
|
return res.status(404).send({ message: `Project ${name} not found.` });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.download(pathToFile, name, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
const message = getErrorMessage(error);
|
const message = getErrorMessage(error);
|
||||||
res.status(500).send({ message });
|
res.status(500).send({ message });
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { uploadProjectFile } from './db.middleware.js';
|
|||||||
import {
|
import {
|
||||||
projectSanitiser,
|
projectSanitiser,
|
||||||
sanitizeProjectFilename,
|
sanitizeProjectFilename,
|
||||||
|
validateDownloadProject,
|
||||||
validateLoadProjectFile,
|
validateLoadProjectFile,
|
||||||
validatePatchProjectFile,
|
validatePatchProjectFile,
|
||||||
validateProjectDuplicate,
|
validateProjectDuplicate,
|
||||||
@@ -24,7 +25,7 @@ import {
|
|||||||
|
|
||||||
export const router = express.Router();
|
export const router = express.Router();
|
||||||
|
|
||||||
router.get('/download', projectDownload);
|
router.post('/download', validateDownloadProject, projectDownload);
|
||||||
router.post('/upload', uploadProjectFile, postProjectFile);
|
router.post('/upload', uploadProjectFile, postProjectFile);
|
||||||
|
|
||||||
router.patch('/', validatePatchProjectFile, patchPartialProjectFile);
|
router.patch('/', validatePatchProjectFile, patchPartialProjectFile);
|
||||||
|
|||||||
@@ -104,3 +104,19 @@ export const validateProjectRename = [
|
|||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Validates a download request which can include an optional project name.
|
||||||
|
*/
|
||||||
|
export const validateDownloadProject = [
|
||||||
|
body('fileName').isString().optional(),
|
||||||
|
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(422).json({ errors: errors.array() });
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ export const timerConfig = {
|
|||||||
skipLimit: 1000, // threshold of skip for recalculating
|
skipLimit: 1000, // threshold of skip for recalculating
|
||||||
updateRate: 32, // how often do we update the timer
|
updateRate: 32, // how often do we update the timer
|
||||||
notificationRate: 1000, // how often do we notify clients and integrations
|
notificationRate: 1000, // how often do we notify clients and integrations
|
||||||
triggerAhead: 16, // how far ahead do we trigger the end event
|
triggerAhead: 10, // how far ahead do we trigger the end event
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { OntimeEvent } from 'ontime-types';
|
|||||||
|
|
||||||
import * as runtimeState from '../stores/runtimeState.js';
|
import * as runtimeState from '../stores/runtimeState.js';
|
||||||
import type { UpdateResult } from '../stores/runtimeState.js';
|
import type { UpdateResult } from '../stores/runtimeState.js';
|
||||||
|
import { timerConfig } from '../config/config.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service manages Ontime's main timer
|
* Service manages Ontime's main timer
|
||||||
@@ -41,7 +42,7 @@ export class TimerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
const endTime = state.timer.current - 10;
|
const endTime = state.timer.current - timerConfig.triggerAhead;
|
||||||
this.endCallback = setTimeout(() => this.update(), endTime);
|
this.endCallback = setTimeout(() => this.update(), endTime);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,24 @@ export class HttpIntegration implements IIntegration<HttpSubscription, HttpSetti
|
|||||||
|
|
||||||
emit(path: string) {
|
emit(path: string) {
|
||||||
got.get(path, { retry: { limit: 0 } }).catch((err) => {
|
got.get(path, { retry: { limit: 0 } }).catch((err) => {
|
||||||
logger.error(LogOrigin.Tx, `HTTP Integration: ${err.code}`);
|
logger.warning(LogOrigin.Tx, `HTTP Integration: ${err.message}`);
|
||||||
|
|
||||||
|
if (err.code === 'ECONNREFUSED') {
|
||||||
|
logger.warning(LogOrigin.Tx, `HTTP Integration: '${err.code}' The server refused the connection`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === 'ENOTFOUND') {
|
||||||
|
logger.warning(LogOrigin.Tx, `HTTP Integration: '${err.code}' DNS lookup failed`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === 'ETIMEDOUT') {
|
||||||
|
logger.warning(LogOrigin.Tx, `HTTP Integration: '${err.code}' The connection timed out`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warning(LogOrigin.Tx, `HTTP Integration: ${err.code}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ export function generate(
|
|||||||
links = {};
|
links = {};
|
||||||
firstStart = null;
|
firstStart = null;
|
||||||
lastEnd = null;
|
lastEnd = null;
|
||||||
|
totalDuration = 0;
|
||||||
|
totalDelay = 0;
|
||||||
|
|
||||||
let accumulatedDelay = 0;
|
let accumulatedDelay = 0;
|
||||||
let daySpan = 0;
|
let daySpan = 0;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EndAction, LogOrigin, OntimeEvent, Playback, RuntimeStore, TimerLifeCycle } from 'ontime-types';
|
import { EndAction, LogOrigin, MaybeNumber, OntimeEvent, Playback, RuntimeStore, TimerLifeCycle } from 'ontime-types';
|
||||||
import { millisToString, validatePlayback } from 'ontime-utils';
|
import { millisToString, validatePlayback } from 'ontime-utils';
|
||||||
|
|
||||||
import { deepEqual } from 'fast-equals';
|
import { deepEqual } from 'fast-equals';
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
getPlayableEvents,
|
getPlayableEvents,
|
||||||
} from '../rundown-service/rundownUtils.js';
|
} from '../rundown-service/rundownUtils.js';
|
||||||
import { integrationService } from '../integration-service/IntegrationService.js';
|
import { integrationService } from '../integration-service/IntegrationService.js';
|
||||||
|
import { getForceUpdate, getShouldClockUpdate, getShouldTimerUpdate } from './rundownService.utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service manages runtime status of app
|
* Service manages runtime status of app
|
||||||
@@ -27,15 +28,20 @@ import { integrationService } from '../integration-service/IntegrationService.js
|
|||||||
*/
|
*/
|
||||||
class RuntimeService {
|
class RuntimeService {
|
||||||
private eventTimer: TimerService | null = null;
|
private eventTimer: TimerService | null = null;
|
||||||
private lastOnUpdate = -1;
|
private lastIntegrationClockUpdate = -1;
|
||||||
|
private lastIntegrationTimerValue = -1;
|
||||||
|
|
||||||
/** last time we updated the socket */
|
/** last time we updated the socket */
|
||||||
static previousUpdate: number;
|
static previousTimerUpdate: number;
|
||||||
|
static previousTimerValue: MaybeNumber;
|
||||||
|
static previousClockUpdate: number;
|
||||||
/** last known state */
|
/** last known state */
|
||||||
static previousState: RuntimeState;
|
static previousState: RuntimeState;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
RuntimeService.previousUpdate = -1;
|
RuntimeService.previousTimerUpdate = -1;
|
||||||
|
RuntimeService.previousTimerValue = -1;
|
||||||
|
RuntimeService.previousClockUpdate = -1;
|
||||||
RuntimeService.previousState = {} as RuntimeState;
|
RuntimeService.previousState = {} as RuntimeState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,19 +67,25 @@ class RuntimeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update normal cycle
|
const hasRunningTimer = Boolean(newState.eventNow) && newState.timer.playback === Playback.Play;
|
||||||
if (newState.clock - this.lastOnUpdate >= timerConfig.notificationRate) {
|
const shouldUpdateTimer =
|
||||||
const hasRunningTimer = Boolean(newState.eventNow) && newState.timer.playback === Playback.Play;
|
hasRunningTimer && getShouldTimerUpdate(this.lastIntegrationTimerValue, newState.timer.current);
|
||||||
if (hasRunningTimer) {
|
|
||||||
process.nextTick(() => {
|
if (shouldUpdateTimer) {
|
||||||
integrationService.dispatch(TimerLifeCycle.onUpdate);
|
process.nextTick(() => {
|
||||||
});
|
integrationService.dispatch(TimerLifeCycle.onUpdate);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
this.lastIntegrationTimerValue = newState.timer.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldUpdateClock = getShouldClockUpdate(this.lastIntegrationClockUpdate, newState.clock);
|
||||||
|
if (shouldUpdateClock) {
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
integrationService.dispatch(TimerLifeCycle.onClock);
|
integrationService.dispatch(TimerLifeCycle.onClock);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.lastOnUpdate = newState.clock;
|
this.lastIntegrationClockUpdate = newState.clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldCallRoll) {
|
if (shouldCallRoll) {
|
||||||
@@ -516,29 +528,30 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
|||||||
// we do the comparison by explicitly for each property
|
// we do the comparison by explicitly for each property
|
||||||
// to apply custom logic for different datasets
|
// to apply custom logic for different datasets
|
||||||
|
|
||||||
// some of the data, we only update at intervals
|
const shouldUpdateClock = getShouldClockUpdate(RuntimeService.previousClockUpdate, state.clock);
|
||||||
const isTimeToUpdate =
|
const shouldForceTimerUpdate = getForceUpdate(RuntimeService.previousTimerUpdate, state.clock);
|
||||||
state.clock < RuntimeService.previousUpdate ||
|
const shouldUpdateTimer =
|
||||||
state.clock - RuntimeService.previousUpdate >= timerConfig.notificationRate;
|
shouldForceTimerUpdate || getShouldTimerUpdate(RuntimeService.previousTimerValue, state.timer.current);
|
||||||
|
|
||||||
// some changes need an immediate update
|
// some changes need an immediate update
|
||||||
const hasNewLoaded = state.eventNow?.id !== RuntimeService.previousState?.eventNow?.id;
|
const hasNewLoaded = state.eventNow?.id !== RuntimeService.previousState?.eventNow?.id;
|
||||||
|
|
||||||
const hasSkippedBack = state.clock < RuntimeService.previousUpdate;
|
|
||||||
const justStarted = !RuntimeService.previousState?.timer;
|
const justStarted = !RuntimeService.previousState?.timer;
|
||||||
const hasChangedPlayback = RuntimeService.previousState.timer?.playback !== state.timer.playback;
|
const hasChangedPlayback = RuntimeService.previousState.timer?.playback !== state.timer.playback;
|
||||||
const hasImmediateChanges = hasNewLoaded || hasSkippedBack || justStarted || hasChangedPlayback;
|
const hasImmediateChanges = hasNewLoaded || justStarted || hasChangedPlayback;
|
||||||
|
|
||||||
if (hasChangedPlayback) {
|
if (hasChangedPlayback) {
|
||||||
eventStore.set('onAir', state.timer.playback !== Playback.Stop);
|
eventStore.set('onAir', state.timer.playback !== Playback.Stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasImmediateChanges || (isTimeToUpdate && !deepEqual(RuntimeService.previousState?.timer, state.timer))) {
|
if (hasImmediateChanges || (shouldUpdateTimer && !deepEqual(RuntimeService.previousState?.timer, state.timer))) {
|
||||||
|
RuntimeService.previousTimerUpdate = state.clock;
|
||||||
|
RuntimeService.previousTimerValue = state.timer.current;
|
||||||
eventStore.set('timer', state.timer);
|
eventStore.set('timer', state.timer);
|
||||||
RuntimeService.previousState.timer = { ...state.timer };
|
RuntimeService.previousState.timer = { ...state.timer };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasChangedPlayback || (isTimeToUpdate && !deepEqual(RuntimeService.previousState?.runtime, state.runtime))) {
|
if (hasChangedPlayback || (shouldUpdateTimer && !deepEqual(RuntimeService.previousState?.runtime, state.runtime))) {
|
||||||
eventStore.set('runtime', state.runtime);
|
eventStore.set('runtime', state.runtime);
|
||||||
RuntimeService.previousState.runtime = { ...state.runtime };
|
RuntimeService.previousState.runtime = { ...state.runtime };
|
||||||
}
|
}
|
||||||
@@ -549,8 +562,8 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
|||||||
updateEventIfChanged('eventNext', state);
|
updateEventIfChanged('eventNext', state);
|
||||||
updateEventIfChanged('publicEventNext', state);
|
updateEventIfChanged('publicEventNext', state);
|
||||||
|
|
||||||
if (isTimeToUpdate) {
|
if (shouldUpdateClock) {
|
||||||
RuntimeService.previousUpdate = state.clock;
|
RuntimeService.previousClockUpdate = state.clock;
|
||||||
eventStore.set('clock', state.clock);
|
eventStore.set('clock', state.clock);
|
||||||
saveRestoreState(state);
|
saveRestoreState(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
|
import { getShouldClockUpdate, getShouldTimerUpdate } from '../rundownService.utils.js';
|
||||||
|
|
||||||
|
describe('getShouldClockUpdate()', () => {
|
||||||
|
it('should return true when we slid forwards', () => {
|
||||||
|
const previousUpdate = Date.now() - 2000; // 2 seconds ago
|
||||||
|
const now = Date.now();
|
||||||
|
const result = getShouldClockUpdate(previousUpdate, now);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when we slid backwards', () => {
|
||||||
|
const previousUpdate = Date.now();
|
||||||
|
const now = Date.now() - 2000; // 2 seconds ago
|
||||||
|
const result = getShouldClockUpdate(previousUpdate, now);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when clock is a second ahead', () => {
|
||||||
|
const previousUpdate = MILLIS_PER_MINUTE - 100;
|
||||||
|
const now = MILLIS_PER_MINUTE;
|
||||||
|
const result = getShouldClockUpdate(previousUpdate, now);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when clock is not a second ahead and force update is not required', () => {
|
||||||
|
const previousUpdate = Date.now() - 32;
|
||||||
|
const now = Date.now();
|
||||||
|
const result = getShouldClockUpdate(previousUpdate, now);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getShouldTimerUpdate', () => {
|
||||||
|
it('should return false when currentValue is null', () => {
|
||||||
|
const previousValue = 0;
|
||||||
|
const currentValue = null;
|
||||||
|
const result = getShouldTimerUpdate(previousValue, currentValue);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when timer is a second ahead', () => {
|
||||||
|
const previousValue = 6500;
|
||||||
|
const currentValue = 5000;
|
||||||
|
const result = getShouldTimerUpdate(previousValue, currentValue);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when timer is not a second ahead', () => {
|
||||||
|
const previousValue = 5500;
|
||||||
|
const currentValue = 5200;
|
||||||
|
const result = getShouldTimerUpdate(previousValue, currentValue);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('timer value is ceiled', () => {
|
||||||
|
const previousValue = 5001; // 6
|
||||||
|
const currentValue = 4999; // 5
|
||||||
|
const result = getShouldTimerUpdate(previousValue, currentValue);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { millisToSeconds } from 'ontime-utils';
|
||||||
|
|
||||||
|
import { timerConfig } from '../../config/config.js';
|
||||||
|
import { MaybeNumber } from 'ontime-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether we should update the clock value
|
||||||
|
* - clock has slid
|
||||||
|
* - we have rolled into a new seconds unit
|
||||||
|
*/
|
||||||
|
export function getShouldClockUpdate(previousUpdate: number, now: number): boolean {
|
||||||
|
const shouldForceUpdate = getForceUpdate(previousUpdate, now);
|
||||||
|
if (shouldForceUpdate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const isClockSecondAhead = millisToSeconds(now) !== millisToSeconds(previousUpdate + timerConfig.triggerAhead);
|
||||||
|
return isClockSecondAhead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether we should update the timer value
|
||||||
|
* - we have rolled into a new seconds unit
|
||||||
|
*/
|
||||||
|
export function getShouldTimerUpdate(previousValue: number, currentValue: MaybeNumber): boolean {
|
||||||
|
if (currentValue === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// we avoid trigger ahead since it can cause duplicate triggers
|
||||||
|
// we force the timer value to be negative because we need a ceiling reduction
|
||||||
|
const shouldUpdateTimer = millisToSeconds(-currentValue) !== millisToSeconds(-previousValue);
|
||||||
|
return shouldUpdateTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In some cases we want to force an update to the timer
|
||||||
|
* - if the clock has slid back
|
||||||
|
* - if we have escaped the update rate (clock slid forward)
|
||||||
|
*/
|
||||||
|
export function getForceUpdate(previousUpdate: number, now: number): boolean {
|
||||||
|
const isClockBehind = now < previousUpdate;
|
||||||
|
const hasExceededRate = now - previousUpdate >= timerConfig.notificationRate;
|
||||||
|
return isClockBehind || hasExceededRate;
|
||||||
|
}
|
||||||
@@ -168,7 +168,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
load(event2, [event1, event2]);
|
load(event2, [event1, event2]);
|
||||||
start();
|
start();
|
||||||
newState = getState();
|
newState = getState();
|
||||||
expect(newState.runtime.actualStart).toBe(firstStart);
|
expect(newState.runtime.actualStart).toBeCloseTo(firstStart, 0);
|
||||||
// we are over-under, the difference between the schedule and the actual start
|
// we are over-under, the difference between the schedule and the actual start
|
||||||
const delayBefore = event2.timeStart - newState.clock;
|
const delayBefore = event2.timeStart - newState.clock;
|
||||||
expect(newState.runtime.offset).toBe(delayBefore);
|
expect(newState.runtime.offset).toBe(delayBefore);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const initialRuntime: Runtime = {
|
|||||||
plannedEnd: 0,
|
plannedEnd: 0,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
expectedEnd: null,
|
expectedEnd: null,
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
const initialTimer: TimerState = {
|
const initialTimer: TimerState = {
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
@@ -35,7 +35,7 @@ const initialTimer: TimerState = {
|
|||||||
playback: Playback.Stop,
|
playback: Playback.Stop,
|
||||||
secondaryTimer: null,
|
secondaryTimer: null,
|
||||||
startedAt: null,
|
startedAt: null,
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export type RuntimeState = {
|
export type RuntimeState = {
|
||||||
clock: number; // realtime clock
|
clock: number; // realtime clock
|
||||||
@@ -59,7 +59,7 @@ const runtimeState: RuntimeState = {
|
|||||||
publicEventNow: null,
|
publicEventNow: null,
|
||||||
eventNext: null,
|
eventNext: null,
|
||||||
publicEventNext: null,
|
publicEventNext: null,
|
||||||
runtime: initialRuntime,
|
runtime: { ...initialRuntime },
|
||||||
timer: { ...initialTimer },
|
timer: { ...initialTimer },
|
||||||
_timer: {
|
_timer: {
|
||||||
totalDelay: 0,
|
totalDelay: 0,
|
||||||
@@ -78,12 +78,10 @@ export function clear() {
|
|||||||
runtimeState.eventNext = null;
|
runtimeState.eventNext = null;
|
||||||
runtimeState.publicEventNext = null;
|
runtimeState.publicEventNext = null;
|
||||||
|
|
||||||
runtimeState.runtime = {
|
runtimeState.runtime.offset = null;
|
||||||
...initialRuntime,
|
runtimeState.runtime.actualStart = null;
|
||||||
// persist session stuff
|
runtimeState.runtime.expectedEnd = null;
|
||||||
actualStart: runtimeState.runtime.actualStart,
|
runtimeState.runtime.selectedEventIndex = null;
|
||||||
numEvents: runtimeState.runtime.numEvents,
|
|
||||||
};
|
|
||||||
|
|
||||||
runtimeState.timer.playback = Playback.Stop;
|
runtimeState.timer.playback = Playback.Stop;
|
||||||
runtimeState.clock = clock.timeNow();
|
runtimeState.clock = clock.timeNow();
|
||||||
@@ -123,7 +121,7 @@ export function updateRundownData(rundownData: RundownData) {
|
|||||||
|
|
||||||
runtimeState.runtime.numEvents = rundownData.numEvents;
|
runtimeState.runtime.numEvents = rundownData.numEvents;
|
||||||
runtimeState.runtime.plannedStart = rundownData.firstStart;
|
runtimeState.runtime.plannedStart = rundownData.firstStart;
|
||||||
runtimeState.runtime.plannedEnd = rundownData.firstStart + rundownData.totalDuration;
|
runtimeState.runtime.plannedEnd = rundownData.lastEnd;
|
||||||
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ export function ensureDirectory(directory: string): void {
|
|||||||
/**
|
/**
|
||||||
* Ensures that a filename ends with .json extension
|
* Ensures that a filename ends with .json extension
|
||||||
*/
|
*/
|
||||||
export function ensureJsonExtension(filename: string): string {
|
export function ensureJsonExtension(filename: string | undefined): string | undefined {
|
||||||
if (!filename) return filename;
|
if (!filename) return filename;
|
||||||
|
return filename.endsWith('.json') ? filename : `${filename}.json`;
|
||||||
return filename.includes('.json') ? filename : `${filename}.json`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-2
@@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "3.0.0",
|
"version": "3.0.2",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"lightdev",
|
|
||||||
"ontime",
|
"ontime",
|
||||||
"timer",
|
"timer",
|
||||||
"rundown"
|
"rundown"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const MILLIS_PER_MINUTE = 1000 * 60;
|
|||||||
export const MILLIS_PER_HOUR = 1000 * 60 * 60;
|
export const MILLIS_PER_HOUR = 1000 * 60 * 60;
|
||||||
|
|
||||||
function convertMillis(millis: MaybeNumber, conversion: number) {
|
function convertMillis(millis: MaybeNumber, conversion: number) {
|
||||||
if (millis == null || millis === 0) {
|
if (!millis) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+210
-65
@@ -146,7 +146,7 @@ importers:
|
|||||||
version: 13.4.0(react-dom@18.2.0)(react@18.2.0)
|
version: 13.4.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@testing-library/user-event':
|
'@testing-library/user-event':
|
||||||
specifier: ^14.1.1
|
specifier: ^14.1.1
|
||||||
version: 14.4.3(@testing-library/dom@9.3.4)
|
version: 14.4.3(@testing-library/dom@10.1.0)
|
||||||
'@types/color':
|
'@types/color':
|
||||||
specifier: ^3.0.3
|
specifier: ^3.0.3
|
||||||
version: 3.0.3
|
version: 3.0.3
|
||||||
@@ -261,15 +261,12 @@ importers:
|
|||||||
express:
|
express:
|
||||||
specifier: ^4.18.2
|
specifier: ^4.18.2
|
||||||
version: 4.18.2
|
version: 4.18.2
|
||||||
express-session:
|
|
||||||
specifier: ^1.17.3
|
|
||||||
version: 1.17.3
|
|
||||||
express-static-gzip:
|
express-static-gzip:
|
||||||
specifier: ^2.1.7
|
specifier: ^2.1.7
|
||||||
version: 2.1.7
|
version: 2.1.7
|
||||||
express-validator:
|
express-validator:
|
||||||
specifier: ^6.14.2
|
specifier: ^7.0.1
|
||||||
version: 6.14.2
|
version: 7.0.1
|
||||||
fast-equals:
|
fast-equals:
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1
|
version: 5.0.1
|
||||||
@@ -345,7 +342,7 @@ importers:
|
|||||||
version: 8.53.0
|
version: 8.53.0
|
||||||
eslint-plugin-prettier:
|
eslint-plugin-prettier:
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.0.3)
|
version: 5.0.1(eslint@8.53.0)(prettier@3.0.3)
|
||||||
nodemon:
|
nodemon:
|
||||||
specifier: ^2.0.20
|
specifier: ^2.0.20
|
||||||
version: 2.0.20
|
version: 2.0.20
|
||||||
@@ -369,7 +366,7 @@ importers:
|
|||||||
version: 5.4.3
|
version: 5.4.3
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^1.2.2
|
specifier: ^1.2.2
|
||||||
version: 1.2.2(@types/node@18.11.18)(jsdom@21.1.0)(sass@1.57.1)
|
version: 1.2.2(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/types:
|
packages/types:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
@@ -463,6 +460,14 @@ packages:
|
|||||||
'@babel/highlight': 7.23.4
|
'@babel/highlight': 7.23.4
|
||||||
chalk: 2.4.2
|
chalk: 2.4.2
|
||||||
|
|
||||||
|
/@babel/code-frame@7.24.2:
|
||||||
|
resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
dependencies:
|
||||||
|
'@babel/highlight': 7.24.5
|
||||||
|
picocolors: 1.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@babel/compat-data@7.23.5:
|
/@babel/compat-data@7.23.5:
|
||||||
resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==}
|
resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -624,6 +629,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
|
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
/@babel/helper-validator-identifier@7.24.5:
|
||||||
|
resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@babel/helper-validator-option@7.23.5:
|
/@babel/helper-validator-option@7.23.5:
|
||||||
resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
|
resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -648,6 +658,16 @@ packages:
|
|||||||
chalk: 2.4.2
|
chalk: 2.4.2
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
|
/@babel/highlight@7.24.5:
|
||||||
|
resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
dependencies:
|
||||||
|
'@babel/helper-validator-identifier': 7.24.5
|
||||||
|
chalk: 2.4.2
|
||||||
|
js-tokens: 4.0.0
|
||||||
|
picocolors: 1.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@babel/parser@7.23.6:
|
/@babel/parser@7.23.6:
|
||||||
resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==}
|
resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
@@ -697,8 +717,8 @@ packages:
|
|||||||
regenerator-runtime: 0.13.11
|
regenerator-runtime: 0.13.11
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@babel/runtime@7.24.0:
|
/@babel/runtime@7.24.5:
|
||||||
resolution: {integrity: sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==}
|
resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.14.1
|
regenerator-runtime: 0.14.1
|
||||||
@@ -3059,6 +3079,20 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@testing-library/dom@10.1.0:
|
||||||
|
resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
dependencies:
|
||||||
|
'@babel/code-frame': 7.24.2
|
||||||
|
'@babel/runtime': 7.24.5
|
||||||
|
'@types/aria-query': 5.0.4
|
||||||
|
aria-query: 5.3.0
|
||||||
|
chalk: 4.1.2
|
||||||
|
dom-accessibility-api: 0.5.16
|
||||||
|
lz-string: 1.5.0
|
||||||
|
pretty-format: 27.5.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@testing-library/dom@8.19.1:
|
/@testing-library/dom@8.19.1:
|
||||||
resolution: {integrity: sha512-P6iIPyYQ+qH8CvGauAqanhVnjrnRe0IZFSYCeGkSRW9q3u8bdVn2NPI+lasFyVsEQn1J/IFmp5Aax41+dAP9wg==}
|
resolution: {integrity: sha512-P6iIPyYQ+qH8CvGauAqanhVnjrnRe0IZFSYCeGkSRW9q3u8bdVn2NPI+lasFyVsEQn1J/IFmp5Aax41+dAP9wg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -3073,20 +3107,6 @@ packages:
|
|||||||
pretty-format: 27.5.1
|
pretty-format: 27.5.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@testing-library/dom@9.3.4:
|
|
||||||
resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==}
|
|
||||||
engines: {node: '>=14'}
|
|
||||||
dependencies:
|
|
||||||
'@babel/code-frame': 7.23.5
|
|
||||||
'@babel/runtime': 7.24.0
|
|
||||||
'@types/aria-query': 5.0.4
|
|
||||||
aria-query: 5.1.3
|
|
||||||
chalk: 4.1.2
|
|
||||||
dom-accessibility-api: 0.5.16
|
|
||||||
lz-string: 1.5.0
|
|
||||||
pretty-format: 27.5.1
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@testing-library/jest-dom@5.16.5:
|
/@testing-library/jest-dom@5.16.5:
|
||||||
resolution: {integrity: sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==}
|
resolution: {integrity: sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==}
|
||||||
engines: {node: '>=8', npm: '>=6', yarn: '>=1'}
|
engines: {node: '>=8', npm: '>=6', yarn: '>=1'}
|
||||||
@@ -3116,13 +3136,13 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@testing-library/user-event@14.4.3(@testing-library/dom@9.3.4):
|
/@testing-library/user-event@14.4.3(@testing-library/dom@10.1.0):
|
||||||
resolution: {integrity: sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==}
|
resolution: {integrity: sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==}
|
||||||
engines: {node: '>=12', npm: '>=6'}
|
engines: {node: '>=12', npm: '>=6'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@testing-library/dom': '>=7.21.4'
|
'@testing-library/dom': '>=7.21.4'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@testing-library/dom': 9.3.4
|
'@testing-library/dom': 10.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@tootallnate/once@2.0.0:
|
/@tootallnate/once@2.0.0:
|
||||||
@@ -3975,7 +3995,7 @@ packages:
|
|||||||
lodash.isplainobject: 4.0.6
|
lodash.isplainobject: 4.0.6
|
||||||
lodash.union: 4.6.0
|
lodash.union: 4.6.0
|
||||||
normalize-path: 3.0.0
|
normalize-path: 3.0.0
|
||||||
readable-stream: 2.3.7
|
readable-stream: 2.3.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/archiver-utils@3.0.4:
|
/archiver-utils@3.0.4:
|
||||||
@@ -4053,6 +4073,12 @@ packages:
|
|||||||
deep-equal: 2.2.0
|
deep-equal: 2.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/aria-query@5.3.0:
|
||||||
|
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
||||||
|
dependencies:
|
||||||
|
dequal: 2.0.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/array-flatten@1.1.1:
|
/array-flatten@1.1.1:
|
||||||
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
|
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -4655,11 +4681,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/cookie@0.4.2:
|
|
||||||
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
|
|
||||||
engines: {node: '>= 0.6'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/cookie@0.5.0:
|
/cookie@0.5.0:
|
||||||
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -4955,6 +4976,11 @@ packages:
|
|||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/dequal@2.0.3:
|
||||||
|
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/destroy@1.2.0:
|
/destroy@1.2.0:
|
||||||
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
|
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
|
||||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||||
@@ -5426,6 +5452,26 @@ packages:
|
|||||||
synckit: 0.8.5
|
synckit: 0.8.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/eslint-plugin-prettier@5.0.1(eslint@8.53.0)(prettier@3.0.3):
|
||||||
|
resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==}
|
||||||
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/eslint': '>=8.0.0'
|
||||||
|
eslint: '>=8.0.0'
|
||||||
|
eslint-config-prettier: '*'
|
||||||
|
prettier: '>=3.0.0'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/eslint':
|
||||||
|
optional: true
|
||||||
|
eslint-config-prettier:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
eslint: 8.53.0
|
||||||
|
prettier: 3.0.3
|
||||||
|
prettier-linter-helpers: 1.0.0
|
||||||
|
synckit: 0.8.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
/eslint-plugin-react-hooks@4.6.0(eslint@8.53.0):
|
/eslint-plugin-react-hooks@4.6.0(eslint@8.53.0):
|
||||||
resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
|
resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -5682,22 +5728,6 @@ packages:
|
|||||||
jest-util: 29.3.1
|
jest-util: 29.3.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/express-session@1.17.3:
|
|
||||||
resolution: {integrity: sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==}
|
|
||||||
engines: {node: '>= 0.8.0'}
|
|
||||||
dependencies:
|
|
||||||
cookie: 0.4.2
|
|
||||||
cookie-signature: 1.0.6
|
|
||||||
debug: 2.6.9
|
|
||||||
depd: 2.0.0
|
|
||||||
on-headers: 1.0.2
|
|
||||||
parseurl: 1.3.3
|
|
||||||
safe-buffer: 5.2.1
|
|
||||||
uid-safe: 2.1.5
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/express-static-gzip@2.1.7:
|
/express-static-gzip@2.1.7:
|
||||||
resolution: {integrity: sha512-QOCZUC+lhPPCjIJKpQGu1Oa61Axg9Mq09Qvit8Of7kzpMuwDeMSqjjQteQS3OVw/GkENBoSBheuQDWPlngImvw==}
|
resolution: {integrity: sha512-QOCZUC+lhPPCjIJKpQGu1Oa61Axg9Mq09Qvit8Of7kzpMuwDeMSqjjQteQS3OVw/GkENBoSBheuQDWPlngImvw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5706,12 +5736,12 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/express-validator@6.14.2:
|
/express-validator@7.0.1:
|
||||||
resolution: {integrity: sha512-8XfAUrQ6Y7dIIuy9KcUPCfG/uCbvREctrxf5EeeME+ulanJ4iiW71lWmm9r4YcKKYOCBMan0WpVg7FtHu4Z4Wg==}
|
resolution: {integrity: sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==}
|
||||||
engines: {node: '>= 8.0.0'}
|
engines: {node: '>= 8.0.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
validator: 13.7.0
|
validator: 13.12.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/express@4.18.2:
|
/express@4.18.2:
|
||||||
@@ -7577,6 +7607,7 @@ packages:
|
|||||||
/on-headers@1.0.2:
|
/on-headers@1.0.2:
|
||||||
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
|
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/once@1.4.0:
|
/once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
@@ -7960,11 +7991,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
|
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
/random-bytes@1.0.0:
|
|
||||||
resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==}
|
|
||||||
engines: {node: '>= 0.8'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/range-parser@1.2.1:
|
/range-parser@1.2.1:
|
||||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -8178,6 +8204,18 @@ packages:
|
|||||||
string_decoder: 1.1.1
|
string_decoder: 1.1.1
|
||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
|
/readable-stream@2.3.8:
|
||||||
|
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||||
|
dependencies:
|
||||||
|
core-util-is: 1.0.3
|
||||||
|
inherits: 2.0.4
|
||||||
|
isarray: 1.0.0
|
||||||
|
process-nextick-args: 2.0.1
|
||||||
|
safe-buffer: 5.1.2
|
||||||
|
string_decoder: 1.1.1
|
||||||
|
util-deprecate: 1.0.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/readable-stream@3.6.2:
|
/readable-stream@3.6.2:
|
||||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -9179,13 +9217,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==}
|
resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/uid-safe@2.1.5:
|
|
||||||
resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==}
|
|
||||||
engines: {node: '>= 0.8'}
|
|
||||||
dependencies:
|
|
||||||
random-bytes: 1.0.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/unbox-primitive@1.0.2:
|
/unbox-primitive@1.0.2:
|
||||||
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
|
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9325,8 +9356,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/validator@13.7.0:
|
/validator@13.12.0:
|
||||||
resolution: {integrity: sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==}
|
resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -9346,6 +9377,27 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/vite-node@1.2.2(@types/node@18.11.18):
|
||||||
|
resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
cac: 6.7.14
|
||||||
|
debug: 4.3.4
|
||||||
|
pathe: 1.1.1
|
||||||
|
picocolors: 1.0.0
|
||||||
|
vite: 5.1.0(@types/node@18.11.18)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vite-node@1.2.2(@types/node@18.11.18)(sass@1.57.1):
|
/vite-node@1.2.2(@types/node@18.11.18)(sass@1.57.1):
|
||||||
resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==}
|
resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
@@ -9408,6 +9460,42 @@ packages:
|
|||||||
- typescript
|
- typescript
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/vite@5.1.0(@types/node@18.11.18):
|
||||||
|
resolution: {integrity: sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': ^18.0.0 || >=20.0.0
|
||||||
|
less: '*'
|
||||||
|
lightningcss: ^1.21.0
|
||||||
|
sass: '*'
|
||||||
|
stylus: '*'
|
||||||
|
sugarss: '*'
|
||||||
|
terser: ^5.4.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
less:
|
||||||
|
optional: true
|
||||||
|
lightningcss:
|
||||||
|
optional: true
|
||||||
|
sass:
|
||||||
|
optional: true
|
||||||
|
stylus:
|
||||||
|
optional: true
|
||||||
|
sugarss:
|
||||||
|
optional: true
|
||||||
|
terser:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 18.11.18
|
||||||
|
esbuild: 0.19.10
|
||||||
|
postcss: 8.4.35
|
||||||
|
rollup: 4.9.1
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vite@5.1.0(@types/node@18.11.18)(sass@1.57.1):
|
/vite@5.1.0(@types/node@18.11.18)(sass@1.57.1):
|
||||||
resolution: {integrity: sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==}
|
resolution: {integrity: sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
@@ -9445,6 +9533,63 @@ packages:
|
|||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/vitest@1.2.2(@types/node@18.11.18):
|
||||||
|
resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@edge-runtime/vm': '*'
|
||||||
|
'@types/node': ^18.0.0 || >=20.0.0
|
||||||
|
'@vitest/browser': ^1.0.0
|
||||||
|
'@vitest/ui': ^1.0.0
|
||||||
|
happy-dom: '*'
|
||||||
|
jsdom: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@edge-runtime/vm':
|
||||||
|
optional: true
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
'@vitest/browser':
|
||||||
|
optional: true
|
||||||
|
'@vitest/ui':
|
||||||
|
optional: true
|
||||||
|
happy-dom:
|
||||||
|
optional: true
|
||||||
|
jsdom:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 18.11.18
|
||||||
|
'@vitest/expect': 1.2.2
|
||||||
|
'@vitest/runner': 1.2.2
|
||||||
|
'@vitest/snapshot': 1.2.2
|
||||||
|
'@vitest/spy': 1.2.2
|
||||||
|
'@vitest/utils': 1.2.2
|
||||||
|
acorn-walk: 8.3.2
|
||||||
|
cac: 6.7.14
|
||||||
|
chai: 4.3.10
|
||||||
|
debug: 4.3.4
|
||||||
|
execa: 8.0.1
|
||||||
|
local-pkg: 0.5.0
|
||||||
|
magic-string: 0.30.5
|
||||||
|
pathe: 1.1.1
|
||||||
|
picocolors: 1.0.0
|
||||||
|
std-env: 3.6.0
|
||||||
|
strip-literal: 1.3.0
|
||||||
|
tinybench: 2.5.1
|
||||||
|
tinypool: 0.8.2
|
||||||
|
vite: 5.1.0(@types/node@18.11.18)
|
||||||
|
vite-node: 1.2.2(@types/node@18.11.18)
|
||||||
|
why-is-node-running: 2.2.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vitest@1.2.2(@types/node@18.11.18)(jsdom@21.1.0)(sass@1.57.1):
|
/vitest@1.2.2(@types/node@18.11.18)(jsdom@21.1.0)(sass@1.57.1):
|
||||||
resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==}
|
resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
|||||||
+6
-5
@@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"app": "ontime",
|
"app": "ontime",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0-beta.3",
|
||||||
"serverPort": 4001,
|
"serverPort": 4001,
|
||||||
"editorKey": null,
|
"editorKey": null,
|
||||||
"operatorKey": null,
|
"operatorKey": null,
|
||||||
@@ -18,11 +18,12 @@
|
|||||||
"language": "en"
|
"language": "en"
|
||||||
},
|
},
|
||||||
"viewSettings": {
|
"viewSettings": {
|
||||||
"overrideStyles": false,
|
|
||||||
"normalColor": "#ffffffcc",
|
|
||||||
"warningColor": "#FFAB33",
|
|
||||||
"dangerColor": "#ED3333",
|
"dangerColor": "#ED3333",
|
||||||
"endMessage": ""
|
"endMessage": "",
|
||||||
|
"freezeEnd": false,
|
||||||
|
"normalColor": "#ffffffcc",
|
||||||
|
"overrideStyles": false,
|
||||||
|
"warningColor": "#FFAB33"
|
||||||
},
|
},
|
||||||
"urlPresets": [],
|
"urlPresets": [],
|
||||||
"customFields": {},
|
"customFields": {},
|
||||||
|
|||||||
Reference in New Issue
Block a user