mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-08 15:59:16 +00:00
Feat/end action (#308)
* feat: end action --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com> Co-authored-by: Fabian Posenau <19673098+kellhogs@users.noreply.github.com>
This commit is contained in:
@@ -30,7 +30,6 @@
|
|||||||
"react-qr-code": "^2.0.11",
|
"react-qr-code": "^2.0.11",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"react-table": "^7.7.0",
|
"react-table": "^7.7.0",
|
||||||
"react-use-websocket": "^4.3.1",
|
|
||||||
"typeface-open-sans": "^1.1.13",
|
"typeface-open-sans": "^1.1.13",
|
||||||
"web-vitals": "^3.1.1",
|
"web-vitals": "^3.1.1",
|
||||||
"zustand": "^4.3.6"
|
"zustand": "^4.3.6"
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ export const RUNTIME = ['runtimeStore'];
|
|||||||
// external stuff
|
// external stuff
|
||||||
export const githubURL = 'https://api.github.com/repos/cpvalente/ontime/releases/latest';
|
export const githubURL = 'https://api.github.com/repos/cpvalente/ontime/releases/latest';
|
||||||
|
|
||||||
// external stuff
|
|
||||||
export const githubURL = 'https://api.github.com/repos/cpvalente/ontime/releases/latest';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description finds server path given the current location, it
|
* @description finds server path given the current location, it
|
||||||
* @return {*}
|
* @return {*}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
import { Button, Select, Switch } from '@chakra-ui/react';
|
import { Button, Select, Switch } from '@chakra-ui/react';
|
||||||
import { IoBan } from '@react-icons/all-files/io5/IoBan';
|
import { IoBan } from '@react-icons/all-files/io5/IoBan';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { OntimeEvent, TimerType } from 'ontime-types';
|
import { EndAction, OntimeEvent, TimerType } from 'ontime-types';
|
||||||
import { millisToString } from 'ontime-utils';
|
import { millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
import { editorEventId } from '../../common/atoms/LocalEventSettings';
|
import { editorEventId } from '../../common/atoms/LocalEventSettings';
|
||||||
@@ -169,18 +169,6 @@ export default function EventEditor() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.timeSettings}>
|
<div className={style.timeSettings}>
|
||||||
<label className={style.inputLabel}>Timer Behaviour</label>
|
|
||||||
<Select
|
|
||||||
size='sm'
|
|
||||||
name='timerBehaviour'
|
|
||||||
value={event.timerBehaviour}
|
|
||||||
onChange={(event) => handleChange('timerBehaviour', event.target.value)}
|
|
||||||
>
|
|
||||||
<option value='start-end'>Start to end</option>
|
|
||||||
<option value='duration'>Duration</option>
|
|
||||||
<option value='follow-previous'>Follow previous</option>
|
|
||||||
<option value='start-only'>Start only</option>
|
|
||||||
</Select>
|
|
||||||
<label className={style.inputLabel}>Timer Type</label>
|
<label className={style.inputLabel}>Timer Type</label>
|
||||||
<Select
|
<Select
|
||||||
size='sm'
|
size='sm'
|
||||||
@@ -192,6 +180,18 @@ export default function EventEditor() {
|
|||||||
<option value={TimerType.CountUp}>Count up</option>
|
<option value={TimerType.CountUp}>Count up</option>
|
||||||
<option value={TimerType.Clock}>Clock</option>
|
<option value={TimerType.Clock}>Clock</option>
|
||||||
</Select>
|
</Select>
|
||||||
|
<label className={style.inputLabel}>End Action</label>
|
||||||
|
<Select
|
||||||
|
size='sm'
|
||||||
|
name='endAction'
|
||||||
|
value={event.endAction}
|
||||||
|
onChange={(event) => handleChange('endAction', event.target.value)}
|
||||||
|
>
|
||||||
|
<option value={EndAction.Continue}>Continue</option>
|
||||||
|
<option value={EndAction.Stop}>Stop</option>
|
||||||
|
<option value={EndAction.LoadNext}>Load Next</option>
|
||||||
|
<option value={EndAction.PlayNext}>Play Next</option>
|
||||||
|
</Select>
|
||||||
<span className={style.spacer} />
|
<span className={style.spacer} />
|
||||||
<label className={`${style.inputLabel} ${style.publicToggle}`}>
|
<label className={`${style.inputLabel} ${style.publicToggle}`}>
|
||||||
<Switch isChecked={event.isPublic} onChange={() => togglePublic(event.isPublic)} variant='ontime' />
|
<Switch isChecked={event.isPublic} onChange={() => togglePublic(event.isPublic)} variant='ontime' />
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function dispatchFromAdapter(type: string, payload: unknown, source?: 'os
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'startid': {
|
case 'startid': {
|
||||||
if (!payload) {
|
if (!payload || typeof payload !== 'string') {
|
||||||
throw new Error(`Event ID not recognised: ${payload}`);
|
throw new Error(`Event ID not recognised: ${payload}`);
|
||||||
}
|
}
|
||||||
PlaybackService.startById(payload);
|
PlaybackService.startById(payload);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { OntimeBlock, OntimeDelay, OntimeEvent, SupportedEvent, TimerType } from 'ontime-types';
|
import { EndAction, OntimeBlock, OntimeDelay, OntimeEvent, SupportedEvent, TimerType } from 'ontime-types';
|
||||||
|
|
||||||
export const event: Omit<OntimeEvent, 'id'> = {
|
export const event: Omit<OntimeEvent, 'id'> = {
|
||||||
title: '',
|
title: '',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
presenter: '',
|
presenter: '',
|
||||||
note: '',
|
note: '',
|
||||||
timerBehaviour: 'start-end',
|
endAction: EndAction.Continue,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
timeStart: 0,
|
timeStart: 0,
|
||||||
timeEnd: 0,
|
timeEnd: 0,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Playback } from 'ontime-types';
|
import { OntimeEvent, Playback } from 'ontime-types';
|
||||||
|
|
||||||
import { eventLoader, EventLoader } from '../classes/event-loader/EventLoader.js';
|
import { eventLoader, EventLoader } from '../classes/event-loader/EventLoader.js';
|
||||||
import { eventStore } from '../stores/EventStore.js';
|
import { eventStore } from '../stores/EventStore.js';
|
||||||
@@ -13,10 +13,10 @@ import { logger } from '../classes/Logger.js';
|
|||||||
export class PlaybackService {
|
export class PlaybackService {
|
||||||
/**
|
/**
|
||||||
* makes calls for loading and starting given event
|
* makes calls for loading and starting given event
|
||||||
* @param {object} event
|
* @param {OntimeEvent} event
|
||||||
* @return {boolean} success
|
* @return {boolean} success
|
||||||
*/
|
*/
|
||||||
static loadEvent(event) {
|
static loadEvent(event: OntimeEvent): boolean {
|
||||||
let success = false;
|
let success = false;
|
||||||
if (!event) {
|
if (!event) {
|
||||||
logger.error('PLAYBACK', 'No event found');
|
logger.error('PLAYBACK', 'No event found');
|
||||||
@@ -36,7 +36,7 @@ export class PlaybackService {
|
|||||||
* @param {string} eventId
|
* @param {string} eventId
|
||||||
* @return {boolean} success
|
* @return {boolean} success
|
||||||
*/
|
*/
|
||||||
static startById(eventId) {
|
static startById(eventId: string): boolean {
|
||||||
const event = EventLoader.getEventWithId(eventId);
|
const event = EventLoader.getEventWithId(eventId);
|
||||||
const success = PlaybackService.loadEvent(event);
|
const success = PlaybackService.loadEvent(event);
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -51,7 +51,7 @@ export class PlaybackService {
|
|||||||
* @param {number} eventIndex
|
* @param {number} eventIndex
|
||||||
* @return {boolean} success
|
* @return {boolean} success
|
||||||
*/
|
*/
|
||||||
static startByIndex(eventIndex) {
|
static startByIndex(eventIndex: number): boolean {
|
||||||
const event = EventLoader.getEventAtIndex(eventIndex);
|
const event = EventLoader.getEventAtIndex(eventIndex);
|
||||||
const success = PlaybackService.loadEvent(event);
|
const success = PlaybackService.loadEvent(event);
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -66,7 +66,7 @@ export class PlaybackService {
|
|||||||
* @param {string} eventId
|
* @param {string} eventId
|
||||||
* @return {boolean} success
|
* @return {boolean} success
|
||||||
*/
|
*/
|
||||||
static loadById(eventId) {
|
static loadById(eventId: string): boolean {
|
||||||
const event = EventLoader.getEventWithId(eventId);
|
const event = EventLoader.getEventWithId(eventId);
|
||||||
const success = PlaybackService.loadEvent(event);
|
const success = PlaybackService.loadEvent(event);
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -80,7 +80,7 @@ export class PlaybackService {
|
|||||||
* @param {number} eventIndex
|
* @param {number} eventIndex
|
||||||
* @return {boolean} success
|
* @return {boolean} success
|
||||||
*/
|
*/
|
||||||
static loadByIndex(eventIndex) {
|
static loadByIndex(eventIndex: number): boolean {
|
||||||
const event = EventLoader.getEventAtIndex(eventIndex);
|
const event = EventLoader.getEventAtIndex(eventIndex);
|
||||||
const success = PlaybackService.loadEvent(event);
|
const success = PlaybackService.loadEvent(event);
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -104,14 +104,28 @@ export class PlaybackService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads event after currently selected
|
* Loads event after currently selected
|
||||||
|
* @param {string} [fallbackAction] - 'stop', 'pause'
|
||||||
|
* @return {boolean} success
|
||||||
*/
|
*/
|
||||||
static loadNext() {
|
static loadNext(fallbackAction?: 'stop' | 'pause'): boolean {
|
||||||
const nextEvent = eventLoader.findNext();
|
const nextEvent = eventLoader.findNext();
|
||||||
if (nextEvent) {
|
if (nextEvent) {
|
||||||
const success = PlaybackService.loadEvent(nextEvent);
|
const success = PlaybackService.loadEvent(nextEvent);
|
||||||
if (success) {
|
if (success) {
|
||||||
logger.info('PLAYBACK', `Loaded event with ID ${nextEvent.id}`);
|
logger.info('PLAYBACK', `Loaded event with ID ${nextEvent.id}`);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
} else if (fallbackAction === 'stop') {
|
||||||
|
logger.info('PLAYBACK', `No next event found! Stopping playback`);
|
||||||
|
PlaybackService.stop();
|
||||||
|
return false;
|
||||||
|
} else if (fallbackAction === 'pause') {
|
||||||
|
logger.info('PLAYBACK', `No next event found! Pausing playback`);
|
||||||
|
PlaybackService.pause();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
logger.info('PLAYBACK', `No next event found! Continuing playback`);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +140,17 @@ export class PlaybackService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts playback on next event
|
||||||
|
* @param {string} [fallbackAction] - 'stop', 'pause'
|
||||||
|
*/
|
||||||
|
static startNext(fallbackAction?: 'stop' | 'pause') {
|
||||||
|
const success = PlaybackService.loadNext(fallbackAction);
|
||||||
|
if (success) {
|
||||||
|
PlaybackService.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pauses playback on selected event
|
* Pauses playback on selected event
|
||||||
*/
|
*/
|
||||||
@@ -190,7 +215,7 @@ export class PlaybackService {
|
|||||||
* Adds delay to current event
|
* Adds delay to current event
|
||||||
* @param {number} delayTime time in minutes
|
* @param {number} delayTime time in minutes
|
||||||
*/
|
*/
|
||||||
static setDelay(delayTime) {
|
static setDelay(delayTime: number) {
|
||||||
if (eventTimer.loadedTimerId) {
|
if (eventTimer.loadedTimerId) {
|
||||||
const delayInMs = delayTime * 1000 * 60;
|
const delayInMs = delayTime * 1000 * 60;
|
||||||
eventTimer.delay(delayInMs);
|
eventTimer.delay(delayInMs);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Playback, TimerLifeCycle, TimerState } from 'ontime-types';
|
import { EndAction, Playback, TimerLifeCycle, TimerState } from 'ontime-types';
|
||||||
|
|
||||||
import { eventStore } from '../stores/EventStore.js';
|
import { eventStore } from '../stores/EventStore.js';
|
||||||
import { PlaybackService } from './PlaybackService.js';
|
import { PlaybackService } from './PlaybackService.js';
|
||||||
@@ -47,6 +47,7 @@ export class TimerService {
|
|||||||
selectedEventId: null,
|
selectedEventId: null,
|
||||||
duration: null,
|
duration: null,
|
||||||
timerType: null,
|
timerType: null,
|
||||||
|
endAction: null,
|
||||||
};
|
};
|
||||||
this.loadedTimerId = null;
|
this.loadedTimerId = null;
|
||||||
this.pausedTime = 0;
|
this.pausedTime = 0;
|
||||||
@@ -78,6 +79,7 @@ export class TimerService {
|
|||||||
// update relevant information and force update
|
// update relevant information and force update
|
||||||
this.timer.duration = timer.duration;
|
this.timer.duration = timer.duration;
|
||||||
this.timer.timerType = timer.timerType;
|
this.timer.timerType = timer.timerType;
|
||||||
|
this.timer.endAction = timer.endAction;
|
||||||
|
|
||||||
// this might not be ideal
|
// this might not be ideal
|
||||||
this.timer.finishedAt = null;
|
this.timer.finishedAt = null;
|
||||||
@@ -117,6 +119,7 @@ export class TimerService {
|
|||||||
this.timer.current = timer.duration;
|
this.timer.current = timer.duration;
|
||||||
this.playback = Playback.Armed;
|
this.playback = Playback.Armed;
|
||||||
this.timer.timerType = timer.timerType;
|
this.timer.timerType = timer.timerType;
|
||||||
|
this.timer.endAction = timer.endAction;
|
||||||
this.pausedTime = 0;
|
this.pausedTime = 0;
|
||||||
this.pausedAt = 0;
|
this.pausedAt = 0;
|
||||||
|
|
||||||
@@ -210,7 +213,7 @@ export class TimerService {
|
|||||||
* Delays running timer by given amount
|
* Delays running timer by given amount
|
||||||
* @param {number} amount
|
* @param {number} amount
|
||||||
*/
|
*/
|
||||||
delay(amount) {
|
delay(amount: number) {
|
||||||
if (!this.loadedTimerId) {
|
if (!this.loadedTimerId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -289,6 +292,7 @@ export class TimerService {
|
|||||||
this.pausedTime,
|
this.pausedTime,
|
||||||
this.timer.clock,
|
this.timer.clock,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock);
|
this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,6 +307,16 @@ export class TimerService {
|
|||||||
_onFinish() {
|
_onFinish() {
|
||||||
eventStore.set('timer', this.timer);
|
eventStore.set('timer', this.timer);
|
||||||
integrationService.dispatch(TimerLifeCycle.onFinish);
|
integrationService.dispatch(TimerLifeCycle.onFinish);
|
||||||
|
if (this.timer.endAction === EndAction.Stop) {
|
||||||
|
PlaybackService.stop();
|
||||||
|
} else if (this.timer.endAction === EndAction.LoadNext) {
|
||||||
|
// we need to delay here to put this action in the queue stack. otherwise it won't be executed properly
|
||||||
|
setTimeout(() => {
|
||||||
|
PlaybackService.loadNext();
|
||||||
|
}, 0);
|
||||||
|
} else if (this.timer.endAction === EndAction.PlayNext) {
|
||||||
|
PlaybackService.startNext();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
roll(currentEvent, nextEvent, timers) {
|
roll(currentEvent, nextEvent, timers) {
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ export const validateEvent = (eventArgs) => {
|
|||||||
presenter: makeString(e.presenter, d.presenter),
|
presenter: makeString(e.presenter, d.presenter),
|
||||||
timeStart: start,
|
timeStart: start,
|
||||||
timeEnd: end,
|
timeEnd: end,
|
||||||
timerBehaviour: makeString(e.timerBehaviour, d.timerBehaviour),
|
endAction: makeString(e.endAction, d.endAction),
|
||||||
timerType: makeString(e.timerType, d.timerType),
|
timerType: makeString(e.timerType, d.timerType),
|
||||||
duration: validateDuration(start, end),
|
duration: validateDuration(start, end),
|
||||||
isPublic: typeof e.isPublic === 'boolean' ? e.isPublic : d.isPublic,
|
isPublic: typeof e.isPublic === 'boolean' ? e.isPublic : d.isPublic,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export enum EndAction {
|
||||||
|
Continue = 'continue',
|
||||||
|
Stop = 'stop',
|
||||||
|
LoadNext = 'load-next',
|
||||||
|
PlayNext = 'play-next',
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { EndAction } from '../EndAction.type.js';
|
||||||
import { TimerType } from '../TimerType.type.js';
|
import { TimerType } from '../TimerType.type.js';
|
||||||
|
|
||||||
export enum SupportedEvent {
|
export enum SupportedEvent {
|
||||||
@@ -28,7 +29,7 @@ export type OntimeEvent = OntimeBaseEvent & {
|
|||||||
subtitle: string;
|
subtitle: string;
|
||||||
presenter: string;
|
presenter: string;
|
||||||
note: string;
|
note: string;
|
||||||
timerBehaviour: 'start-end',
|
endAction: EndAction,
|
||||||
timerType: TimerType,
|
timerType: TimerType,
|
||||||
timeStart: number;
|
timeStart: number;
|
||||||
timeEnd: number;
|
timeEnd: number;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { TimerType } from '../TimerType.type.js';
|
import { TimerType } from '../TimerType.type.js';
|
||||||
|
import { EndAction } from '../EndAction.type.js';
|
||||||
|
|
||||||
export type TimerState = {
|
export type TimerState = {
|
||||||
clock: number; // realtime clock
|
clock: number; // realtime clock
|
||||||
@@ -12,4 +13,5 @@ export type TimerState = {
|
|||||||
selectedEventId: string | null;
|
selectedEventId: string | null;
|
||||||
duration: number | null;
|
duration: number | null;
|
||||||
timerType: TimerType | null;
|
timerType: TimerType | null;
|
||||||
|
endAction: EndAction | null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Alias } from './definitions/core/Alias.type.js';
|
import { Alias } from './definitions/core/Alias.type.js';
|
||||||
import { DatabaseModel } from './definitions/DataModel.type.js';
|
import { DatabaseModel } from './definitions/DataModel.type.js';
|
||||||
|
import { EndAction } from './definitions/EndAction.type.js';
|
||||||
import { EventData } from './definitions/core/EventData.type.js';
|
import { EventData } from './definitions/core/EventData.type.js';
|
||||||
import { Message } from './definitions/runtime/MessageControl.type.js';
|
import { Message } from './definitions/runtime/MessageControl.type.js';
|
||||||
import {
|
import {
|
||||||
@@ -28,6 +29,7 @@ export type { DatabaseModel };
|
|||||||
|
|
||||||
// ---> Rundown
|
// ---> Rundown
|
||||||
export { TimerType };
|
export { TimerType };
|
||||||
|
export { EndAction };
|
||||||
export { SupportedEvent };
|
export { SupportedEvent };
|
||||||
export type { OntimeBaseEvent, OntimeBlock, OntimeDelay, OntimeEvent };
|
export type { OntimeBaseEvent, OntimeBlock, OntimeDelay, OntimeEvent };
|
||||||
export type { OntimeRundown, OntimeRundownEntry };
|
export type { OntimeRundown, OntimeRundownEntry };
|
||||||
|
|||||||
Generated
-12
@@ -85,7 +85,6 @@ importers:
|
|||||||
react-qr-code: ^2.0.11
|
react-qr-code: ^2.0.11
|
||||||
react-router-dom: ^6.3.0
|
react-router-dom: ^6.3.0
|
||||||
react-table: ^7.7.0
|
react-table: ^7.7.0
|
||||||
react-use-websocket: ^4.3.1
|
|
||||||
sass: ^1.57.1
|
sass: ^1.57.1
|
||||||
stylelint: ^14.16.1
|
stylelint: ^14.16.1
|
||||||
stylelint-config-prettier: ^9.0.4
|
stylelint-config-prettier: ^9.0.4
|
||||||
@@ -125,7 +124,6 @@ importers:
|
|||||||
react-qr-code: 2.0.11_react@18.2.0
|
react-qr-code: 2.0.11_react@18.2.0
|
||||||
react-router-dom: 6.6.2_biqbaboplfbrettd7655fr4n2y
|
react-router-dom: 6.6.2_biqbaboplfbrettd7655fr4n2y
|
||||||
react-table: 7.8.0_react@18.2.0
|
react-table: 7.8.0_react@18.2.0
|
||||||
react-use-websocket: 4.3.1_biqbaboplfbrettd7655fr4n2y
|
|
||||||
typeface-open-sans: 1.1.13
|
typeface-open-sans: 1.1.13
|
||||||
web-vitals: 3.1.1
|
web-vitals: 3.1.1
|
||||||
zustand: 4.3.6_react@18.2.0
|
zustand: 4.3.6_react@18.2.0
|
||||||
@@ -7583,16 +7581,6 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-use-websocket/4.3.1_biqbaboplfbrettd7655fr4n2y:
|
|
||||||
resolution: {integrity: sha512-zHPLWrgcqydJaak2O5V9hiz4q2dwkwqNQqpgFVmSuPxLZdsZlnDs8DVHy3WtHH+A6ms/8aHIyX7+7ulOcrnR0Q==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>= 18.0.0'
|
|
||||||
react-dom: '>= 18.0.0'
|
|
||||||
dependencies:
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0_react@18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react/18.2.0:
|
/react/18.2.0:
|
||||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|||||||
Reference in New Issue
Block a user