* fix: fetch in offline environments (#295)

* Add arm platforms to docker build (#297)

* add arm platforms to docker build

---------

Co-authored-by: Fabian Posenau <fabian@fphome.de>

* fix: docker build (#298)

Co-authored-by: Fabian Posenau <fabian@fphome.de>

* Timer: fix too many renders error when using ?progress (#305)

* Timer: fix too many renders error when using ?progress

* version bump (#314)

* style: small tweaks

* fix: prevent actions in roll mode

* chore: update external assets

* style: show shortcuts in quick blocks

* refactor: revert optimisations on callbacks

* fun: industry dictionary

* style: indicators for event features

---------

Co-authored-by: Fabian Posenau <fabian.p99@gmx.de>
Co-authored-by: Fabian Posenau <fabian@fphome.de>
Co-authored-by: Marks Polakovs <github@markspolakovs.me>
This commit is contained in:
Carlos Valente
2023-04-03 09:03:36 +02:00
committed by GitHub
parent 03e7428348
commit 32b5ab0574
14 changed files with 297 additions and 2796 deletions
@@ -4,6 +4,10 @@ $input-delayed-border-color: #E69056;
.timeInput { .timeInput {
width: fit-content !important; width: fit-content !important;
.inputButton {
aspect-ratio: 1;
}
.inputField { .inputField {
font-size: $input-font-size; font-size: $input-font-size;
letter-spacing: 1px; letter-spacing: 1px;
@@ -174,6 +174,7 @@ export default function EventEditor() {
name='timerType' name='timerType'
value={event.timerType} value={event.timerType}
onChange={(event) => handleChange('timerType', event.target.value)} onChange={(event) => handleChange('timerType', event.target.value)}
variant='ontime'
> >
<option value={TimerType.CountDown}>Count down</option> <option value={TimerType.CountDown}>Count down</option>
<option value={TimerType.CountUp}>Count up</option> <option value={TimerType.CountUp}>Count up</option>
@@ -185,6 +186,7 @@ export default function EventEditor() {
name='endAction' name='endAction'
value={event.endAction} value={event.endAction}
onChange={(event) => handleChange('endAction', event.target.value)} onChange={(event) => handleChange('endAction', event.target.value)}
variant='ontime'
> >
<option value={EndAction.Continue}>Continue</option> <option value={EndAction.Continue}>Continue</option>
<option value={EndAction.Stop}>Stop</option> <option value={EndAction.Stop}>Stop</option>
+1 -1
View File
@@ -245,7 +245,7 @@ export default function Rundown(props: RundownProps) {
/> />
{((showQuickEntry && index === cursor) || isLast) && ( {((showQuickEntry && index === cursor) || isLast) && (
<QuickAddBlock <QuickAddBlock
showKbd={false} showKbd={index === cursor}
eventId={entry.id} eventId={entry.id}
previousEventId={previousEventId} previousEventId={previousEventId}
disableAddDelay={entry.type === 'delay'} disableAddDelay={entry.type === 'delay'}
@@ -48,7 +48,8 @@ export default function RundownEntry(props: RundownEntryProps) {
// we assume the data is not changing in the lifecycle of this component // we assume the data is not changing in the lifecycle of this component
// changes to the data would make rundown re-render, also re-rendering this component // changes to the data would make rundown re-render, also re-rendering this component
const actionHandler = useCallback((action: EventItemActions, payload?: number | FieldValue) => { const actionHandler = useCallback(
(action: EventItemActions, payload?: number | FieldValue) => {
switch (action) { switch (action) {
case 'event': { case 'event': {
const newEvent = { type: SupportedEvent.Event }; const newEvent = { type: SupportedEvent.Event };
@@ -111,7 +112,20 @@ export default function RundownEntry(props: RundownEntryProps) {
default: default:
throw new Error(`Unhandled event ${action}`); throw new Error(`Unhandled event ${action}`);
} }
}, []); },
[
addEvent,
data,
defaultPublic,
deleteEvent,
emitError,
openId,
previousEventId,
removeOpenEvent,
startTimeIsLastEnd,
updateEvent,
],
);
if (data.type === SupportedEvent.Event) { if (data.type === SupportedEvent.Event) {
return ( return (
@@ -123,6 +137,8 @@ export default function RundownEntry(props: RundownEntryProps) {
eventIndex={eventIndex + 1} eventIndex={eventIndex + 1}
eventId={data.id} eventId={data.id}
isPublic={data.isPublic} isPublic={data.isPublic}
endAction={data.endAction}
timerType={data.timerType}
title={data.title} title={data.title}
note={data.note} note={data.note}
delay={delay} delay={delay}
@@ -154,15 +154,24 @@
grid-area: status; grid-area: status;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center;
gap: 8px; gap: 8px;
.tag {
font-size: 0.55em;
color: $active-indicator;
}
.statusIcon { .statusIcon {
width: 16px; width: 16px;
height: 16px; height: 16px;
color: $gray-1000; color: $gray-500;
} }
.statusIcon.active { .statusIcon.active {
color: $ui-white; color: $active-indicator;
}
.statusIcon.disabled {
color: $gray-1000;
} }
} }
@@ -2,7 +2,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useSortable } from '@dnd-kit/sortable'; import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities'; import { CSS } from '@dnd-kit/utilities';
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo'; import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
import { OntimeEvent, Playback } from 'ontime-types'; import { EndAction, OntimeEvent, Playback, TimerType } from 'ontime-types';
import { useCursor } from '../../../common/stores/cursorStore'; import { useCursor } from '../../../common/stores/cursorStore';
import { useEventEditorStore } from '../../../common/stores/eventEditor'; import { useEventEditorStore } from '../../../common/stores/eventEditor';
@@ -21,6 +21,8 @@ interface EventBlockProps {
eventIndex: number; eventIndex: number;
eventId: string; eventId: string;
isPublic: boolean; isPublic: boolean;
endAction: EndAction;
timerType: TimerType;
title: string; title: string;
note: string; note: string;
delay: number; delay: number;
@@ -51,6 +53,8 @@ export default function EventBlock(props: EventBlockProps) {
eventIndex, eventIndex,
eventId, eventId,
isPublic = true, isPublic = true,
endAction,
timerType,
title, title,
note, note,
delay, delay,
@@ -143,6 +147,8 @@ export default function EventBlock(props: EventBlockProps) {
duration={duration} duration={duration}
eventId={eventId} eventId={eventId}
isPublic={isPublic} isPublic={isPublic}
endAction={endAction}
timerType={timerType}
title={title} title={title}
note={note} note={note}
delay={delay} delay={delay}
@@ -1,14 +1,20 @@
import { memo, useCallback, useEffect, useState } from 'react'; import { memo, useCallback, useEffect, useState } from 'react';
import { Editable, EditableInput, EditablePreview, Tooltip } from '@chakra-ui/react'; import { Editable, EditableInput, EditablePreview, Tooltip } from '@chakra-ui/react';
import { IoCaretDownCircle } from '@react-icons/all-files/io5/IoCaretDownCircle';
import { IoCaretUpCircle } from '@react-icons/all-files/io5/IoCaretUpCircle';
import { IoOptions } from '@react-icons/all-files/io5/IoOptions'; import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
import { IoPeople } from '@react-icons/all-files/io5/IoPeople'; import { IoPeople } from '@react-icons/all-files/io5/IoPeople';
import { IoPlay } from '@react-icons/all-files/io5/IoPlay'; import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
import { IoPlayCircle } from '@react-icons/all-files/io5/IoPlayCircle';
import { IoPlayForwardCircle } from '@react-icons/all-files/io5/IoPlayForwardCircle';
import { IoPlayOutline } from '@react-icons/all-files/io5/IoPlayOutline'; import { IoPlayOutline } from '@react-icons/all-files/io5/IoPlayOutline';
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward'; import { IoPlaySkipForwardCircle } from '@react-icons/all-files/io5/IoPlaySkipForwardCircle';
import { IoReload } from '@react-icons/all-files/io5/IoReload'; import { IoReload } from '@react-icons/all-files/io5/IoReload';
import { IoRemoveCircle } from '@react-icons/all-files/io5/IoRemoveCircle'; import { IoRemoveCircle } from '@react-icons/all-files/io5/IoRemoveCircle';
import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline'; import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
import { Playback } from 'ontime-types'; import { IoStopCircle } from '@react-icons/all-files/io5/IoStopCircle';
import { IoTime } from '@react-icons/all-files/io5/IoTime';
import { EndAction, Playback, TimerType } from 'ontime-types';
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn'; import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
import { useEventAction } from '../../../common/hooks/useEventAction'; import { useEventAction } from '../../../common/hooks/useEventAction';
@@ -38,6 +44,8 @@ interface EventBlockInnerProps {
duration: number; duration: number;
eventId: string; eventId: string;
isPublic: boolean; isPublic: boolean;
endAction: EndAction;
timerType: TimerType;
title: string; title: string;
note: string; note: string;
delay: number; delay: number;
@@ -57,6 +65,8 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
duration, duration,
eventId, eventId,
isPublic = true, isPublic = true,
endAction,
timerType,
title, title,
note, note,
delay, delay,
@@ -180,14 +190,24 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
{selected && <EventBlockProgressBar playback={playback} />} {selected && <EventBlockProgressBar playback={playback} />}
</div> </div>
<div className={style.eventStatus} tabIndex={-1}> <div className={style.eventStatus} tabIndex={-1}>
<Tooltip label='Next event' isDisabled={!next} {...tooltipProps}> {next && (
<Tooltip label='Next event' {...tooltipProps}>
<span className={style.tag}>NEXT</span>
</Tooltip>
)}
<Tooltip label={`End action: ${endAction}`} {...tooltipProps}>
<span> <span>
<IoPlaySkipForward className={`${style.statusIcon} ${next ? style.active : ''}`} /> <EndActionIcon action={endAction} className={style.statusIcon} />
</span>
</Tooltip>
<Tooltip label={`Time type: ${timerType}`} {...tooltipProps}>
<span>
<TimerIcon type={timerType} className={style.statusIcon} />
</span> </span>
</Tooltip> </Tooltip>
<Tooltip label={`${isPublic ? 'Event is public' : 'Event is private'}`} {...tooltipProps}> <Tooltip label={`${isPublic ? 'Event is public' : 'Event is private'}`} {...tooltipProps}>
<span> <span>
<IoPeople className={`${style.statusIcon} ${isPublic ? style.active : ''}`} /> <IoPeople className={`${style.statusIcon} ${isPublic ? style.active : style.disabled}`} />
</span> </span>
</Tooltip> </Tooltip>
</div> </div>
@@ -212,3 +232,28 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
}; };
export default memo(EventBlockInner); export default memo(EventBlockInner);
function EndActionIcon(props: { action: EndAction; className: string }) {
const { action, className } = props;
if (action === EndAction.LoadNext) {
return <IoPlaySkipForwardCircle className={className} />;
}
if (action === EndAction.PlayNext) {
return <IoPlayForwardCircle className={className} />;
}
if (action === EndAction.Stop) {
return <IoStopCircle className={className} />;
}
return <IoPlayCircle className={className} />;
}
function TimerIcon(props: { type: TimerType; className: string }) {
const { type, className } = props;
if (type === TimerType.CountUp) {
return <IoCaretUpCircle className={className} />;
}
if (type === TimerType.Clock) {
return <IoTime className={className} />;
}
return <IoCaretDownCircle className={className} />;
}
+14
View File
@@ -95,6 +95,20 @@
"!**/{mock,mocks,__mock__,__mocks__}", "!**/{mock,mocks,__mock__,__mocks__}",
"!*{.spec.js,*.test.js,*.spec.ts,.test.ts}" "!*{.spec.js,*.test.js,*.spec.ts,.test.ts}"
] ]
},
{
"from": "../server/src/preloaded-db/",
"to": "extraResources/preloaded-db/",
"filter": [
"**/*"
]
},
{
"from": "../server/src/external/",
"to": "extraResources/external/",
"filter": [
"**/*"
]
} }
] ]
} }
+2 -2
View File
@@ -7,7 +7,7 @@ import cors from 'cors';
import { join, resolve } from 'path'; import { join, resolve } from 'path';
import { initSentry, reportSentryException } from './modules/sentry.js'; import { initSentry, reportSentryException } from './modules/sentry.js';
import { currentDirectory, environment, isProduction, resolvedPath } from './setup.js'; import { currentDirectory, environment, externalsStartDirectory, isProduction, resolvedPath } from './setup.js';
import { ONTIME_VERSION } from './ONTIME_VERSION.js'; import { ONTIME_VERSION } from './ONTIME_VERSION.js';
import { OSCSettings } from 'ontime-types'; import { OSCSettings } from 'ontime-types';
@@ -61,7 +61,7 @@ app.use('/ontime', ontimeRouter);
app.use('/playback', playbackRouter); app.use('/playback', playbackRouter);
// serve static - css // serve static - css
app.use('/external', express.static(join(currentDirectory, 'external'))); app.use('/external', express.static(externalsStartDirectory));
// serve static - react, in test mode we fetch the React app from module // serve static - react, in test mode we fetch the React app from module
app.use(express.static(join(currentDirectory, resolvedPath()))); app.use(express.static(join(currentDirectory, resolvedPath())));
+2
View File
@@ -307,6 +307,7 @@ 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.playback === Playback.Play) {
if (this.timer.endAction === EndAction.Stop) { if (this.timer.endAction === EndAction.Stop) {
PlaybackService.stop(); PlaybackService.stop();
} else if (this.timer.endAction === EndAction.LoadNext) { } else if (this.timer.endAction === EndAction.LoadNext) {
@@ -318,6 +319,7 @@ export class TimerService {
PlaybackService.startNext(); PlaybackService.startNext();
} }
} }
}
roll(currentEvent, nextEvent, timers) { roll(currentEvent, nextEvent, timers) {
this._clear(); this._clear();
+7 -3
View File
@@ -66,10 +66,14 @@ if (import.meta.url) {
// path to server src folder // path to server src folder
export const currentDirectory = dirname(__dirname); export const currentDirectory = dirname(__dirname);
const appPath = isTest ? '../' : getAppDataPath(); const testDbStartDirectory = isTest ? '../' : getAppDataPath();
export const externalsStartDirectory = isProduction ? getAppDataPath() : join(currentDirectory, 'external');
// path to public db // path to public db
export const resolveDbDirectory = join(appPath, isTest ? config.database.testdb : config.database.directory); export const resolveDbDirectory = join(
testDbStartDirectory,
isTest ? config.database.testdb : config.database.directory,
);
export const resolveDbPath = join(resolveDbDirectory, config.database.filename); export const resolveDbPath = join(resolveDbDirectory, config.database.filename);
export const pathToStartDb = isTest export const pathToStartDb = isTest
@@ -77,7 +81,7 @@ export const pathToStartDb = isTest
: join(currentDirectory, '/preloaded-db/', config.database.filename); : join(currentDirectory, '/preloaded-db/', config.database.filename);
// path to public styles // path to public styles
export const resolveStylesDirectory = join(appPath, config.styles.directory); export const resolveStylesDirectory = join(externalsStartDirectory, config.styles.directory);
export const resolveStylesPath = join(resolveStylesDirectory, config.styles.filename); export const resolveStylesPath = join(resolveStylesDirectory, config.styles.filename);
export const pathToStartStyles = join(currentDirectory, '/external/styles/', config.styles.filename); export const pathToStartStyles = join(currentDirectory, '/external/styles/', config.styles.filename);
File diff suppressed because it is too large Load Diff
@@ -8,7 +8,7 @@ test('test', async ({ context }) => {
// stage timer message // stage timer message
await editorPage.getByPlaceholder('Shown in stage timer').click(); await editorPage.getByPlaceholder('Shown in stage timer').click();
await expect(editorPage.getByPlaceholder('Shown in stage timer')).toBeEnabled(true); await expect(editorPage.getByPlaceholder('Shown in stage timer')).toBeEnabled();
await editorPage.getByPlaceholder('Shown in stage timer').fill('testing stage'); await editorPage.getByPlaceholder('Shown in stage timer').fill('testing stage');
await editorPage.getByRole('button', { name: /toggle timer screen message/i }).click({ timeout: 5000 }); await editorPage.getByRole('button', { name: /toggle timer screen message/i }).click({ timeout: 5000 });