mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-22 07:29:08 +00:00
Merge remote-tracking branch 'origin/master' into v3
This commit is contained in:
@@ -44,7 +44,8 @@ jobs:
|
|||||||
- name: Docker Setup Buildx
|
- name: Docker Setup Buildx
|
||||||
uses: docker/setup-buildx-action@v2.5.0
|
uses: docker/setup-buildx-action@v2.5.0
|
||||||
|
|
||||||
- name: Build and push Docker images
|
- name: Build and push stable release
|
||||||
|
if: github.event.release.prerelease == false
|
||||||
uses: docker/build-push-action@v4.0.0
|
uses: docker/build-push-action@v4.0.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
@@ -54,3 +55,14 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/ontime:${{ env.RELEASE_VERSION }} , ${{ secrets.DOCKERHUB_USERNAME }}/ontime:latest
|
tags: ${{ secrets.DOCKERHUB_USERNAME }}/ontime:${{ env.RELEASE_VERSION }} , ${{ secrets.DOCKERHUB_USERNAME }}/ontime:latest
|
||||||
|
|
||||||
|
- name: Build and push pre-release
|
||||||
|
if: github.event.release.prerelease == true
|
||||||
|
uses: docker/build-push-action@v4.0.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
|
||||||
|
# Push is a shorthand for --output=type=registry
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKERHUB_USERNAME }}/ontime:${{ env.RELEASE_VERSION }} , ${{ secrets.DOCKERHUB_USERNAME }}/ontime:nightly
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"build:local": "cross-env NODE_ENV=local vite build",
|
"build:local": "cross-env NODE_ENV=local vite build",
|
||||||
"build:electron": "cross-env NODE_ENV=local vite build",
|
"build:electron": "cross-env NODE_ENV=local vite build",
|
||||||
"build:docker": "vite build",
|
"build:docker": "vite build",
|
||||||
|
"build:localdocker": "cross-env NODE_ENV=local vite build",
|
||||||
"lint": "eslint . --quiet",
|
"lint": "eslint . --quiet",
|
||||||
"lint-staged": "eslint",
|
"lint-staged": "eslint",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
|
|||||||
@@ -14,24 +14,26 @@ interface TimeInputProps {
|
|||||||
time?: number;
|
time?: number;
|
||||||
delay?: number;
|
delay?: number;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
validationHandler: (entry: TimeEntryField, val: number) => boolean;
|
|
||||||
previousEnd?: number;
|
previousEnd?: number;
|
||||||
warning?: string;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ButtonInitial(name: TimeEntryField) {
|
||||||
|
if (name === 'timeStart') return 'S';
|
||||||
|
if (name === 'timeEnd') return 'E';
|
||||||
|
if (name === 'durationOverride') return 'D';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function ButtonTooltip(name: TimeEntryField, tooltip?: string) {
|
||||||
|
if (name === 'timeStart') return `Start${tooltip ? `: ${tooltip}` : ''}`;
|
||||||
|
if (name === 'timeEnd') return `End${tooltip ? `: ${tooltip}` : ''}`;
|
||||||
|
if (name === 'durationOverride') return `Duration${tooltip ? `: ${tooltip}` : ''}`;
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
export default function TimeInput(props: TimeInputProps) {
|
export default function TimeInput(props: TimeInputProps) {
|
||||||
const {
|
const { id, name, submitHandler, time = 0, delay = 0, placeholder, previousEnd = 0, className } = props;
|
||||||
id,
|
|
||||||
name,
|
|
||||||
submitHandler,
|
|
||||||
time = 0,
|
|
||||||
delay = 0,
|
|
||||||
placeholder,
|
|
||||||
validationHandler,
|
|
||||||
previousEnd = 0,
|
|
||||||
className = '',
|
|
||||||
} = props;
|
|
||||||
const { emitError } = useEmitLog();
|
const { emitError } = useEmitLog();
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const [value, setValue] = useState<string>('');
|
const [value, setValue] = useState<string>('');
|
||||||
@@ -87,15 +89,12 @@ export default function TimeInput(props: TimeInputProps) {
|
|||||||
// check if time is different from before
|
// check if time is different from before
|
||||||
if (newValMillis === time) return false;
|
if (newValMillis === time) return false;
|
||||||
|
|
||||||
// validate with parent
|
|
||||||
if (!validationHandler(name, newValMillis)) return false;
|
|
||||||
|
|
||||||
// update entry
|
// update entry
|
||||||
submitHandler(name, newValMillis);
|
submitHandler(name, newValMillis);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
[name, previousEnd, submitHandler, time, validationHandler],
|
[name, previousEnd, submitHandler, time],
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,8 +152,19 @@ export default function TimeInput(props: TimeInputProps) {
|
|||||||
resetValue();
|
resetValue();
|
||||||
}, [resetValue, time]);
|
}, [resetValue, time]);
|
||||||
|
|
||||||
|
const isDelayed = delay !== 0;
|
||||||
|
const inputClasses = cx([style.timeInput, isDelayed ? style.delayed : null]);
|
||||||
|
const buttonClasses = cx([style.inputButton, isDelayed ? style.delayed : null]);
|
||||||
const timeInputClass = className ? className : style.timeInput;
|
const timeInputClass = className ? className : style.timeInput;
|
||||||
|
|
||||||
|
const TooltipLabel = useMemo(() => {
|
||||||
|
return ButtonTooltip(name, '');
|
||||||
|
}, [name]);
|
||||||
|
|
||||||
|
const ButtonText = useMemo(() => {
|
||||||
|
return ButtonInitial(name);
|
||||||
|
}, [name]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
size='sm'
|
size='sm'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { memo, useState } from 'react';
|
import { memo } from 'react';
|
||||||
import { Select, Switch } from '@chakra-ui/react';
|
import { Select, Switch } from '@chakra-ui/react';
|
||||||
import { EndAction, OntimeEvent, TimerType } from 'ontime-types';
|
import { EndAction, OntimeEvent, TimerType } from 'ontime-types';
|
||||||
import { calculateDuration, millisToString } from 'ontime-utils';
|
import { calculateDuration, millisToString } from 'ontime-utils';
|
||||||
@@ -8,7 +8,6 @@ import TimeInputWithButton from '../../../common/components/input/time-input/Tim
|
|||||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||||
import { millisToDelayString } from '../../../common/utils/dateConfig';
|
import { millisToDelayString } from '../../../common/utils/dateConfig';
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
import { TimeEntryField, validateEntry } from '../../../common/utils/timesManager';
|
|
||||||
|
|
||||||
import style from '../EventEditor.module.scss';
|
import style from '../EventEditor.module.scss';
|
||||||
|
|
||||||
@@ -41,13 +40,6 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
|||||||
props;
|
props;
|
||||||
const { updateEvent } = useEventAction();
|
const { updateEvent } = useEventAction();
|
||||||
|
|
||||||
const [warning, setWarnings] = useState({ start: '', end: '', duration: '' });
|
|
||||||
|
|
||||||
const timerValidationHandler = (entry: TimeEntryField, val: number) => {
|
|
||||||
const valid = validateEntry(entry, val, timeStart, timeEnd);
|
|
||||||
setWarnings((prev) => ({ ...prev, ...valid.warnings }));
|
|
||||||
return valid.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (field: TimeActions, value: number | string | boolean) => {
|
const handleSubmit = (field: TimeActions, value: number | string | boolean) => {
|
||||||
const newEventData: Partial<OntimeEvent> = { id: eventId };
|
const newEventData: Partial<OntimeEvent> = { id: eventId };
|
||||||
@@ -99,11 +91,9 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
|||||||
id='timeStart'
|
id='timeStart'
|
||||||
name='timeStart'
|
name='timeStart'
|
||||||
submitHandler={handleSubmit}
|
submitHandler={handleSubmit}
|
||||||
validationHandler={timerValidationHandler}
|
|
||||||
time={timeStart}
|
time={timeStart}
|
||||||
delay={delay}
|
delay={delay}
|
||||||
placeholder='Start'
|
placeholder='Start'
|
||||||
warning={warning.start}
|
|
||||||
/>
|
/>
|
||||||
<label className={inputTimeLabels} htmlFor='timeEnd'>
|
<label className={inputTimeLabels} htmlFor='timeEnd'>
|
||||||
{endLabel}
|
{endLabel}
|
||||||
@@ -112,11 +102,9 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
|||||||
id='timeEnd'
|
id='timeEnd'
|
||||||
name='timeEnd'
|
name='timeEnd'
|
||||||
submitHandler={handleSubmit}
|
submitHandler={handleSubmit}
|
||||||
validationHandler={timerValidationHandler}
|
|
||||||
time={timeEnd}
|
time={timeEnd}
|
||||||
delay={delay}
|
delay={delay}
|
||||||
placeholder='End'
|
placeholder='End'
|
||||||
warning={warning.end}
|
|
||||||
/>
|
/>
|
||||||
<label className={style.inputLabel} htmlFor='durationOverride'>
|
<label className={style.inputLabel} htmlFor='durationOverride'>
|
||||||
Duration
|
Duration
|
||||||
@@ -125,10 +113,8 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
|||||||
id='durationOverride'
|
id='durationOverride'
|
||||||
name='durationOverride'
|
name='durationOverride'
|
||||||
submitHandler={handleSubmit}
|
submitHandler={handleSubmit}
|
||||||
validationHandler={timerValidationHandler}
|
|
||||||
time={duration}
|
time={duration}
|
||||||
placeholder='Duration'
|
placeholder='Duration'
|
||||||
warning={warning.duration}
|
|
||||||
/>
|
/>
|
||||||
<span className={style.spacer} />
|
<span className={style.spacer} />
|
||||||
<label className={`${style.inputLabel} ${style.publicToggle}`}>
|
<label className={`${style.inputLabel} ${style.publicToggle}`}>
|
||||||
|
|||||||
@@ -215,7 +215,9 @@ export default function Rundown(props: RundownProps) {
|
|||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
eventIndex = 0;
|
eventIndex = 0;
|
||||||
}
|
}
|
||||||
|
let isFirstEvent = false;
|
||||||
if (entry.type === SupportedEvent.Event) {
|
if (entry.type === SupportedEvent.Event) {
|
||||||
|
isFirstEvent = eventIndex === 0;
|
||||||
eventIndex++;
|
eventIndex++;
|
||||||
previousEnd = thisEnd;
|
previousEnd = thisEnd;
|
||||||
thisEnd = entry.timeEnd;
|
thisEnd = entry.timeEnd;
|
||||||
@@ -237,6 +239,7 @@ export default function Rundown(props: RundownProps) {
|
|||||||
<RundownEntry
|
<RundownEntry
|
||||||
type={entry.type}
|
type={entry.type}
|
||||||
isPast={isPast}
|
isPast={isPast}
|
||||||
|
isFirstEvent={isFirstEvent}
|
||||||
data={entry}
|
data={entry}
|
||||||
selected={isSelected}
|
selected={isSelected}
|
||||||
hasCursor={hasCursor}
|
hasCursor={hasCursor}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export type EventItemActions = 'set-cursor' | 'event' | 'delay' | 'block' | 'del
|
|||||||
interface RundownEntryProps {
|
interface RundownEntryProps {
|
||||||
type: SupportedEvent;
|
type: SupportedEvent;
|
||||||
isPast: boolean;
|
isPast: boolean;
|
||||||
|
isFirstEvent: boolean;
|
||||||
data: OntimeRundownEntry;
|
data: OntimeRundownEntry;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
hasCursor: boolean;
|
hasCursor: boolean;
|
||||||
@@ -32,8 +33,19 @@ interface RundownEntryProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function RundownEntry(props: RundownEntryProps) {
|
export default function RundownEntry(props: RundownEntryProps) {
|
||||||
const { isPast, data, selected, hasCursor, next, previousEnd, previousEventId, playback, isRolling, disableEdit } =
|
const {
|
||||||
props;
|
isPast,
|
||||||
|
data,
|
||||||
|
selected,
|
||||||
|
hasCursor,
|
||||||
|
next,
|
||||||
|
previousEnd,
|
||||||
|
previousEventId,
|
||||||
|
playback,
|
||||||
|
isRolling,
|
||||||
|
disableEdit,
|
||||||
|
isFirstEvent,
|
||||||
|
} = props;
|
||||||
const { emitError } = useEmitLog();
|
const { emitError } = useEmitLog();
|
||||||
const { addEvent, updateEvent, deleteEvent, swapEvents } = useEventAction();
|
const { addEvent, updateEvent, deleteEvent, swapEvents } = useEventAction();
|
||||||
|
|
||||||
@@ -160,6 +172,7 @@ export default function RundownEntry(props: RundownEntryProps) {
|
|||||||
isRolling={isRolling}
|
isRolling={isRolling}
|
||||||
actionHandler={actionHandler}
|
actionHandler={actionHandler}
|
||||||
disableEdit={disableEdit}
|
disableEdit={disableEdit}
|
||||||
|
isFirstEvent={isFirstEvent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (data.type === SupportedEvent.Block) {
|
} else if (data.type === SupportedEvent.Block) {
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ $skip-opacity: 0.1;
|
|||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"binder ... ... ..."
|
'binder ... ... ...'
|
||||||
"binder pb-actions times actions"
|
'binder pb-actions times actions'
|
||||||
"binder pb-actions title next"
|
'binder pb-actions title next-ind'
|
||||||
"binder pb-actions estatus estatus"
|
'binder pb-actions estatus estatus'
|
||||||
"binder ... ... ...";
|
'binder ... ... ...';
|
||||||
|
|
||||||
grid-template-columns: $block-binder-width auto 1fr auto;
|
grid-template-columns: $block-binder-width auto 1fr auto;
|
||||||
grid-template-rows: 0.25rem 2.25rem 2.25rem 2.25rem 0.25rem;
|
grid-template-rows: 0.25rem 2.25rem 2.25rem 2.25rem 0.25rem;
|
||||||
@@ -53,8 +53,18 @@ $skip-opacity: 0.1;
|
|||||||
outline: 1px solid $block-cursor-color;
|
outline: 1px solid $block-cursor-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* we stop the eventActions from having opacity to fix issue with dropdown drawing order */
|
||||||
&.past:not(.skip) {
|
&.past:not(.skip) {
|
||||||
opacity: 0.6;
|
.delayNote,
|
||||||
|
.statusElements,
|
||||||
|
.eventTitle,
|
||||||
|
.eventNote,
|
||||||
|
.eventTimers,
|
||||||
|
.eventStatus,
|
||||||
|
.playbackActions,
|
||||||
|
.binder {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.skip {
|
&.skip {
|
||||||
@@ -160,8 +170,8 @@ $skip-opacity: 0.1;
|
|||||||
grid-area: estatus;
|
grid-area: estatus;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"notes status"
|
'notes status'
|
||||||
"progb progb";
|
'progb progb';
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
grid-template-rows: auto 0.25rem;
|
grid-template-rows: auto 0.25rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -180,9 +190,8 @@ $skip-opacity: 0.1;
|
|||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.nextTag {
|
.nextTag {
|
||||||
grid-area: next;
|
grid-area: next-ind;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: $orange-500;
|
color: $orange-500;
|
||||||
letter-spacing: 0.03px;
|
letter-spacing: 0.03px;
|
||||||
@@ -190,6 +199,35 @@ $skip-opacity: 0.1;
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.indicators {
|
||||||
|
grid-area: next-ind;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
background-color: $gray-1250;
|
||||||
|
margin: 0.4rem;
|
||||||
|
margin-right: 0;
|
||||||
|
border-radius: 0.7rem;
|
||||||
|
width: 0.7rem;
|
||||||
|
height: 0.7rem;
|
||||||
|
}
|
||||||
|
.indicator.delay {
|
||||||
|
background-color: $ontime-delay;
|
||||||
|
}
|
||||||
|
.indicator.overlap {
|
||||||
|
background-color: $gray-600;
|
||||||
|
}
|
||||||
|
.indicator.spacing {
|
||||||
|
background-color: $gray-600;
|
||||||
|
}
|
||||||
|
.indicator.nextDay {
|
||||||
|
background-color: $gray-600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.eventStatus {
|
.eventStatus {
|
||||||
grid-area: status;
|
grid-area: status;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -198,7 +236,6 @@ $skip-opacity: 0.1;
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
color: var(--status-color-override, $gray-500);
|
color: var(--status-color-override, $gray-500);
|
||||||
|
|
||||||
|
|
||||||
.statusIcon {
|
.statusIcon {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ interface EventBlockProps {
|
|||||||
},
|
},
|
||||||
) => void;
|
) => void;
|
||||||
disableEdit: boolean;
|
disableEdit: boolean;
|
||||||
|
isFirstEvent: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EventBlock(props: EventBlockProps) {
|
export default function EventBlock(props: EventBlockProps) {
|
||||||
@@ -76,6 +77,7 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
isRolling,
|
isRolling,
|
||||||
actionHandler,
|
actionHandler,
|
||||||
disableEdit,
|
disableEdit,
|
||||||
|
isFirstEvent,
|
||||||
} = props;
|
} = props;
|
||||||
const { selectedEventId, setSelectedEventId, clearSelectedEventId } = useEventIdSwapping();
|
const { selectedEventId, setSelectedEventId, clearSelectedEventId } = useEventIdSwapping();
|
||||||
const moveCursorTo = useAppMode((state) => state.setCursor);
|
const moveCursorTo = useAppMode((state) => state.setCursor);
|
||||||
@@ -210,6 +212,7 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
isRolling={isRolling}
|
isRolling={isRolling}
|
||||||
actionHandler={actionHandler}
|
actionHandler={actionHandler}
|
||||||
disableEdit={disableEdit}
|
disableEdit={disableEdit}
|
||||||
|
isFirstEvent={isFirstEvent}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward'
|
|||||||
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
||||||
import { IoTime } from '@react-icons/all-files/io5/IoTime';
|
import { IoTime } from '@react-icons/all-files/io5/IoTime';
|
||||||
import { EndAction, Playback, TimerType } from 'ontime-types';
|
import { EndAction, Playback, TimerType } from 'ontime-types';
|
||||||
|
import { millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||||
import { useAppMode } from '../../../common/stores/appModeStore';
|
import { useAppMode } from '../../../common/stores/appModeStore';
|
||||||
|
import { millisToDelayString } from '../../../common/utils/dateConfig';
|
||||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||||
import EditableBlockTitle from '../common/EditableBlockTitle';
|
import EditableBlockTitle from '../common/EditableBlockTitle';
|
||||||
import { EventItemActions } from '../RundownEntry';
|
import { EventItemActions } from '../RundownEntry';
|
||||||
@@ -53,6 +55,7 @@ interface EventBlockInnerProps {
|
|||||||
isRolling: boolean;
|
isRolling: boolean;
|
||||||
actionHandler: (action: EventItemActions, payload?: any) => void;
|
actionHandler: (action: EventItemActions, payload?: any) => void;
|
||||||
disableEdit: boolean;
|
disableEdit: boolean;
|
||||||
|
isFirstEvent: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventBlockInner = (props: EventBlockInnerProps) => {
|
const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||||
@@ -76,6 +79,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
|||||||
isRolling,
|
isRolling,
|
||||||
actionHandler,
|
actionHandler,
|
||||||
disableEdit,
|
disableEdit,
|
||||||
|
isFirstEvent,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [renderInner, setRenderInner] = useState(false);
|
const [renderInner, setRenderInner] = useState(false);
|
||||||
@@ -103,6 +107,19 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
|||||||
playBtnStyles._hover = {};
|
playBtnStyles._hover = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const delayedStart = Math.max(0, timeStart + delay);
|
||||||
|
const newTime = millisToString(delayedStart);
|
||||||
|
const delayTime = delay !== 0 ? millisToDelayString(delay) : null;
|
||||||
|
|
||||||
|
const overlap = previousEnd - timeStart;
|
||||||
|
const overlapTime = !isFirstEvent
|
||||||
|
? overlap > 0
|
||||||
|
? `Overlapping ${millisToDelayString(overlap)}`
|
||||||
|
: overlap < 0
|
||||||
|
? `Spacing ${millisToDelayString(overlap)}`
|
||||||
|
: null
|
||||||
|
: null;
|
||||||
|
|
||||||
return !renderInner ? null : (
|
return !renderInner ? null : (
|
||||||
<>
|
<>
|
||||||
<EventBlockTimers
|
<EventBlockTimers
|
||||||
@@ -114,10 +131,36 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
|||||||
previousEnd={previousEnd}
|
previousEnd={previousEnd}
|
||||||
/>
|
/>
|
||||||
<EditableBlockTitle title={title} eventId={eventId} placeholder='Event title' className={style.eventTitle} />
|
<EditableBlockTitle title={title} eventId={eventId} placeholder='Event title' className={style.eventTitle} />
|
||||||
{next && (
|
{next ? (
|
||||||
<Tooltip label='Next event' {...tooltipProps}>
|
<Tooltip label='Next event' {...tooltipProps}>
|
||||||
<span className={style.nextTag}>UP NEXT</span>
|
<span className={style.nextTag}>UP NEXT</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<span className={style.indicators}>
|
||||||
|
<Tooltip
|
||||||
|
label={
|
||||||
|
delayTime && (
|
||||||
|
<div>
|
||||||
|
{delayTime}
|
||||||
|
<br />
|
||||||
|
New Time: {newTime}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={`${style.indicator} ${delayTime ? style.delay : ''}`} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label={overlapTime}>
|
||||||
|
<div
|
||||||
|
className={`${style.indicator} ${
|
||||||
|
overlap > 0 ? style.overlap : overlap < 0 && overlapTime !== null ? style.spacing : ''
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label='Start time is later than end'>
|
||||||
|
<div className={`${style.indicator} ${timeStart > timeEnd ? style.nextDay : ''}`} />
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
<EventBlockPlayback
|
<EventBlockPlayback
|
||||||
eventId={eventId}
|
eventId={eventId}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { memo, useCallback, useState } from 'react';
|
import { memo } from 'react';
|
||||||
import { OntimeEvent } from 'ontime-types';
|
import { OntimeEvent } from 'ontime-types';
|
||||||
import { calculateDuration, millisToString } from 'ontime-utils';
|
import { calculateDuration, millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
import TimeInputWithButton from '../../../../common/components/input/time-input/TimeInputWithButton';
|
import TimeInputWithButton from '../../../../common/components/input/time-input/TimeInputWithButton';
|
||||||
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
||||||
import { millisToDelayString } from '../../../../common/utils/dateConfig';
|
import { millisToDelayString } from '../../../../common/utils/dateConfig';
|
||||||
import { TimeEntryField, validateEntry } from '../../../../common/utils/timesManager';
|
|
||||||
|
|
||||||
import style from '../EventBlock.module.scss';
|
import style from '../EventBlock.module.scss';
|
||||||
|
|
||||||
@@ -24,8 +23,6 @@ const EventBlockTimers = (props: EventBlockTimerProps) => {
|
|||||||
const { eventId, timeStart, timeEnd, duration, delay, previousEnd } = props;
|
const { eventId, timeStart, timeEnd, duration, delay, previousEnd } = props;
|
||||||
const { updateEvent } = useEventAction();
|
const { updateEvent } = useEventAction();
|
||||||
|
|
||||||
const [warning, setWarnings] = useState({ start: '', end: '', duration: '' });
|
|
||||||
|
|
||||||
const handleSubmit = (field: TimeActions, value: number) => {
|
const handleSubmit = (field: TimeActions, value: number) => {
|
||||||
const newEventData: Partial<OntimeEvent> = { id: eventId };
|
const newEventData: Partial<OntimeEvent> = { id: eventId };
|
||||||
switch (field) {
|
switch (field) {
|
||||||
@@ -49,21 +46,6 @@ const EventBlockTimers = (props: EventBlockTimerProps) => {
|
|||||||
updateEvent(newEventData);
|
updateEvent(newEventData);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Validates a time input against its pair
|
|
||||||
* @param {string} entry - field to validate: timeStart, timeEnd, durationOverride
|
|
||||||
* @param {number} val - field value
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
const handleValidation = useCallback(
|
|
||||||
(field: TimeEntryField, value: number) => {
|
|
||||||
const valid = validateEntry(field, value, timeStart, timeEnd);
|
|
||||||
setWarnings((prev) => ({ ...prev, ...valid.warnings }));
|
|
||||||
return valid.value;
|
|
||||||
},
|
|
||||||
[timeEnd, timeStart],
|
|
||||||
);
|
|
||||||
|
|
||||||
const delayedStart = Math.max(0, timeStart + delay);
|
const delayedStart = Math.max(0, timeStart + delay);
|
||||||
const newTime = millisToString(delayedStart);
|
const newTime = millisToString(delayedStart);
|
||||||
const delayTime = delay !== 0 ? millisToDelayString(delay) : null;
|
const delayTime = delay !== 0 ? millisToDelayString(delay) : null;
|
||||||
@@ -73,32 +55,26 @@ const EventBlockTimers = (props: EventBlockTimerProps) => {
|
|||||||
<TimeInputWithButton
|
<TimeInputWithButton
|
||||||
name='timeStart'
|
name='timeStart'
|
||||||
submitHandler={handleSubmit}
|
submitHandler={handleSubmit}
|
||||||
validationHandler={handleValidation}
|
|
||||||
time={timeStart}
|
time={timeStart}
|
||||||
delay={delay}
|
delay={delay}
|
||||||
placeholder='Start'
|
placeholder='Start'
|
||||||
previousEnd={previousEnd}
|
previousEnd={previousEnd}
|
||||||
warning={warning.start}
|
|
||||||
/>
|
/>
|
||||||
<TimeInputWithButton
|
<TimeInputWithButton
|
||||||
name='timeEnd'
|
name='timeEnd'
|
||||||
submitHandler={handleSubmit}
|
submitHandler={handleSubmit}
|
||||||
validationHandler={handleValidation}
|
|
||||||
time={timeEnd}
|
time={timeEnd}
|
||||||
delay={delay}
|
delay={delay}
|
||||||
placeholder='End'
|
placeholder='End'
|
||||||
previousEnd={previousEnd}
|
previousEnd={previousEnd}
|
||||||
warning={warning.end}
|
|
||||||
/>
|
/>
|
||||||
<TimeInputWithButton
|
<TimeInputWithButton
|
||||||
name='durationOverride'
|
name='durationOverride'
|
||||||
submitHandler={handleSubmit}
|
submitHandler={handleSubmit}
|
||||||
validationHandler={handleValidation}
|
|
||||||
time={duration}
|
time={duration}
|
||||||
delay={0}
|
delay={0}
|
||||||
placeholder='Duration'
|
placeholder='Duration'
|
||||||
previousEnd={previousEnd}
|
previousEnd={previousEnd}
|
||||||
warning={warning.duration}
|
|
||||||
/>
|
/>
|
||||||
{delayTime && (
|
{delayTime && (
|
||||||
<div className={style.delayNote}>
|
<div className={style.delayNote}>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { TranslationObject } from './en';
|
|||||||
export const langIt: TranslationObject = {
|
export const langIt: TranslationObject = {
|
||||||
'common.end_time': 'Ora di Fine',
|
'common.end_time': 'Ora di Fine',
|
||||||
'common.expected_finish': 'Fine Prevista',
|
'common.expected_finish': 'Fine Prevista',
|
||||||
'common.minutes': 'min',
|
|
||||||
'common.now': 'Adesso',
|
'common.now': 'Adesso',
|
||||||
'common.next': 'Prossimo',
|
'common.next': 'Prossimo',
|
||||||
'common.public_message': 'Messaggio pubblico',
|
'common.public_message': 'Messaggio pubblico',
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"build:electron": "pnpm prebuild && esbuild src/app.ts --log-level=error --platform=node --format=cjs --bundle --minify --outfile=dist/index.cjs",
|
"build:electron": "pnpm prebuild && esbuild src/app.ts --log-level=error --platform=node --format=cjs --bundle --minify --outfile=dist/index.cjs",
|
||||||
"build:local": "pnpm prebuild && esbuild src/index.ts --log-level=error --platform=node --format=cjs --bundle --minify --outfile=dist/index.cjs",
|
"build:local": "pnpm prebuild && esbuild src/index.ts --log-level=error --platform=node --format=cjs --bundle --minify --outfile=dist/index.cjs",
|
||||||
"build:docker": "pnpm prebuild && esbuild src/index.ts --log-level=error --platform=node --format=cjs --minify --bundle --outfile=dist/docker.cjs",
|
"build:docker": "pnpm prebuild && esbuild src/index.ts --log-level=error --platform=node --format=cjs --minify --bundle --outfile=dist/docker.cjs",
|
||||||
|
"build:localdocker": "NODE_ENV=local pnpm prebuild && esbuild src/index.ts --log-level=error --platform=node --format=cjs --minify --bundle --outfile=dist/docker.cjs",
|
||||||
"build:debug": "pnpm prebuild && esbuild src/app.ts --platform=node --format=cjs --bundle --outfile=dist/index.cjs",
|
"build:debug": "pnpm prebuild && esbuild src/app.ts --platform=node --format=cjs --bundle --outfile=dist/index.cjs",
|
||||||
"lint": "eslint . --quiet",
|
"lint": "eslint . --quiet",
|
||||||
"lint-staged": "eslint",
|
"lint-staged": "eslint",
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ version: "3"
|
|||||||
services:
|
services:
|
||||||
ontime:
|
ontime:
|
||||||
container_name: ontime
|
container_name: ontime
|
||||||
image: getontime/ontime:beta_v2
|
image: getontime/ontime:latest
|
||||||
ports:
|
ports:
|
||||||
- "4001:4001/tcp"
|
- "4001:4001/tcp"
|
||||||
- "8888:8888/udp"
|
- "8888:8888/udp"
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"build:local": "turbo run build:local",
|
"build:local": "turbo run build:local",
|
||||||
"build:electron": "turbo run build:electron",
|
"build:electron": "turbo run build:electron",
|
||||||
|
"build:docker": "turbo run build:docker",
|
||||||
|
"build:localdocker": "turbo run build:localdocker",
|
||||||
"dist-win": "turbo run dist-win",
|
"dist-win": "turbo run dist-win",
|
||||||
"dist-mac": "turbo run dist-mac",
|
"dist-mac": "turbo run dist-mac",
|
||||||
"dist-linux": "turbo run dist-linux",
|
"dist-linux": "turbo run dist-linux",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"build:local": {},
|
"build:local": {},
|
||||||
"build:electron": {},
|
"build:electron": {},
|
||||||
"build:docker": {},
|
"build:docker": {},
|
||||||
|
"build:localdocker": {},
|
||||||
"e2e": {
|
"e2e": {
|
||||||
"dependsOn": ["^build"]
|
"dependsOn": ["^build"]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user