mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 15:09:10 +00:00
v2:feat (#231)
* style: element relationship * feat: utility to add first element * refactor: organise dir * ux: deleting element closes drawer * fix: issue with escaped characters * fix: issue with overflow in events list * fix: prevent event timer from receiving non event * style: prevent small shift on class change * style: visually detach event editor * fix: entry block knows last event * fix: skip event timer updates on non events * fix: prevent electron OPENGL error * style: cleanup text styles * style: unify input styles * refactor: simplify style and composition * refactor: tweaks on style + ts * refactor: add validation to events post * fix: handle non events in event timer * refactor: cleanup unused
This commit is contained in:
+12
-10
@@ -6,19 +6,25 @@ import {
|
|||||||
AlertDialogFooter,
|
AlertDialogFooter,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogOverlay,
|
AlertDialogOverlay,
|
||||||
Button, IconButton, Tooltip,
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { FiPower } from '@react-icons/all-files/fi/FiPower';
|
import { FiPower } from '@react-icons/all-files/fi/FiPower';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { LoggingContext } from '../../context/LoggingContext';
|
import { LoggingContext } from '../../context/LoggingContext';
|
||||||
|
import { Size } from '../../models/UtilTypes';
|
||||||
|
|
||||||
export default function QuitIconBtn(props) {
|
interface QuitIconBtnProps {
|
||||||
|
clickHandler: () => void;
|
||||||
|
size?: Size;
|
||||||
|
}
|
||||||
|
export default function QuitIconBtn(props: QuitIconBtnProps) {
|
||||||
const { clickHandler, size = 'lg', ...rest } = props;
|
const { clickHandler, size = 'lg', ...rest } = props;
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const { emitInfo } = useContext(LoggingContext);
|
const { emitInfo } = useContext(LoggingContext);
|
||||||
const onClose = () => setIsOpen(false);
|
const onClose = () => setIsOpen(false);
|
||||||
const cancelRef = useRef();
|
const cancelRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.process?.type === 'renderer') {
|
if (window.process?.type === 'renderer') {
|
||||||
@@ -38,6 +44,7 @@ export default function QuitIconBtn(props) {
|
|||||||
<>
|
<>
|
||||||
<Tooltip label='Quit Application'>
|
<Tooltip label='Quit Application'>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
aria-label='Quit Application'
|
||||||
size={size}
|
size={size}
|
||||||
icon={<FiPower />}
|
icon={<FiPower />}
|
||||||
colorScheme='red'
|
colorScheme='red'
|
||||||
@@ -58,7 +65,7 @@ export default function QuitIconBtn(props) {
|
|||||||
This will shutdown the program and all running servers. Are you sure?
|
This will shutdown the program and all running servers. Are you sure?
|
||||||
</AlertDialogBody>
|
</AlertDialogBody>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<Button ref={cancelRef} onClick={onClose}>
|
<Button ref={cancelRef} onClick={onClose} variant='ghost'>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button colorScheme='red' onClick={handleShutdown} ml={3}>
|
<Button colorScheme='red' onClick={handleShutdown} ml={3}>
|
||||||
@@ -71,8 +78,3 @@ export default function QuitIconBtn(props) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
QuitIconBtn.propTypes = {
|
|
||||||
clickHandler: PropTypes.func,
|
|
||||||
size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']),
|
|
||||||
};
|
|
||||||
@@ -133,9 +133,10 @@ export default function TimeInput(props) {
|
|||||||
size='sm'
|
size='sm'
|
||||||
icon={<IoLink style={{ transform: 'rotate(-45deg)' }} />}
|
icon={<IoLink style={{ transform: 'rotate(-45deg)' }} />}
|
||||||
aria-label='automate'
|
aria-label='automate'
|
||||||
colorScheme='whiteAlpha'
|
colorScheme='blue'
|
||||||
style={{ borderRadius: '2px', width: 'min-content' }}
|
style={{ borderRadius: '2px', width: 'min-content' }}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
|
variant='ghost'
|
||||||
/>
|
/>
|
||||||
</InputLeftElement>
|
</InputLeftElement>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useSocket } from './socketContext';
|
import { useSocket } from '../context/socketContext';
|
||||||
|
|
||||||
export default function useSubscription<T>(topic: string, initialState: T, requestString?: string) {
|
export default function useSubscription<T>(topic: string, initialState: T, requestString?: string) {
|
||||||
const socket = useSocket();
|
const socket = useSocket();
|
||||||
+3
@@ -3,10 +3,13 @@ declare module '*.scss' {
|
|||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListenerType = (event: 'string', args: unknown[]) => void;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
ipcRenderer: {
|
ipcRenderer: {
|
||||||
send: (channel: string, args?: string | object) => void;
|
send: (channel: string, args?: string | object) => void;
|
||||||
|
on: (channel: string, listener: ListenerType) => void;
|
||||||
};
|
};
|
||||||
process: {
|
process: {
|
||||||
type: string;
|
type: string;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { Input } from '@chakra-ui/react';
|
||||||
import { Editable, EditableInput, EditablePreview, IconButton, Tooltip } from '@chakra-ui/react';
|
|
||||||
import { IoSunny } from '@react-icons/all-files/io5/IoSunny';
|
import { IoSunny } from '@react-icons/all-files/io5/IoSunny';
|
||||||
|
|
||||||
|
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||||
|
|
||||||
import style from './MessageControl.module.scss';
|
import style from './MessageControl.module.scss';
|
||||||
@@ -17,44 +17,32 @@ interface InputRowProps {
|
|||||||
|
|
||||||
export default function InputRow(props: InputRowProps) {
|
export default function InputRow(props: InputRowProps) {
|
||||||
const { label, placeholder, text, visible, actionHandler, changeHandler } = props;
|
const { label, placeholder, text, visible, actionHandler, changeHandler } = props;
|
||||||
const [inputText, setInputText] = useState<string>(text || '');
|
|
||||||
|
|
||||||
const handleInputChange = (newValue: string) => {
|
const handleInputChange = (newValue: string) => {
|
||||||
setInputText(newValue);
|
|
||||||
changeHandler(newValue);
|
changeHandler(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (text) {
|
|
||||||
setInputText(text);
|
|
||||||
}
|
|
||||||
}, [text]);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${visible ? style.inputRowActive : ''}`}>
|
<div className={`${visible ? style.inputRowActive : ''}`}>
|
||||||
<span className={style.label}>{label}</span>
|
<span className={style.label}>{label}</span>
|
||||||
<div className={style.inputItems}>
|
<div className={style.inputItems}>
|
||||||
<Editable
|
<Input
|
||||||
onChange={(newValue) => handleInputChange(newValue)}
|
size='sm'
|
||||||
value={inputText}
|
variant='filled'
|
||||||
|
value={text}
|
||||||
|
onChange={(event) => handleInputChange(event.target.value)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className={style.inline}
|
/>
|
||||||
color={text === '' ? '#505050' : 'inherit'}
|
<TooltipActionBtn
|
||||||
>
|
clickHandler={() => actionHandler('update', { field: 'isPublic', value: !visible })}
|
||||||
<EditablePreview className={`${style.padleft} ${style.fullWidth}`} />
|
tooltip={visible ? 'Make invisible' : 'Make visible'}
|
||||||
<EditableInput className={style.padleft} />
|
aria-label='Toggle tooltip visibility'
|
||||||
</Editable>
|
openDelay={tooltipDelayMid}
|
||||||
<Tooltip label={visible ? 'Make invisible' : 'Make visible'} openDelay={tooltipDelayMid}>
|
icon={<IoSunny size='18px' />}
|
||||||
<IconButton
|
colorScheme='blue'
|
||||||
aria-label='Toggle visibility'
|
variant={visible ? 'solid' : 'outline'}
|
||||||
size='sm'
|
size='sm'
|
||||||
icon={<IoSunny size='18px' />}
|
/>
|
||||||
colorScheme='blue'
|
|
||||||
variant={visible ? 'solid' : 'outline'}
|
|
||||||
onClick={() => actionHandler('update', { field: 'isPublic', value: !visible })}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,30 +24,10 @@
|
|||||||
color: $label-gray;
|
color: $label-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline {
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: $input-bg;
|
|
||||||
border: $input-border;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.padleft {
|
|
||||||
padding-left: 0.5em;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fullWidth {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputRowActive {
|
.inputRowActive {
|
||||||
.label {
|
.label {
|
||||||
color: $action-blue;
|
color: $action-blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline {
|
|
||||||
border-color: $action-blue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
.eventEditor {
|
.eventEditor {
|
||||||
border-radius: 3px 3px 0 0;
|
border-radius: 3px 3px 0 0;
|
||||||
background-color: $bg-container-l1;
|
background-color: $bg-container-l1;
|
||||||
border-top: $border-l1;
|
border-top: 1px solid $bg-gray-900;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@@ -160,10 +160,10 @@
|
|||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
grid-area: even;
|
grid-area: even;
|
||||||
height: calc(100% - 3em);
|
height: calc(100% - 24px);
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
height: calc(100% - 3em);
|
height: calc(100% - 24px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: calc(100% - 3em);
|
height: calc(100% - 24px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ $clearance: 8px;
|
|||||||
$block-border-radius: 3px;
|
$block-border-radius: 3px;
|
||||||
|
|
||||||
@mixin block-spacing() {
|
@mixin block-spacing() {
|
||||||
margin: 4px 1px;
|
|
||||||
padding: 4px 10px 4px 2px;
|
padding: 4px 10px 4px 2px;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
@use '../../../theme/main' as *;
|
@use '../../../theme/main' as *;
|
||||||
|
|
||||||
@mixin when-visible() {
|
.create {
|
||||||
opacity: 1;
|
background-color: $bg-container-l2;
|
||||||
height: calc(2.5em + 20px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 5%;
|
gap: 5%;
|
||||||
margin: 2px 0;
|
margin: 4px 2px 2px;
|
||||||
}
|
|
||||||
|
|
||||||
.create {
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
position: relative;
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
.createEvent,
|
.createEvent,
|
||||||
.createDelay,
|
.createDelay,
|
||||||
.createBlock {
|
.createBlock {
|
||||||
@@ -32,69 +22,63 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.keyboard {
|
|
||||||
margin-left: 4px;
|
|
||||||
padding: 0 4px;
|
|
||||||
color: #ccc;
|
|
||||||
border-radius: 2px;
|
|
||||||
font-family: Monospaced, sans-serif;
|
|
||||||
background-color: rgba(0, 0, 0, 0.24);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.createEvent {
|
.createEvent {
|
||||||
border: 1px solid #2b6cb0;
|
border: 1px solid $light-bg;
|
||||||
color: lighten(#2b6cb0, 30%);
|
color: lighten($light-bg, 30%);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #2b6cb0;
|
background-color: $light-bg;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.createDelay {
|
.createDelay {
|
||||||
border: 1px solid #ecc94b;;
|
border: 1px solid $block-delay-color;
|
||||||
color: lighten(#ecc94b, 30%);
|
color: lighten($block-delay-color, 30%);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #ecc94b;
|
background-color: $block-delay-color;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.createBlock {
|
.createBlock {
|
||||||
border: 1px solid #805ad5;
|
border: 1px solid $block-block-color;
|
||||||
color: lighten(#805ad5, 30%);
|
color: lighten($block-block-color, 30%);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #805ad5;
|
background-color: $block-block-color;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.options {
|
.keyboard {
|
||||||
display: flex;
|
margin-left: 4px;
|
||||||
flex-direction: column;
|
padding: 0 4px;
|
||||||
|
color: $label-gray;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
.options {
|
||||||
opacity: 0.65;
|
display: flex;
|
||||||
transition: opacity 0.15s;
|
flex-direction: column;
|
||||||
|
|
||||||
&:hover {
|
label {
|
||||||
opacity: 1;
|
opacity: 0.65;
|
||||||
}
|
transition: opacity 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled {
|
|
||||||
border-color: $text-gray-disabled;
|
|
||||||
color: $text-gray-disabled;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.create.visible {
|
.disabled {
|
||||||
@include when-visible;
|
border-color: $text-gray-disabled;
|
||||||
}
|
color: $text-gray-disabled;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
import { useCallback, useContext, useRef } from 'react';
|
||||||
import { Checkbox, Tooltip } from '@chakra-ui/react';
|
import { Checkbox, Tooltip } from '@chakra-ui/react';
|
||||||
import { defaultPublicAtom, startTimeIsLastEndAtom } from 'common/atoms/LocalEventSettings';
|
import { defaultPublicAtom, startTimeIsLastEndAtom } from 'common/atoms/LocalEventSettings';
|
||||||
import { LoggingContext } from 'common/context/LoggingContext';
|
import { LoggingContext } from 'common/context/LoggingContext';
|
||||||
@@ -13,7 +13,7 @@ import style from './EntryBlock.module.scss';
|
|||||||
interface EntryBlockProps {
|
interface EntryBlockProps {
|
||||||
showKbd: boolean;
|
showKbd: boolean;
|
||||||
previousId?: string;
|
previousId?: string;
|
||||||
visible?: boolean;
|
previousEventId: string | null;
|
||||||
disableAddDelay?: boolean;
|
disableAddDelay?: boolean;
|
||||||
disableAddBlock: boolean;
|
disableAddBlock: boolean;
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ export default function EntryBlock(props: EntryBlockProps) {
|
|||||||
const {
|
const {
|
||||||
showKbd,
|
showKbd,
|
||||||
previousId,
|
previousId,
|
||||||
visible = true,
|
previousEventId,
|
||||||
disableAddDelay = true,
|
disableAddDelay = true,
|
||||||
disableAddBlock,
|
disableAddBlock,
|
||||||
} = props;
|
} = props;
|
||||||
@@ -30,14 +30,17 @@ export default function EntryBlock(props: EntryBlockProps) {
|
|||||||
const { emitError } = useContext(LoggingContext);
|
const { emitError } = useContext(LoggingContext);
|
||||||
const startTimeIsLastEnd = useAtomValue(startTimeIsLastEndAtom);
|
const startTimeIsLastEnd = useAtomValue(startTimeIsLastEndAtom);
|
||||||
const defaultPublic = useAtomValue(defaultPublicAtom);
|
const defaultPublic = useAtomValue(defaultPublicAtom);
|
||||||
const [doStartTime, setStartTime] = useState(startTimeIsLastEnd);
|
const doStartTime = useRef<HTMLInputElement | null>(null);
|
||||||
const [doPublic, setPublic] = useState(defaultPublic);
|
const doPublic = useRef<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
const handleCreateEvent = useCallback((eventType: EventTypes) => {
|
const handleCreateEvent = useCallback((eventType: EventTypes) => {
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case 'event': {
|
case 'event': {
|
||||||
const newEvent = { type: 'event', after: previousId, isPublic: doPublic };
|
const isPublicOption = doPublic?.current?.checked || defaultPublic;
|
||||||
const options = { startIsLastEnd: doStartTime ? previousId : undefined };
|
const startTimeIsLastEndOption = doStartTime?.current?.checked || doStartTime;
|
||||||
|
|
||||||
|
const newEvent = { type: 'event', after: previousId, isPublic: isPublicOption };
|
||||||
|
const options = { startIsLastEnd: startTimeIsLastEndOption ? previousEventId : undefined };
|
||||||
addEvent(newEvent, options);
|
addEvent(newEvent, options);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -55,18 +58,10 @@ export default function EntryBlock(props: EntryBlockProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [addEvent, doPublic, doStartTime, emitError, previousId]);
|
}, [addEvent, doPublic, doStartTime, emitError, previousId, previousEventId]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setStartTime(startTimeIsLastEnd);
|
|
||||||
}, [startTimeIsLastEnd]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setPublic(defaultPublic);
|
|
||||||
}, [defaultPublic]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${style.create} ${visible ? style.visible : ''}`}>
|
<div className={style.create}>
|
||||||
<Tooltip label='Add Event' openDelay={tooltipDelayMid}>
|
<Tooltip label='Add Event' openDelay={tooltipDelayMid}>
|
||||||
<span
|
<span
|
||||||
className={style.createEvent}
|
className={style.createEvent}
|
||||||
@@ -96,18 +91,18 @@ export default function EntryBlock(props: EntryBlockProps) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div className={style.options}>
|
<div className={style.options}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
ref={doStartTime}
|
||||||
size='sm'
|
size='sm'
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
isChecked={doStartTime}
|
defaultChecked={startTimeIsLastEnd}
|
||||||
onChange={(e) => setStartTime(e.target.checked)}
|
|
||||||
>
|
>
|
||||||
Start time is last end
|
Start time is last end
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
ref={doPublic}
|
||||||
size='sm'
|
size='sm'
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
isChecked={doPublic}
|
defaultChecked={defaultPublic}
|
||||||
onChange={(e) => setPublic(e.target.checked)}
|
|
||||||
>
|
>
|
||||||
Event is public
|
Event is public
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
grid-template-columns: $binder-width auto 1fr auto;
|
grid-template-columns: $binder-width auto 1fr auto;
|
||||||
grid-template-rows: 36px 36px 36px $element-spacing;
|
grid-template-rows: 36px 36px 36px $element-spacing;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 2px 1px;
|
margin: 2px 2px;
|
||||||
padding-right: $clearance;
|
padding-right: $clearance;
|
||||||
|
|
||||||
// style - general
|
// style - general
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export default function EventBlockTimers(props) {
|
|||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(field, value) => {
|
(field, value) => {
|
||||||
console.log('called update', field, value)
|
|
||||||
actionHandler('update', { field, value });
|
actionHandler('update', { field, value });
|
||||||
},
|
},
|
||||||
[actionHandler]
|
[actionHandler]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { duplicateEvent } from 'common/utils/eventsManager';
|
|||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import useSubscription from '../../../common/context/useSubscription';
|
import useSubscription from '../../../common/hooks/useSubscription';
|
||||||
import EntryBlock from '../entry-block/EntryBlock';
|
import EntryBlock from '../entry-block/EntryBlock';
|
||||||
|
|
||||||
import EventListItem from './EventListItem';
|
import EventListItem from './EventListItem';
|
||||||
@@ -170,7 +170,13 @@ export default function EventList(props) {
|
|||||||
return (
|
return (
|
||||||
<div className={style.alignCenter}>
|
<div className={style.alignCenter}>
|
||||||
<Empty text='No Events' style={{ marginTop: '7vh' }} />
|
<Empty text='No Events' style={{ marginTop: '7vh' }} />
|
||||||
<Button variant='solid' colorScheme='blue'>Create Event</Button>
|
<Button
|
||||||
|
onClick={() => insertAtCursor('event', cursor)}
|
||||||
|
variant='solid'
|
||||||
|
colorScheme='blue'
|
||||||
|
>
|
||||||
|
Create Event
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -178,6 +184,7 @@ export default function EventList(props) {
|
|||||||
let eventIndex = -1;
|
let eventIndex = -1;
|
||||||
let previousEnd = 0;
|
let previousEnd = 0;
|
||||||
let thisEnd = 0;
|
let thisEnd = 0;
|
||||||
|
let previousEventId = null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.eventContainer}>
|
<div className={style.eventContainer}>
|
||||||
@@ -198,6 +205,7 @@ export default function EventList(props) {
|
|||||||
eventIndex++;
|
eventIndex++;
|
||||||
previousEnd = thisEnd;
|
previousEnd = thisEnd;
|
||||||
thisEnd = e.timeEnd;
|
thisEnd = e.timeEnd;
|
||||||
|
previousEventId = e.id;
|
||||||
}
|
}
|
||||||
const isLast = index === events.length - 1;
|
const isLast = index === events.length - 1;
|
||||||
return (
|
return (
|
||||||
@@ -226,6 +234,7 @@ export default function EventList(props) {
|
|||||||
<EntryBlock
|
<EntryBlock
|
||||||
showKbd={index === cursor}
|
showKbd={index === cursor}
|
||||||
previousId={e.id}
|
previousId={e.id}
|
||||||
|
previousEventId={previousEventId}
|
||||||
disableAddDelay={e.type === 'delay'}
|
disableAddDelay={e.type === 'delay'}
|
||||||
disableAddBlock={e.type === 'block'}
|
disableAddBlock={e.type === 'block'}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { useCallback, useContext } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { defaultPublicAtom, startTimeIsLastEndAtom } from 'common/atoms/LocalEventSettings';
|
import {
|
||||||
|
defaultPublicAtom,
|
||||||
|
editorEventId,
|
||||||
|
startTimeIsLastEndAtom,
|
||||||
|
} from 'common/atoms/LocalEventSettings';
|
||||||
import { LoggingContext } from 'common/context/LoggingContext';
|
import { LoggingContext } from 'common/context/LoggingContext';
|
||||||
import { useEventAction } from 'common/hooks/useEventAction';
|
import { useEventAction } from 'common/hooks/useEventAction';
|
||||||
import { OntimeEvent, OntimeEventEntry } from 'common/models/EventTypes';
|
import { OntimeEvent, OntimeEventEntry } from 'common/models/EventTypes';
|
||||||
import { Playstate } from 'common/models/OntimeTypes';
|
import { Playstate } from 'common/models/OntimeTypes';
|
||||||
import { duplicateEvent } from 'common/utils/eventsManager';
|
import { duplicateEvent } from 'common/utils/eventsManager';
|
||||||
import { calculateDuration } from 'common/utils/timesManager';
|
import { calculateDuration } from 'common/utils/timesManager';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtom, useAtomValue } from 'jotai';
|
||||||
|
|
||||||
import { CursorContext } from '../../../common/context/CursorContext';
|
import { CursorContext } from '../../../common/context/CursorContext';
|
||||||
import BlockBlock from '../block-block/BlockBlock';
|
import BlockBlock from '../block-block/BlockBlock';
|
||||||
@@ -40,6 +44,7 @@ export default function EventListItem(props: EventListItemProps) {
|
|||||||
const defaultPublic = useAtomValue(defaultPublicAtom);
|
const defaultPublic = useAtomValue(defaultPublicAtom);
|
||||||
const { addEvent, updateEvent, deleteEvent } = useEventAction();
|
const { addEvent, updateEvent, deleteEvent } = useEventAction();
|
||||||
const { moveCursorTo } = useContext(CursorContext);
|
const { moveCursorTo } = useContext(CursorContext);
|
||||||
|
const [openId, setOpenId] = useAtom(editorEventId);
|
||||||
|
|
||||||
// Create / delete new events
|
// Create / delete new events
|
||||||
type FieldValue = {
|
type FieldValue = {
|
||||||
@@ -75,6 +80,9 @@ export default function EventListItem(props: EventListItemProps) {
|
|||||||
}
|
}
|
||||||
case 'delete': {
|
case 'delete': {
|
||||||
deleteEvent(data.id);
|
deleteEvent(data.id);
|
||||||
|
if (openId === data.id) {
|
||||||
|
setOpenId(null);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'clone': {
|
case 'clone': {
|
||||||
|
|||||||
@@ -20,13 +20,15 @@ export default function EventListWrapper() {
|
|||||||
}, [emitError, isError]);
|
}, [emitError, isError]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.content}>
|
<>
|
||||||
<EventListMenu />
|
<EventListMenu />
|
||||||
{status === 'success' && data ? (
|
<div className={styles.content}>
|
||||||
<EventList events={data} />
|
{status === 'success' && data ? (
|
||||||
) : (
|
<EventList events={data} />
|
||||||
<Empty text='Connecting to server' />
|
) : (
|
||||||
)}
|
<Empty text='Connecting to server' />
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,10 +145,6 @@ export default function EventEditor() {
|
|||||||
<label className={style.inputLabel}>Title</label>
|
<label className={style.inputLabel}>Title</label>
|
||||||
<TextInput field='title' initialText={event.title} submitHandler={handleSubmit} />
|
<TextInput field='title' initialText={event.title} submitHandler={handleSubmit} />
|
||||||
</div>
|
</div>
|
||||||
<div className={style.column}>
|
|
||||||
<label className={style.inputLabel}>Subtitle</label>
|
|
||||||
<TextInput field='subtitle' initialText={event.subtitle} submitHandler={handleSubmit} />
|
|
||||||
</div>
|
|
||||||
<div className={style.column}>
|
<div className={style.column}>
|
||||||
<label className={style.inputLabel}>Presenter</label>
|
<label className={style.inputLabel}>Presenter</label>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -157,6 +153,10 @@ export default function EventEditor() {
|
|||||||
submitHandler={handleSubmit}
|
submitHandler={handleSubmit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={style.column}>
|
||||||
|
<label className={style.inputLabel}>Subtitle</label>
|
||||||
|
<TextInput field='subtitle' initialText={event.subtitle} submitHandler={handleSubmit} />
|
||||||
|
</div>
|
||||||
<div className={style.padTop}>
|
<div className={style.padTop}>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<FiUsers />}
|
leftIcon={<FiUsers />}
|
||||||
@@ -190,7 +190,7 @@ export default function EventEditor() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.column}>
|
<div className={style.column}>
|
||||||
<label className={style.inputLabel}>Notes</label>
|
<label className={style.inputLabel}>Note</label>
|
||||||
<TextInput
|
<TextInput
|
||||||
field='note'
|
field='note'
|
||||||
initialText={event.note}
|
initialText={event.note}
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ export default function Info() {
|
|||||||
|
|
||||||
const selected = !data.numEvents
|
const selected = !data.numEvents
|
||||||
? 'No events'
|
? 'No events'
|
||||||
: `Event ${data.selectedEventIndex != null ? data.selectedEventIndex + 1 : '-'}/${
|
: `Event ${data.selectedEventIndex != null ? data.selectedEventIndex + 1 : '-'} / ${
|
||||||
data.numEvents ? data.numEvents : '-'
|
data.numEvents ? data.numEvents : '-'
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={style.main}>
|
<div className={style.main}>
|
||||||
<span>Running on port 4001</span>
|
<span>Ontime running on port 4001</span>
|
||||||
<span>{selected}</span>
|
<span>{selected}</span>
|
||||||
</div>
|
</div>
|
||||||
<InfoNif />
|
<InfoNif />
|
||||||
|
|||||||
@@ -9,13 +9,12 @@
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
@include container;
|
@include container;
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: $ontime-pink;
|
color: $label-gray;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
align-content: center;
|
align-content: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
padding-top: 24px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { useSocket } from '../../common/context/socketContext';
|
import { useSocket } from '../../common/context/socketContext';
|
||||||
import useSubscription from '../../common/context/useSubscription';
|
import useSubscription from '../../common/hooks/useSubscription';
|
||||||
import useEvent from '../../common/hooks-query/useEvent';
|
import useEvent from '../../common/hooks-query/useEvent';
|
||||||
import useEventsList from '../../common/hooks-query/useEventsList';
|
import useEventsList from '../../common/hooks-query/useEventsList';
|
||||||
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
||||||
|
|||||||
@@ -110,6 +110,13 @@
|
|||||||
transition: 0.5s;
|
transition: 0.5s;
|
||||||
transition-property: opacity;
|
transition-property: opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
font-size: 1.5vw;
|
||||||
|
line-height: 2vw;
|
||||||
|
white-space: pre-line;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.clock-container {
|
.clock-container {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ let tray = null;
|
|||||||
|
|
||||||
// Ensure there isn't another instance of the app running already
|
// Ensure there isn't another instance of the app running already
|
||||||
const lock = app.requestSingleInstanceLock();
|
const lock = app.requestSingleInstanceLock();
|
||||||
|
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
dialog.showErrorBox('Multiple instances', 'An instance if the App is already running.');
|
dialog.showErrorBox('Multiple instances', 'An instance if the App is already running.');
|
||||||
app.quit();
|
app.quit();
|
||||||
@@ -111,6 +112,7 @@ function createWindow() {
|
|||||||
win.setMenu(null);
|
win.setMenu(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.disableHardwareAcceleration();
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
// Set app title in windows
|
// Set app title in windows
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
|
|||||||
+1
-1
@@ -141,7 +141,7 @@ export const startServer = async (overrideConfig = null) => {
|
|||||||
|
|
||||||
// init timer
|
// init timer
|
||||||
global.timer = new EventTimer(socket, config.timer, oscConfig, http);
|
global.timer = new EventTimer(socket, config.timer, oscConfig, http);
|
||||||
global.timer.setupWithEventList(events);
|
global.timer.setupWithEventList(events.filter((entry) => entry.type === 'event'));
|
||||||
|
|
||||||
socket.info('SERVER', returnMessage);
|
socket.info('SERVER', returnMessage);
|
||||||
socket.startListener();
|
socket.startListener();
|
||||||
|
|||||||
@@ -95,7 +95,9 @@ export class DataProvider {
|
|||||||
*/
|
*/
|
||||||
static async insertEventAfterId(entry, id) {
|
static async insertEventAfterId(entry, id) {
|
||||||
const index = [...data.events].findIndex((event) => event.id === id);
|
const index = [...data.events].findIndex((event) => event.id === id);
|
||||||
await DataProvider.insertEventAt(entry, index + 1);
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const { _after, ...sanitisedEvent } = entry;
|
||||||
|
await DataProvider.insertEventAt(sanitisedEvent, index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getSettings() {
|
static getSettings() {
|
||||||
|
|||||||
@@ -772,40 +772,45 @@ export class EventTimer extends Timer {
|
|||||||
* @param previousId
|
* @param previousId
|
||||||
*/
|
*/
|
||||||
insertEventAfterId(event, previousId) {
|
insertEventAfterId(event, previousId) {
|
||||||
// find object in events
|
if (typeof previousId === 'undefined') {
|
||||||
const previousIndex = this._eventlist.findIndex((e) => e.id === previousId);
|
// Insert at beginning
|
||||||
if (previousIndex === -1) {
|
this._eventlist.unshift(event);
|
||||||
throw 'Event not found';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previousIndex + 1 >= this._eventlist.length) {
|
|
||||||
this._eventlist.push(event);
|
|
||||||
} else {
|
} else {
|
||||||
this._eventlist.splice(previousIndex + 1, 0, event);
|
// find object in events
|
||||||
}
|
const previousIndex = this._eventlist.findIndex((e) => e.id === previousId);
|
||||||
|
if (previousIndex === -1) {
|
||||||
|
throw 'Event not found';
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
if (previousIndex + 1 >= this._eventlist.length) {
|
||||||
// check if entry is running
|
this._eventlist.push(event);
|
||||||
if (event.id === this.selectedEventId) {
|
} else {
|
||||||
// handle reload selected
|
this._eventlist.splice(previousIndex + 1, 0, event);
|
||||||
// Reload data if running
|
}
|
||||||
const type =
|
|
||||||
this.selectedEventId === event.id && this._startedAt != null ? 'reload' : 'load';
|
try {
|
||||||
this.loadEvent(this.selectedEventIndex, type);
|
// check if entry is running
|
||||||
} else if (event.id === this.nextEventId) {
|
if (event.id === this.selectedEventId) {
|
||||||
// roll needs to recalculate
|
// handle reload selected
|
||||||
if (this.state === 'roll') {
|
// Reload data if running
|
||||||
this.rollLoad();
|
const type =
|
||||||
|
this.selectedEventId === event.id && this._startedAt != null ? 'reload' : 'load';
|
||||||
|
this.loadEvent(this.selectedEventIndex, type);
|
||||||
|
} else if (event.id === this.nextEventId) {
|
||||||
|
// roll needs to recalculate
|
||||||
|
if (this.state === 'roll') {
|
||||||
|
this.rollLoad();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// load titles
|
// load titles
|
||||||
if ('title' in event || 'subtitle' in event || 'presenter' in event) {
|
if ('title' in event || 'subtitle' in event || 'presenter' in event) {
|
||||||
this._loadTitlesNext();
|
this._loadTitlesNext();
|
||||||
this._loadTitlesNow();
|
this._loadTitlesNow();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.socket.error('SERVER', error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
this.socket.error('SERVER', error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update clients
|
// update clients
|
||||||
@@ -815,21 +820,6 @@ export class EventTimer extends Timer {
|
|||||||
this.runCycle();
|
this.runCycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description inserts an event in the first position of the list
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
insertEventAtStart(event) {
|
|
||||||
// Insert at beginning
|
|
||||||
this._eventlist.unshift(event);
|
|
||||||
|
|
||||||
// update clients
|
|
||||||
this.broadcastState();
|
|
||||||
|
|
||||||
// run cycle
|
|
||||||
this.runCycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deleted an event from the list by its id
|
* Deleted an event from the list by its id
|
||||||
* @param {string} eventId
|
* @param {string} eventId
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { body, validationResult } from 'express-validator';
|
import { body, validationResult } from 'express-validator';
|
||||||
|
|
||||||
export const eventSanitizer = [
|
export const eventSanitizer = [
|
||||||
body('title').optional().isString().trim().escape(),
|
body('title').optional().isString().trim(),
|
||||||
body('url').optional().isString().trim().escape(),
|
body('url').optional().isString().trim(),
|
||||||
body('publicInfo').optional().isString().trim().escape(),
|
body('publicInfo').optional().isString().trim(),
|
||||||
body('backstageInfo').optional().isString().trim().escape(),
|
body('backstageInfo').optional().isString().trim(),
|
||||||
body('endMessage').optional().isString().trim().escape(),
|
body('endMessage').optional().isString().trim(),
|
||||||
(req, res, next) => {
|
(req, res, next) => {
|
||||||
const errors = validationResult(req);
|
const errors = validationResult(req);
|
||||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||||
|
|||||||
@@ -14,17 +14,20 @@ import { socketProvider } from '../classes/socket/SocketController.js';
|
|||||||
const socket = socketProvider;
|
const socket = socketProvider;
|
||||||
|
|
||||||
async function _insertAndSync(newEvent) {
|
async function _insertAndSync(newEvent) {
|
||||||
if (newEvent.order) {
|
const afterId = newEvent?.after;
|
||||||
const events = DataProvider.getEvents();
|
if (typeof afterId === 'undefined') {
|
||||||
await DataProvider.insertEventAt(newEvent, newEvent.order);
|
|
||||||
const previousId = events?.[newEvent.order - 1]?.id;
|
|
||||||
_insertEventInTimerAfterId(newEvent, previousId);
|
|
||||||
} else if (newEvent.after) {
|
|
||||||
await DataProvider.insertEventAfterId(newEvent, newEvent.after);
|
|
||||||
_insertEventInTimerAfterId(newEvent, newEvent.after);
|
|
||||||
} else {
|
|
||||||
await DataProvider.insertEventAt(newEvent, 0);
|
await DataProvider.insertEventAt(newEvent, 0);
|
||||||
_insertEventInTimerAfterId(newEvent);
|
if (newEvent.type === 'event') {
|
||||||
|
_insertEventInTimerAfterId(newEvent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete newEvent.after;
|
||||||
|
await DataProvider.insertEventAfterId(newEvent, afterId);
|
||||||
|
if (newEvent.type === 'event') {
|
||||||
|
const events = DataProvider.getEvents();
|
||||||
|
const { id } = getPreviousPlayable(events, newEvent.id);
|
||||||
|
_insertEventInTimerAfterId(newEvent, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,14 +54,10 @@ function _updateTimers() {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function _insertEventInTimerAfterId(event, previousId) {
|
function _insertEventInTimerAfterId(event, previousId) {
|
||||||
if (typeof previousId === 'undefined') {
|
try {
|
||||||
global.timer.insertEventAtStart(event);
|
global.timer.insertEventAfterId(event, previousId);
|
||||||
} else {
|
} catch (error) {
|
||||||
try {
|
socket.error('SERVER', `Unable to update object: ${error}`);
|
||||||
global.timer.insertEventAfterId(event, previousId);
|
|
||||||
} catch (error) {
|
|
||||||
socket.error('SERVER', `Unable to update object: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,28 +146,29 @@ export const eventsPut = async (req, res) => {
|
|||||||
|
|
||||||
const eventDataFromRequest = req.body;
|
const eventDataFromRequest = req.body;
|
||||||
const eventId = eventDataFromRequest.id;
|
const eventId = eventDataFromRequest.id;
|
||||||
const event = DataProvider.getEventById(eventId);
|
const eventInMemory = DataProvider.getEventById(eventId);
|
||||||
|
|
||||||
if (typeof event === 'undefined') {
|
if (typeof eventInMemory === 'undefined') {
|
||||||
res.status(400).send(`No event with ID found`);
|
res.status(400).send(`No event with ID found`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newData = await DataProvider.updateEventById(eventId, eventDataFromRequest);
|
const patchedObject = await DataProvider.updateEventById(eventId, eventDataFromRequest);
|
||||||
|
|
||||||
if (newData.skip) {
|
if (patchedObject.type === 'event') {
|
||||||
_deleteTimerId(eventId);
|
if (patchedObject.skip) {
|
||||||
// if it is a skip, make sure it is deleted from timer
|
// if it is a skip, make sure it is deleted from timer
|
||||||
// event id might already not exist
|
_deleteTimerId(patchedObject.id);
|
||||||
} else {
|
} else {
|
||||||
try {
|
if (eventInMemory.skip) {
|
||||||
_updateTimersSingle(newData.id, eventDataFromRequest);
|
// if it was skipped before we add it to the timer
|
||||||
} catch (error) {
|
|
||||||
if (error === 'Event not found') {
|
|
||||||
const events = DataProvider.getEvents();
|
const events = DataProvider.getEvents();
|
||||||
const { id: previousId } = getPreviousPlayable(events, newData.id);
|
const { id } = getPreviousPlayable(events, patchedObject.id);
|
||||||
_insertEventInTimerAfterId(newData, previousId);
|
_insertEventInTimerAfterId(patchedObject, id);
|
||||||
|
} else {
|
||||||
|
// otherwise update as normal
|
||||||
|
_updateTimersSingle(patchedObject.id, patchedObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import { body, param, validationResult } from 'express-validator';
|
import { body, param, validationResult } from 'express-validator';
|
||||||
|
|
||||||
|
export const eventsPostValidator = [
|
||||||
|
body('type').isString().exists().isIn(['event', 'delay', 'block']),
|
||||||
|
(req, res, next) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const eventsPutValidator = [
|
export const eventsPutValidator = [
|
||||||
body('id').isString().exists(),
|
body('id').isString().exists(),
|
||||||
(req, res, next) => {
|
(req, res, next) => {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const uploadAndParse = async (file, req, res, options) => {
|
|||||||
} else {
|
} else {
|
||||||
await DataProvider.mergeIntoData(result.data);
|
await DataProvider.mergeIntoData(result.data);
|
||||||
}
|
}
|
||||||
global.timer.setupWithEventList(newEvents);
|
global.timer.setupWithEventList(newEvents.filter((entry) => entry.type === 'event'));
|
||||||
}
|
}
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
export const router = express.Router();
|
|
||||||
|
|
||||||
// import events controller
|
// import events controller
|
||||||
import {
|
import {
|
||||||
|
eventsApplyDelay,
|
||||||
|
eventsDelete,
|
||||||
|
eventsDeleteAll,
|
||||||
eventsGetAll,
|
eventsGetAll,
|
||||||
eventsGetById,
|
eventsGetById,
|
||||||
|
eventsPatch,
|
||||||
eventsPost,
|
eventsPost,
|
||||||
eventsPut,
|
eventsPut,
|
||||||
eventsPatch,
|
|
||||||
eventsReorder,
|
eventsReorder,
|
||||||
eventsApplyDelay,
|
|
||||||
eventsDeleteAll,
|
|
||||||
eventsDelete,
|
|
||||||
} from '../controllers/eventsController.js';
|
} from '../controllers/eventsController.js';
|
||||||
import {
|
import {
|
||||||
|
eventsPostValidator,
|
||||||
eventsPutValidator,
|
eventsPutValidator,
|
||||||
paramsMustHaveEventId,
|
paramsMustHaveEventId,
|
||||||
} from '../controllers/eventsController.validate.js';
|
} from '../controllers/eventsController.validate.js';
|
||||||
|
|
||||||
|
export const router = express.Router();
|
||||||
|
|
||||||
// create route between controller and '/events/' endpoint
|
// create route between controller and '/events/' endpoint
|
||||||
router.get('/', eventsGetAll);
|
router.get('/', eventsGetAll);
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ router.get('/', eventsGetAll);
|
|||||||
router.get('/:eventId', eventsGetById);
|
router.get('/:eventId', eventsGetById);
|
||||||
|
|
||||||
// create route between controller and '/events/' endpoint
|
// create route between controller and '/events/' endpoint
|
||||||
router.post('/', eventsPost);
|
router.post('/', eventsPostValidator, eventsPost);
|
||||||
|
|
||||||
// create route between controller and '/events/' endpoint
|
// create route between controller and '/events/' endpoint
|
||||||
router.put('/', eventsPutValidator, eventsPut);
|
router.put('/', eventsPutValidator, eventsPut);
|
||||||
|
|||||||
Reference in New Issue
Block a user