mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-10 08:39:34 +00:00
Feature: quick start (#369)
* feat: quick start modal * refactor: end message is part of view settings * refactor: small tweaks and type improvements
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
export const STATIC_PORT = 4001;
|
export const STATIC_PORT = 4001;
|
||||||
|
|
||||||
// REST stuff
|
// REST stuff
|
||||||
export const EVENTDATA_TABLE = ['eventdata'];
|
export const EVENT_DATA = ['eventdata'];
|
||||||
export const ALIASES = ['aliases'];
|
export const ALIASES = ['aliases'];
|
||||||
export const USERFIELDS = ['userFields'];
|
export const USERFIELDS = ['userFields'];
|
||||||
export const RUNDOWN_TABLE_KEY = 'rundown';
|
export const RUNDOWN_TABLE_KEY = 'rundown';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { Alias, OSCSettings, Settings, UserFields, ViewSettings } from 'ontime-types';
|
import { Alias, EventData, OSCSettings, Settings, UserFields, ViewSettings } from 'ontime-types';
|
||||||
|
|
||||||
import { apiRepoLatest } from '../../externals';
|
import { apiRepoLatest } from '../../externals';
|
||||||
import { InfoType } from '../models/Info';
|
import { InfoType } from '../models/Info';
|
||||||
@@ -169,3 +169,7 @@ export async function getLatestVersion(): Promise<HasUpdate> {
|
|||||||
version: res.data.tag_name as string,
|
version: res.data.tag_name as string,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function postNew(initialData: Partial<EventData>) {
|
||||||
|
return axios.post(`${ontimeURL}/new`, initialData);
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export const AutoTextArea = (props: AutoTextAreaProps) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Textarea
|
<Textarea
|
||||||
overflow='hidden'
|
overflow='hidden'
|
||||||
@@ -27,7 +26,7 @@ export const AutoTextArea = (props: AutoTextAreaProps) => {
|
|||||||
resize='none'
|
resize='none'
|
||||||
ref={ref}
|
ref={ref}
|
||||||
transition='height none'
|
transition='height none'
|
||||||
variant={isDark ? 'ontime-filled' : 'ontime-filled-onlight'}
|
variant={isDark ? 'ontime-filled' : 'ontime-filled-on-light'}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
||||||
import { EVENTDATA_TABLE } from '../api/apiConstants';
|
import { EVENT_DATA } from '../api/apiConstants';
|
||||||
import { fetchEventData } from '../api/eventDataApi';
|
import { fetchEventData } from '../api/eventDataApi';
|
||||||
import { eventDataPlaceholder } from '../models/EventData';
|
import { eventDataPlaceholder } from '../models/EventData';
|
||||||
|
|
||||||
export default function useEventData() {
|
export default function useEventData() {
|
||||||
const { data, status, isError, refetch } = useQuery({
|
const { data, status, isError, refetch } = useQuery({
|
||||||
queryKey: EVENTDATA_TABLE,
|
queryKey: EVENT_DATA,
|
||||||
queryFn: fetchEventData,
|
queryFn: fetchEventData,
|
||||||
placeholderData: eventDataPlaceholder,
|
placeholderData: eventDataPlaceholder,
|
||||||
retry: 5,
|
retry: 5,
|
||||||
|
|||||||
@@ -6,5 +6,4 @@ export const eventDataPlaceholder: EventData = {
|
|||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
backstageUrl: '',
|
backstageUrl: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
endMessage: '',
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ import { ViewSettings } from 'ontime-types';
|
|||||||
|
|
||||||
export const viewsSettingsPlaceholder: ViewSettings = {
|
export const viewsSettingsPlaceholder: ViewSettings = {
|
||||||
overrideStyles: false,
|
overrideStyles: false,
|
||||||
|
endMessage: '',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import MenuBar from '../menu/MenuBar';
|
|||||||
import AboutModal from '../modals/about-modal/AboutModal';
|
import AboutModal from '../modals/about-modal/AboutModal';
|
||||||
import IntegrationModal from '../modals/integration-modal/IntegrationModal';
|
import IntegrationModal from '../modals/integration-modal/IntegrationModal';
|
||||||
import ModalManager from '../modals/ModalManager';
|
import ModalManager from '../modals/ModalManager';
|
||||||
|
import QuickStart from '../modals/quick-start/QuickStart';
|
||||||
|
|
||||||
import styles from './Editor.module.scss';
|
import styles from './Editor.module.scss';
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ export default function Editor() {
|
|||||||
onClose: onIntegrationModalClose,
|
onClose: onIntegrationModalClose,
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
const { isOpen: isAboutModalOpen, onOpen: onAboutModalOpen, onClose: onAboutModalClose } = useDisclosure();
|
const { isOpen: isAboutModalOpen, onOpen: onAboutModalOpen, onClose: onAboutModalClose } = useDisclosure();
|
||||||
|
const { isOpen: isQuickStartOpen, onOpen: onQuickStartOpen, onClose: onQuickStartClose } = useDisclosure();
|
||||||
|
|
||||||
// Set window title
|
// Set window title
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -34,6 +36,7 @@ export default function Editor() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
<QuickStart onClose={onQuickStartClose} isOpen={isQuickStartOpen} />
|
||||||
<UploadModal onClose={onUploadModalClose} isOpen={isUploadModalOpen} />
|
<UploadModal onClose={onUploadModalClose} isOpen={isUploadModalOpen} />
|
||||||
<IntegrationModal onClose={onIntegrationModalClose} isOpen={isIntegrationModalOpen} />
|
<IntegrationModal onClose={onIntegrationModalClose} isOpen={isIntegrationModalOpen} />
|
||||||
<AboutModal onClose={onAboutModalClose} isOpen={isAboutModalOpen} />
|
<AboutModal onClose={onAboutModalClose} isOpen={isAboutModalOpen} />
|
||||||
@@ -52,6 +55,8 @@ export default function Editor() {
|
|||||||
onIntegrationOpen={onIntegrationModalOpen}
|
onIntegrationOpen={onIntegrationModalOpen}
|
||||||
isAboutOpen={isAboutModalOpen}
|
isAboutOpen={isAboutModalOpen}
|
||||||
onAboutOpen={onAboutModalOpen}
|
onAboutOpen={onAboutModalOpen}
|
||||||
|
isQuickStartOpen={isQuickStartOpen}
|
||||||
|
onQuickStartOpen={onQuickStartOpen}
|
||||||
/>
|
/>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { VStack } from '@chakra-ui/react';
|
import { VStack } from '@chakra-ui/react';
|
||||||
import { FiSave } from '@react-icons/all-files/fi/FiSave';
|
import { IoColorWand } from '@react-icons/all-files/io5/IoColorWand';
|
||||||
import { FiUpload } from '@react-icons/all-files/fi/FiUpload';
|
|
||||||
import { IoExtensionPuzzle } from '@react-icons/all-files/io5/IoExtensionPuzzle';
|
import { IoExtensionPuzzle } from '@react-icons/all-files/io5/IoExtensionPuzzle';
|
||||||
import { IoExtensionPuzzleOutline } from '@react-icons/all-files/io5/IoExtensionPuzzleOutline';
|
import { IoExtensionPuzzleOutline } from '@react-icons/all-files/io5/IoExtensionPuzzleOutline';
|
||||||
import { IoHelp } from '@react-icons/all-files/io5/IoHelp';
|
import { IoHelp } from '@react-icons/all-files/io5/IoHelp';
|
||||||
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
||||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||||
|
import { IoPushOutline } from '@react-icons/all-files/io5/IoPushOutline';
|
||||||
|
import { IoSaveOutline } from '@react-icons/all-files/io5/IoSaveOutline';
|
||||||
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
||||||
|
|
||||||
import { downloadRundown } from '../../common/api/ontimeApi';
|
import { downloadRundown } from '../../common/api/ontimeApi';
|
||||||
@@ -27,6 +28,8 @@ interface MenuBarProps {
|
|||||||
onIntegrationOpen: () => void;
|
onIntegrationOpen: () => void;
|
||||||
isAboutOpen: boolean;
|
isAboutOpen: boolean;
|
||||||
onAboutOpen: () => void;
|
onAboutOpen: () => void;
|
||||||
|
isQuickStartOpen: boolean;
|
||||||
|
onQuickStartOpen: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttonStyle = {
|
const buttonStyle = {
|
||||||
@@ -52,6 +55,8 @@ export default function MenuBar(props: MenuBarProps) {
|
|||||||
onIntegrationOpen,
|
onIntegrationOpen,
|
||||||
isAboutOpen,
|
isAboutOpen,
|
||||||
onAboutOpen,
|
onAboutOpen,
|
||||||
|
isQuickStartOpen,
|
||||||
|
onQuickStartOpen,
|
||||||
} = props;
|
} = props;
|
||||||
const { isElectron, sendToElectron } = useElectronEvent();
|
const { isElectron, sendToElectron } = useElectronEvent();
|
||||||
|
|
||||||
@@ -102,7 +107,15 @@ export default function MenuBar(props: MenuBarProps) {
|
|||||||
<div className={style.gap} />
|
<div className={style.gap} />
|
||||||
<TooltipActionBtn
|
<TooltipActionBtn
|
||||||
{...buttonStyle}
|
{...buttonStyle}
|
||||||
icon={<FiUpload />}
|
icon={<IoColorWand />}
|
||||||
|
className={isQuickStartOpen ? style.open : ''}
|
||||||
|
clickHandler={onQuickStartOpen}
|
||||||
|
tooltip='Quick start'
|
||||||
|
aria-label='Quick start'
|
||||||
|
/>
|
||||||
|
<TooltipActionBtn
|
||||||
|
{...buttonStyle}
|
||||||
|
icon={<IoPushOutline />}
|
||||||
className={isUploadOpen ? style.open : ''}
|
className={isUploadOpen ? style.open : ''}
|
||||||
clickHandler={onUploadOpen}
|
clickHandler={onUploadOpen}
|
||||||
tooltip='Upload showfile'
|
tooltip='Upload showfile'
|
||||||
@@ -110,7 +123,7 @@ export default function MenuBar(props: MenuBarProps) {
|
|||||||
/>
|
/>
|
||||||
<TooltipActionBtn
|
<TooltipActionBtn
|
||||||
{...buttonStyle}
|
{...buttonStyle}
|
||||||
icon={<FiSave />}
|
icon={<IoSaveOutline />}
|
||||||
clickHandler={downloadRundown}
|
clickHandler={downloadRundown}
|
||||||
tooltip='Export showfile'
|
tooltip='Export showfile'
|
||||||
aria-label='Export showfile'
|
aria-label='Export showfile'
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export default function SettingsModal() {
|
|||||||
publicInfo: data.publicInfo,
|
publicInfo: data.publicInfo,
|
||||||
backstageUrl: data.backstageUrl,
|
backstageUrl: data.backstageUrl,
|
||||||
backstageInfo: data.backstageInfo,
|
backstageInfo: data.backstageInfo,
|
||||||
endMessage: data.endMessage,
|
|
||||||
});
|
});
|
||||||
}, [changed, data]);
|
}, [changed, data]);
|
||||||
|
|
||||||
@@ -168,23 +167,6 @@ export default function SettingsModal() {
|
|||||||
onChange={(event) => handleChange('backstageInfo', event.target.value)}
|
onChange={(event) => handleChange('backstageInfo', event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.spacedEntry}>
|
|
||||||
<FormLabel htmlFor='endMessage'>
|
|
||||||
End Message
|
|
||||||
<span className={style.labelNote}>
|
|
||||||
<br />
|
|
||||||
Shown on presenter view when time is finished
|
|
||||||
</span>
|
|
||||||
</FormLabel>
|
|
||||||
<Input
|
|
||||||
{...inputProps}
|
|
||||||
maxLength={30}
|
|
||||||
name='endMessage'
|
|
||||||
placeholder='Empty message shows elapsed time'
|
|
||||||
value={formData.endMessage}
|
|
||||||
onChange={(event) => handleChange('endMessage', event.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<SubmitContainer revert={revert} submitting={submitting} changed={changed} status={status} />
|
<SubmitContainer revert={revert} submitting={submitting} changed={changed} status={status} />
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
@use '../../theme/v2Styles' as *;
|
@use '../../theme/v2Styles' as *;
|
||||||
@use '../../theme/ontimeColours' as *;
|
@use '../../theme/ontimeColours' as *;
|
||||||
|
|
||||||
|
$el-padding-with-compensation: 24px; // 16 + 8
|
||||||
|
|
||||||
@mixin modal-link {
|
@mixin modal-link {
|
||||||
color: $blue-500;
|
color: $blue-500;
|
||||||
transition-property: color;
|
transition-property: color;
|
||||||
@@ -14,7 +16,7 @@
|
|||||||
.headerNotes {
|
.headerNotes {
|
||||||
font-size: $text-body-size;
|
font-size: $text-body-size;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 $section-spacing;
|
padding: 0 $el-padding-with-compensation;
|
||||||
color: $modal-note-color;
|
color: $modal-note-color;
|
||||||
margin-bottom: $section-spacing;
|
margin-bottom: $section-spacing;
|
||||||
|
|
||||||
@@ -24,6 +26,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footerNotes {
|
||||||
|
font-size: $inner-section-text-size;
|
||||||
|
padding: 0 $el-padding-with-compensation;
|
||||||
|
color: $modal-note-color;
|
||||||
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
margin: $element-spacing 0;
|
margin: $element-spacing 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -31,6 +39,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sectionContainer {
|
.sectionContainer {
|
||||||
|
padding: 8px 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%
|
height: 100%
|
||||||
@@ -60,7 +69,7 @@
|
|||||||
.sectionTitle {
|
.sectionTitle {
|
||||||
font-size: $inner-section-text-size;
|
font-size: $inner-section-text-size;
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
&.main {
|
&.main {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -83,9 +92,6 @@
|
|||||||
|
|
||||||
.buttonSection {
|
.buttonSection {
|
||||||
margin-top: $section-spacing;
|
margin-top: $section-spacing;
|
||||||
padding-top: $section-spacing;
|
|
||||||
padding-left: -24px;
|
|
||||||
border-top: 1px solid $gray-100;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $section-spacing;
|
gap: $section-spacing;
|
||||||
}
|
}
|
||||||
@@ -96,7 +102,7 @@
|
|||||||
|
|
||||||
.shiftRight {
|
.shiftRight {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
margin-right: 8px;
|
margin-right: $element-spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
.showPointer {
|
.showPointer {
|
||||||
@@ -109,7 +115,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.padBottom {
|
.padBottom {
|
||||||
padding-bottom: 8px;
|
padding-bottom: $element-spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
@@ -117,17 +123,22 @@
|
|||||||
height: 48px;
|
height: 48px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
margin-right: 16px;
|
margin-right: $section-spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test {
|
.updateSection {
|
||||||
padding-top: 24px;
|
padding-top: $el-padding-with-compensation;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 4px;
|
gap: $element-inner-spacing;
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
font-size: $error-red;
|
font-size: $error-red;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflowContainer {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 40%;
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { FormControl, FormLabel, ModalBody } from '@chakra-ui/react';
|
import { FormControl, FormLabel, Input, ModalBody } from '@chakra-ui/react';
|
||||||
import { IoCheckmarkSharp } from '@react-icons/all-files/io5/IoCheckmarkSharp';
|
|
||||||
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
||||||
|
|
||||||
import { useEmitLog } from '@/common/stores/logger';
|
|
||||||
|
|
||||||
import { postView } from '../../common/api/ontimeApi';
|
import { postView } from '../../common/api/ontimeApi';
|
||||||
import EnableBtn from '../../common/components/buttons/EnableBtn';
|
import EnableBtn from '../../common/components/buttons/EnableBtn';
|
||||||
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
||||||
import { viewsSettingsPlaceholder } from '../../common/models/ViewSettings.type';
|
import { viewsSettingsPlaceholder } from '../../common/models/ViewSettings.type';
|
||||||
|
import { useEmitLog } from '../../common/stores/logger';
|
||||||
import { openLink } from '../../common/utils/linkUtils';
|
import { openLink } from '../../common/utils/linkUtils';
|
||||||
|
import { inputProps } from '../../features/modals/modalHelper';
|
||||||
|
|
||||||
import SubmitContainer from './SubmitContainer';
|
import SubmitContainer from './SubmitContainer';
|
||||||
|
|
||||||
@@ -83,44 +82,37 @@ export default function ViewsSettingsModal() {
|
|||||||
🔥 Changes take effect immediately 🔥
|
🔥 Changes take effect immediately 🔥
|
||||||
</p>
|
</p>
|
||||||
<form onSubmit={submitHandler}>
|
<form onSubmit={submitHandler}>
|
||||||
|
<div className={style.hSeparator}>Timer end message</div>
|
||||||
|
<div className={style.spacedEntry}>
|
||||||
|
<FormLabel htmlFor='endMessage'>
|
||||||
|
End Message
|
||||||
|
<span className={style.labelNote}>
|
||||||
|
<br />
|
||||||
|
Shown on presenter view when time is finished
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...inputProps}
|
||||||
|
maxLength={50}
|
||||||
|
name='endMessage'
|
||||||
|
placeholder='Empty message shows elapsed time'
|
||||||
|
value={formData.endMessage}
|
||||||
|
onChange={(event) => handleChange('endMessage', event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className={style.hSeparator}>Style Options</div>
|
<div className={style.hSeparator}>Style Options</div>
|
||||||
<div className={style.blockNotes}>
|
<div className={style.blockNotes}>
|
||||||
<span className={style.inlineFlex}>
|
<span className={style.inlineFlex}>
|
||||||
<IoInformationCircleOutline color='#2b6cb0' fontSize='2em' />
|
<IoInformationCircleOutline color='#2b6cb0' fontSize='2em' />
|
||||||
CSS Style Overrides
|
CSS Style Overrides
|
||||||
</span>
|
</span>
|
||||||
This feature allows user defined CSS to override the application stylesheets as a way to customise viewers
|
This feature allows user defined CSS to customise viewers appearance. <br />
|
||||||
appearance.
|
|
||||||
<br />
|
|
||||||
Currently the feature affects the following views
|
|
||||||
<br />
|
|
||||||
<ul className={style.featureList}>
|
|
||||||
<li>
|
|
||||||
<IoCheckmarkSharp /> Stage timer
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<IoCheckmarkSharp /> Clock
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<IoCheckmarkSharp /> Minimal timer
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<IoCheckmarkSharp /> Backstage screen
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<IoCheckmarkSharp /> Public screen
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<IoCheckmarkSharp /> Countdown
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
Read more about it in the documentation{' '}
|
|
||||||
<a
|
<a
|
||||||
href='#!'
|
href='#!'
|
||||||
onClick={() => openLink('https://cpvalente.gitbook.io/ontime/features/custom-styling')}
|
onClick={() => openLink('https://cpvalente.gitbook.io/ontime/features/custom-styling')}
|
||||||
className={style.if}
|
className={style.if}
|
||||||
>
|
>
|
||||||
over at Gitbook
|
For details on the styling and file location please refer to documentation
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.modalFields}>
|
<div className={style.modalFields}>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function UpdateChecker(props: UpdateCheckerProps) {
|
|||||||
const disableButton = Boolean(updateMessage && 'version' in updateMessage);
|
const disableButton = Boolean(updateMessage && 'version' in updateMessage);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.test}>
|
<div className={styles.updateSection}>
|
||||||
<Button onClick={versionCheck} variant='ontime-filled' isLoading={isFetching} isDisabled={disableButton}>
|
<Button onClick={versionCheck} variant='ontime-filled' isLoading={isFetching} isDisabled={disableButton}>
|
||||||
Check for updates
|
Check for updates
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
import { ModalBody, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
||||||
|
|
||||||
import ModalWrapper from '../ModalWrapper';
|
import ModalWrapper from '../ModalWrapper';
|
||||||
|
|
||||||
@@ -19,26 +19,28 @@ export default function IntegrationModal(props: IntegrationModalProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalWrapper title='Integration Settings' isOpen={isOpen} onClose={onClose}>
|
<ModalWrapper title='Integration Settings' isOpen={isOpen} onClose={onClose}>
|
||||||
<div className={styles.headerNotes}>
|
<ModalBody>
|
||||||
Manage settings related to protocol integrations
|
<div className={styles.headerNotes}>
|
||||||
<a href={oscDocsUrl} target='_blank' rel='noreferrer'>
|
Manage settings related to protocol integrations
|
||||||
Read the docs
|
<a href={oscDocsUrl} target='_blank' rel='noreferrer'>
|
||||||
</a>
|
Read the docs
|
||||||
</div>
|
</a>
|
||||||
<Tabs variant='ontime' size='sm' isLazy>
|
</div>
|
||||||
<TabList>
|
<Tabs variant='ontime' size='sm' isLazy>
|
||||||
<Tab>OSC</Tab>
|
<TabList>
|
||||||
<Tab>OSC Integration</Tab>
|
<Tab>OSC</Tab>
|
||||||
</TabList>
|
<Tab>OSC Integration</Tab>
|
||||||
<TabPanels>
|
</TabList>
|
||||||
<TabPanel>
|
<TabPanels>
|
||||||
<OscSettings />
|
<TabPanel>
|
||||||
</TabPanel>
|
<OscSettings />
|
||||||
<TabPanel>
|
</TabPanel>
|
||||||
<OscIntegration />
|
<TabPanel>
|
||||||
</TabPanel>
|
<OscIntegration />
|
||||||
</TabPanels>
|
</TabPanel>
|
||||||
</Tabs>
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</ModalBody>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default function OntimeModalFooter(props: OntimeModalFooterProps) {
|
|||||||
const disableSubmit = isSubmitting || !isDirty || !isValid;
|
const disableSubmit = isSubmitting || !isDirty || !isValid;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalFooter className={styles.buttonSection} paddingInlineStart={0} paddingInlineEnd={0} paddingBottom={0}>
|
<ModalFooter className={styles.buttonSection}>
|
||||||
<Button isDisabled={disableRevert} variant='ontime-ghost-on-light' size='sm' onClick={handleRevert}>
|
<Button isDisabled={disableRevert} variant='ontime-ghost-on-light' size='sm' onClick={handleRevert}>
|
||||||
Revert to saved
|
Revert to saved
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { ModalBody } from '@chakra-ui/react';
|
|
||||||
import type { OSCSettings, OscSubscription } from 'ontime-types';
|
import type { OSCSettings, OscSubscription } from 'ontime-types';
|
||||||
import { TimerLifeCycle } from 'ontime-types';
|
import { TimerLifeCycle } from 'ontime-types';
|
||||||
import { generateId } from 'ontime-utils';
|
import { generateId } from 'ontime-utils';
|
||||||
@@ -103,27 +102,25 @@ export default function OscIntegration() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='oscSubscriptions'>
|
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='oscSubscriptions'>
|
||||||
<ModalBody>
|
{subscriptionKeys.map((cycle, idx) => {
|
||||||
{subscriptionKeys.map((cycle, idx) => {
|
return (
|
||||||
return (
|
<>
|
||||||
<>
|
<OscSubscriptionRow
|
||||||
<OscSubscriptionRow
|
key={cycle}
|
||||||
key={cycle}
|
cycle={cycle as TimerLifeCycle}
|
||||||
cycle={cycle as TimerLifeCycle}
|
title={sectionText[cycle as TimerLifeCycle].title}
|
||||||
title={sectionText[cycle as TimerLifeCycle].title}
|
subtitle={sectionText[cycle as TimerLifeCycle].subtitle}
|
||||||
subtitle={sectionText[cycle as TimerLifeCycle].subtitle}
|
visible={showSection === cycle}
|
||||||
visible={showSection === cycle}
|
setShowSection={setShowSection}
|
||||||
setShowSection={setShowSection}
|
subscriptionOptions={subscriptionState[cycle as TimerLifeCycle]}
|
||||||
subscriptionOptions={subscriptionState[cycle as TimerLifeCycle]}
|
handleDelete={deleteSubscriptionEntry}
|
||||||
handleDelete={deleteSubscriptionEntry}
|
handleAddNew={addNewSubscriptionEntry}
|
||||||
handleAddNew={addNewSubscriptionEntry}
|
register={register}
|
||||||
register={register}
|
/>
|
||||||
/>
|
{idx < subscriptionKeys.length - 1 && <hr className={styles.divider} />}
|
||||||
{idx < subscriptionKeys.length - 1 && <hr className={styles.divider} />}
|
</>
|
||||||
</>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
|
||||||
</ModalBody>
|
|
||||||
<OntimeModalFooter
|
<OntimeModalFooter
|
||||||
formId='oscSubscriptions'
|
formId='oscSubscriptions'
|
||||||
handleRevert={resetForm}
|
handleRevert={resetForm}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { FormControl, Input, ModalBody, Switch } from '@chakra-ui/react';
|
import { FormControl, Input, Switch } from '@chakra-ui/react';
|
||||||
|
|
||||||
import useOscSettings, { useOscSettingsMutation } from '../../../common/hooks-query/useOscSettings';
|
import useOscSettings, { useOscSettingsMutation } from '../../../common/hooks-query/useOscSettings';
|
||||||
import { PlaceholderSettings } from '../../../common/models/OscSettings';
|
import { PlaceholderSettings } from '../../../common/models/OscSettings';
|
||||||
@@ -53,114 +53,108 @@ export default function OscSettings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='oscSettings'>
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='oscSettings'>
|
<div className={styles.splitSection}>
|
||||||
<ModalBody>
|
<div>
|
||||||
<div className={styles.splitSection}>
|
<span className={`${styles.sectionTitle} ${styles.main}`}>OSC Input</span>
|
||||||
<div>
|
<span className={styles.sectionSubtitle}>Control Ontime with OSC</span>
|
||||||
<span className={`${styles.sectionTitle} ${styles.main}`}>OSC Input</span>
|
</div>
|
||||||
<span className={styles.sectionSubtitle}>Control Ontime with OSC</span>
|
<Switch {...register('enabledIn')} variant='ontime-on-light' />
|
||||||
</div>
|
</div>
|
||||||
<Switch {...register('enabledIn')} variant='ontime-on-light' />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FormControl isInvalid={!!errors.portIn} className={styles.splitSection}>
|
<FormControl isInvalid={!!errors.portIn} className={styles.splitSection}>
|
||||||
<label htmlFor='portIn'>
|
<label htmlFor='portIn'>
|
||||||
<span className={styles.sectionTitle}>Listen on Port</span>
|
<span className={styles.sectionTitle}>Listen on Port</span>
|
||||||
{errors.portIn ? (
|
{errors.portIn ? (
|
||||||
<span className={styles.error}>{errors.portIn.message}</span>
|
<span className={styles.error}>{errors.portIn.message}</span>
|
||||||
) : (
|
) : (
|
||||||
<span className={styles.sectionSubtitle}>Default 8888</span>
|
<span className={styles.sectionSubtitle}>Default 8888</span>
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
id='portIn'
|
id='portIn'
|
||||||
placeholder='8888'
|
placeholder='8888'
|
||||||
width='75px'
|
width='75px'
|
||||||
size='sm'
|
size='sm'
|
||||||
textAlign='right'
|
textAlign='right'
|
||||||
maxLength={5}
|
maxLength={5}
|
||||||
variant='ontime-filled-on-light'
|
variant='ontime-filled-on-light'
|
||||||
{...register('portIn', {
|
{...register('portIn', {
|
||||||
required: { value: true, message: 'Required field' },
|
required: { value: true, message: 'Required field' },
|
||||||
max: { value: 65535, message: 'Port in incorrect range (1024 - 65535)' },
|
max: { value: 65535, message: 'Port in incorrect range (1024 - 65535)' },
|
||||||
min: { value: 1024, message: 'Port in incorrect range (1024 - 65535)' },
|
min: { value: 1024, message: 'Port in incorrect range (1024 - 65535)' },
|
||||||
pattern: {
|
pattern: {
|
||||||
value: isOnlyNumbers,
|
value: isOnlyNumbers,
|
||||||
message: 'Value should be numeric',
|
message: 'Value should be numeric',
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<hr className={styles.divider} />
|
<div className={styles.splitSection}>
|
||||||
|
<div>
|
||||||
|
<span className={styles.sectionTitle} style={{ fontWeight: 600 }}>
|
||||||
|
OSC Output
|
||||||
|
</span>
|
||||||
|
<span className={styles.sectionSubtitle}>Ontime data feedback</span>
|
||||||
|
</div>
|
||||||
|
<Switch {...register('enabledOut')} variant='ontime-on-light' />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.splitSection}>
|
<FormControl isInvalid={!!errors.targetIP} className={styles.splitSection}>
|
||||||
<div>
|
<label htmlFor='targetIP'>
|
||||||
<span className={styles.sectionTitle} style={{ fontWeight: 600 }}>
|
<span className={styles.sectionTitle}>OSC target IP</span>
|
||||||
OSC Output
|
{errors.targetIP ? (
|
||||||
</span>
|
<span className={styles.error}>{errors.targetIP.message}</span>
|
||||||
<span className={styles.sectionSubtitle}>Ontime data feedback</span>
|
) : (
|
||||||
</div>
|
<span className={styles.sectionSubtitle}>Default 127.0.0.1</span>
|
||||||
<Switch {...register('enabledOut')} variant='ontime-on-light' />
|
)}
|
||||||
</div>
|
</label>
|
||||||
|
<Input
|
||||||
|
id='targetIP'
|
||||||
|
placeholder='127.0.0.1'
|
||||||
|
width='140px'
|
||||||
|
size='sm'
|
||||||
|
textAlign='right'
|
||||||
|
variant='ontime-filled-on-light'
|
||||||
|
{...register('targetIP', {
|
||||||
|
required: { value: true, message: 'Required field' },
|
||||||
|
pattern: {
|
||||||
|
value: isIPAddress,
|
||||||
|
message: 'Invalid IP address',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
<FormControl isInvalid={!!errors.targetIP} className={styles.splitSection}>
|
<FormControl className={styles.splitSection}>
|
||||||
<label htmlFor='targetIP'>
|
<label htmlFor='portOut'>
|
||||||
<span className={styles.sectionTitle}>OSC target IP</span>
|
<span className={styles.sectionTitle}>OSC target Port</span>
|
||||||
{errors.targetIP ? (
|
{errors.portOut ? (
|
||||||
<span className={styles.error}>{errors.targetIP.message}</span>
|
<span className={styles.error}>{errors.portOut.message}</span>
|
||||||
) : (
|
) : (
|
||||||
<span className={styles.sectionSubtitle}>Default 127.0.0.1</span>
|
<span className={styles.sectionSubtitle}>Default 9999</span>
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
id='targetIP'
|
id='portOut'
|
||||||
placeholder='127.0.0.1'
|
placeholder='9999'
|
||||||
width='140px'
|
width='75px'
|
||||||
size='sm'
|
size='sm'
|
||||||
textAlign='right'
|
textAlign='right'
|
||||||
variant='ontime-filled-on-light'
|
maxLength={5}
|
||||||
{...register('targetIP', {
|
variant='ontime-filled-on-light'
|
||||||
required: { value: true, message: 'Required field' },
|
{...register('portOut', {
|
||||||
pattern: {
|
required: { value: true, message: 'Required field' },
|
||||||
value: isIPAddress,
|
max: { value: 65535, message: 'Port in incorrect range (1024 - 65535)' },
|
||||||
message: 'Invalid IP address',
|
min: { value: 1024, message: 'Port in incorrect range (1024 - 65535)' },
|
||||||
},
|
pattern: {
|
||||||
})}
|
value: isOnlyNumbers,
|
||||||
/>
|
message: 'Value should be numeric',
|
||||||
</FormControl>
|
},
|
||||||
|
})}
|
||||||
<FormControl className={styles.splitSection}>
|
/>
|
||||||
<label htmlFor='portOut'>
|
</FormControl>
|
||||||
<span className={styles.sectionTitle}>OSC target Port</span>
|
|
||||||
{errors.portOut ? (
|
|
||||||
<span className={styles.error}>{errors.portOut.message}</span>
|
|
||||||
) : (
|
|
||||||
<span className={styles.sectionSubtitle}>Default 9999</span>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
id='portOut'
|
|
||||||
placeholder='9999'
|
|
||||||
width='75px'
|
|
||||||
size='sm'
|
|
||||||
textAlign='right'
|
|
||||||
maxLength={5}
|
|
||||||
variant='ontime-filled-on-light'
|
|
||||||
{...register('portOut', {
|
|
||||||
required: { value: true, message: 'Required field' },
|
|
||||||
max: { value: 65535, message: 'Port in incorrect range (1024 - 65535)' },
|
|
||||||
min: { value: 1024, message: 'Port in incorrect range (1024 - 65535)' },
|
|
||||||
pattern: {
|
|
||||||
value: isOnlyNumbers,
|
|
||||||
message: 'Value should be numeric',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</ModalBody>
|
|
||||||
</form>
|
|
||||||
<OntimeModalFooter
|
<OntimeModalFooter
|
||||||
formId='oscSettings'
|
formId='oscSettings'
|
||||||
handleRevert={resetForm}
|
handleRevert={resetForm}
|
||||||
@@ -168,6 +162,6 @@ export default function OscSettings() {
|
|||||||
isValid={isValid}
|
isValid={isValid}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
/>
|
/>
|
||||||
</>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
Textarea,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import type { EventData } from 'ontime-types';
|
||||||
|
|
||||||
|
import { EVENT_DATA, RUNDOWN_TABLE } from '../../../common/api/apiConstants';
|
||||||
|
import { postNew } from '../../../common/api/ontimeApi';
|
||||||
|
import useEventData from '../../../common/hooks-query/useEventData';
|
||||||
|
import { eventDataPlaceholder } from '../../../common/models/EventData';
|
||||||
|
import { ontimeQueryClient } from '../../../common/queryClient';
|
||||||
|
|
||||||
|
import styles from '../Modal.module.scss';
|
||||||
|
|
||||||
|
interface QuickStartProps {
|
||||||
|
onClose: () => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QuickStart({ onClose, isOpen }: QuickStartProps) {
|
||||||
|
const { data, status } = useEventData();
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
register,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
} = useForm({ defaultValues: data });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset(data);
|
||||||
|
}, [reset, data]);
|
||||||
|
|
||||||
|
const onSubmit = async (data: Partial<EventData>) => {
|
||||||
|
await postNew(data);
|
||||||
|
await ontimeQueryClient.invalidateQueries(EVENT_DATA);
|
||||||
|
await ontimeQueryClient.invalidateQueries(RUNDOWN_TABLE);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReset = () => reset(eventDataPlaceholder);
|
||||||
|
|
||||||
|
const disableButtons = status !== 'success' || isSubmitting;
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
onClose={onClose}
|
||||||
|
isOpen={isOpen}
|
||||||
|
closeOnOverlayClick={false}
|
||||||
|
motionPreset='slideInBottom'
|
||||||
|
size='xl'
|
||||||
|
scrollBehavior='inside'
|
||||||
|
preserveScrollBarGap
|
||||||
|
variant='ontime'
|
||||||
|
>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Ontime quick start</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody className={styles.pad}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer}>
|
||||||
|
<div className={styles.entryRow}>
|
||||||
|
<label className={styles.sectionTitle}>
|
||||||
|
Event title
|
||||||
|
<Input
|
||||||
|
variant='ontime-filled-on-light'
|
||||||
|
size='sm'
|
||||||
|
maxLength={50}
|
||||||
|
placeholder='Eurovision song contest'
|
||||||
|
{...register('title')}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={styles.entryRow}>
|
||||||
|
<label className={styles.sectionTitle}>
|
||||||
|
Public Info
|
||||||
|
<Textarea
|
||||||
|
variant='ontime-filled-on-light'
|
||||||
|
size='sm'
|
||||||
|
maxLength={150}
|
||||||
|
placeholder='Shows always start ontime'
|
||||||
|
{...register('publicInfo')}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={styles.entryRow}>
|
||||||
|
<label className={styles.sectionTitle}>
|
||||||
|
Public QR Code Url
|
||||||
|
<Input
|
||||||
|
variant='ontime-filled-on-light'
|
||||||
|
size='sm'
|
||||||
|
placeholder='www.getontime.no'
|
||||||
|
{...register('publicUrl')}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={styles.entryRow}>
|
||||||
|
<label className={styles.sectionTitle}>
|
||||||
|
Backstage Info
|
||||||
|
<Textarea
|
||||||
|
variant='ontime-filled-on-light'
|
||||||
|
size='sm'
|
||||||
|
maxLength={150}
|
||||||
|
placeholder='Wi-Fi password: 1234'
|
||||||
|
{...register('backstageInfo')}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={styles.entryRow}>
|
||||||
|
<label className={styles.sectionTitle}>
|
||||||
|
Backstage QR Code Url
|
||||||
|
<Input
|
||||||
|
variant='ontime-filled-on-light'
|
||||||
|
size='sm'
|
||||||
|
placeholder='www.ontime.gitbook.io'
|
||||||
|
{...register('backstageUrl')}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={styles.footerNotes}>
|
||||||
|
Note: Application options will be kept but rundown and event data will be reset <br />
|
||||||
|
</div>
|
||||||
|
<ModalFooter className={styles.buttonSection}>
|
||||||
|
<Button onClick={onReset} isDisabled={disableButtons} variant='ontime-ghost-on-light' size='sm'>
|
||||||
|
Clear data
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={disableButtons}
|
||||||
|
variant='ontime-filled'
|
||||||
|
padding='0 2em'
|
||||||
|
size='sm'
|
||||||
|
>
|
||||||
|
New showfile
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</form>
|
||||||
|
</ModalBody>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ReactNode, useMemo } from 'react';
|
import { ComponentType, useMemo } from 'react';
|
||||||
import { Playback, TitleBlock } from 'ontime-types';
|
import { Playback, TitleBlock } from 'ontime-types';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
|
|
||||||
@@ -10,22 +10,23 @@ import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
|||||||
|
|
||||||
export type TitleManager = TitleBlock & { showNow: boolean; showNext: boolean };
|
export type TitleManager = TitleBlock & { showNow: boolean; showNext: boolean };
|
||||||
|
|
||||||
const withData = (Component: ReactNode) => {
|
const withData = <P extends object>(Component: ComponentType<P>) => {
|
||||||
return (props) => {
|
// eslint-disable-next-line react/display-name -- its ok
|
||||||
|
return (props: P) => {
|
||||||
// persisted app state
|
// persisted app state
|
||||||
const isMirrored = useViewOptionsStore((state) => state.mirror);
|
const isMirrored = useViewOptionsStore((state) => state.mirror);
|
||||||
|
|
||||||
// HTTP API data
|
// HTTP API data
|
||||||
const { data: eventsData } = useRundown();
|
const { data: rundownData } = useRundown();
|
||||||
const { data: genData } = useEventData();
|
const { data: eventData } = useEventData();
|
||||||
const { data: viewSettings } = useViewSettings();
|
const { data: viewSettings } = useViewSettings();
|
||||||
|
|
||||||
const publicEvents = useMemo(() => {
|
const publicEvents = useMemo(() => {
|
||||||
if (Array.isArray(eventsData)) {
|
if (Array.isArray(rundownData)) {
|
||||||
return eventsData.filter((e) => e.type === 'event' && e.title && e.isPublic);
|
return rundownData.filter((e) => e.type === 'event' && e.title && e.isPublic);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}, [eventsData]);
|
}, [rundownData]);
|
||||||
|
|
||||||
// websocket data
|
// websocket data
|
||||||
const data = useStore(runtime);
|
const data = useStore(runtime);
|
||||||
@@ -99,12 +100,12 @@ const withData = (Component: ReactNode) => {
|
|||||||
publicTitle={publicTitleManager}
|
publicTitle={publicTitleManager}
|
||||||
time={TimeManagerType}
|
time={TimeManagerType}
|
||||||
events={publicEvents}
|
events={publicEvents}
|
||||||
backstageEvents={eventsData}
|
backstageEvents={rundownData}
|
||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
publicSelectedId={publicSelectedId}
|
publicSelectedId={publicSelectedId}
|
||||||
viewSettings={viewSettings}
|
viewSettings={viewSettings}
|
||||||
nextId={nextId}
|
nextId={nextId}
|
||||||
general={genData}
|
general={eventData}
|
||||||
onAir={onAir}
|
onAir={onAir}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ interface MinimalTimerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function MinimalTimer(props: MinimalTimerProps) {
|
export default function MinimalTimer(props: MinimalTimerProps) {
|
||||||
const { isMirrored, pres, time, viewSettings, general } = props;
|
const { isMirrored, pres, time, viewSettings } = props;
|
||||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
|||||||
const isPlaying = time.playback !== Playback.Pause;
|
const isPlaying = time.playback !== Playback.Pause;
|
||||||
const isNegative =
|
const isNegative =
|
||||||
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
|
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
|
||||||
const showEndMessage = (time.current ?? 0) < 0 && general.endMessage && !hideEndMessage;
|
const showEndMessage = (time.current ?? 0) < 0 && viewSettings.endMessage && !hideEndMessage;
|
||||||
const showFinished =
|
const showFinished =
|
||||||
time.finished && !userOptions?.hideOvertime && (time.timerType !== TimerType.Clock || showEndMessage);
|
time.finished && !userOptions?.hideOvertime && (time.timerType !== TimerType.Clock || showEndMessage);
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showEndMessage ? (
|
{showEndMessage ? (
|
||||||
<div className='end-message'>{general.endMessage}</div>
|
<div className='end-message'>{viewSettings.endMessage}</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={timerClasses}
|
className={timerClasses}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ interface TimerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Timer(props: TimerProps) {
|
export default function Timer(props: TimerProps) {
|
||||||
const { isMirrored, general, pres, title, time, viewSettings } = props;
|
const { isMirrored, pres, title, time, viewSettings } = props;
|
||||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ export default function Timer(props: TimerProps) {
|
|||||||
const isNegative =
|
const isNegative =
|
||||||
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
|
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
|
||||||
|
|
||||||
const showEndMessage = (time.current ?? 1) < 0 && general.endMessage;
|
const showEndMessage = (time.current ?? 1) < 0 && viewSettings.endMessage;
|
||||||
const showProgress = time.playback !== Playback.Stop;
|
const showProgress = time.playback !== Playback.Stop;
|
||||||
const showFinished = time.finished && (time.timerType !== TimerType.Clock || showEndMessage);
|
const showFinished = time.finished && (time.timerType !== TimerType.Clock || showEndMessage);
|
||||||
const showClock = time.timerType !== TimerType.Clock;
|
const showClock = time.timerType !== TimerType.Clock;
|
||||||
@@ -95,7 +95,7 @@ export default function Timer(props: TimerProps) {
|
|||||||
|
|
||||||
<div className='timer-container'>
|
<div className='timer-container'>
|
||||||
{showEndMessage ? (
|
{showEndMessage ? (
|
||||||
<div className='end-message'>{general.endMessage}</div>
|
<div className='end-message'>{viewSettings.endMessage}</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={timerClasses}
|
className={timerClasses}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export const ontimeModal = {
|
|||||||
header: {
|
header: {
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
letterSpacing: '0.3px',
|
letterSpacing: '0.3px',
|
||||||
padding: '8px 16px',
|
padding: '16px 24px',
|
||||||
fontSize: '20px',
|
fontSize: '20px',
|
||||||
color: '#202020', // $gray-50
|
color: '#202020', // $gray-50
|
||||||
},
|
},
|
||||||
@@ -19,6 +19,9 @@ export const ontimeModal = {
|
|||||||
closeButton: {
|
closeButton: {
|
||||||
color: '#202020', // $gray-50
|
color: '#202020', // $gray-50
|
||||||
},
|
},
|
||||||
|
footer: {
|
||||||
|
padding: '8px',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ontimeSmallModal = {
|
export const ontimeSmallModal = {
|
||||||
|
|||||||
@@ -13,4 +13,7 @@ export const ontimeTab = {
|
|||||||
tablist: {
|
tablist: {
|
||||||
borderBottom: '2px solid #ececec', // $gray-100
|
borderBottom: '2px solid #ececec', // $gray-100
|
||||||
},
|
},
|
||||||
|
tabpanel: {
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,13 +23,11 @@ export const ontimeInputFilled = {
|
|||||||
export const ontimeInputFilledOnLight = {
|
export const ontimeInputFilledOnLight = {
|
||||||
field: {
|
field: {
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
border: '2px solid transparent',
|
border: '2px solid #f6f6f6', // $gray-50
|
||||||
_hover: {
|
_hover: {
|
||||||
backgroundColor: 'white',
|
|
||||||
border: '2px solid #D2DDFF', // $blue-200
|
border: '2px solid #D2DDFF', // $blue-200
|
||||||
},
|
},
|
||||||
_focus: {
|
_focus: {
|
||||||
backgroundColor: 'white',
|
|
||||||
border: '2px solid #578AF4', // $blue-500
|
border: '2px solid #578AF4', // $blue-500
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -42,16 +40,15 @@ export const ontimeTextAreaFilled = {
|
|||||||
export const ontimeTextAreaFilledOnLight = {
|
export const ontimeTextAreaFilledOnLight = {
|
||||||
borderRadius: '3px',
|
borderRadius: '3px',
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
backgroundColor: '#ececec', // $gray-100
|
backgroundColor: 'white',
|
||||||
color: '#202020', // $gray-1200
|
color: '#202020', // $gray-1200
|
||||||
border: '1px solid transparent',
|
border: '2px solid #f6f6f6', // $gray-50
|
||||||
_hover: {
|
_hover: {
|
||||||
backgroundColor: '#cfcfcf', // $gray-300
|
border: '2px solid #D2DDFF', // $blue-200
|
||||||
},
|
},
|
||||||
_focus: {
|
_focus: {
|
||||||
backgroundColor: '#cfcfcf', // $gray-300
|
|
||||||
color: '#101010',
|
color: '#101010',
|
||||||
border: '1px solid #578AF4', // $blue-500
|
border: '2px solid #578AF4', // $blue-500
|
||||||
},
|
},
|
||||||
_placeholder: { color: '#9d9d9d' }, // $gray-500
|
_placeholder: { color: '#9d9d9d' }, // $gray-500
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const theme = extendTheme({
|
|||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
'ontime-filled': { ...ontimeTextAreaFilled },
|
'ontime-filled': { ...ontimeTextAreaFilled },
|
||||||
'ontime-filled-onlight': { ...ontimeTextAreaFilledOnLight },
|
'ontime-filled-on-light': { ...ontimeTextAreaFilledOnLight },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Tooltip: {
|
Tooltip: {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class DataProvider {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async setEventData(newData: EventData) {
|
static async setEventData(newData: Partial<EventData>) {
|
||||||
data.eventData = { ...data.eventData, ...newData };
|
data.eventData = { ...data.eventData, ...newData };
|
||||||
await this.persist();
|
await this.persist();
|
||||||
return data.eventData;
|
return data.eventData;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ describe('safeMerge', () => {
|
|||||||
publicUrl: 'existing public URL',
|
publicUrl: 'existing public URL',
|
||||||
backstageUrl: 'existing backstageUrl',
|
backstageUrl: 'existing backstageUrl',
|
||||||
backstageInfo: 'existing backstageInfo',
|
backstageInfo: 'existing backstageInfo',
|
||||||
endMessage: 'existing endMessage',
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
app: 'ontime',
|
app: 'ontime',
|
||||||
@@ -20,6 +19,7 @@ describe('safeMerge', () => {
|
|||||||
},
|
},
|
||||||
viewSettings: {
|
viewSettings: {
|
||||||
overrideStyles: false,
|
overrideStyles: false,
|
||||||
|
endMessage: 'existing endMessage',
|
||||||
},
|
},
|
||||||
aliases: [],
|
aliases: [],
|
||||||
userFields: {
|
userFields: {
|
||||||
@@ -75,7 +75,6 @@ describe('safeMerge', () => {
|
|||||||
publicInfo: 'new public info',
|
publicInfo: 'new public info',
|
||||||
backstageUrl: 'existing backstageUrl',
|
backstageUrl: 'existing backstageUrl',
|
||||||
backstageInfo: 'existing backstageInfo',
|
backstageInfo: 'existing backstageInfo',
|
||||||
endMessage: 'existing endMessage',
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -145,7 +144,6 @@ describe('safeMerge', () => {
|
|||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
backstageUrl: '',
|
backstageUrl: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
endMessage: '',
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
app: 'ontime',
|
app: 'ontime',
|
||||||
@@ -157,6 +155,7 @@ describe('safeMerge', () => {
|
|||||||
},
|
},
|
||||||
viewSettings: {
|
viewSettings: {
|
||||||
overrideStyles: false,
|
overrideStyles: false,
|
||||||
|
endMessage: '',
|
||||||
},
|
},
|
||||||
aliases: [],
|
aliases: [],
|
||||||
userFields: {
|
userFields: {
|
||||||
|
|||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
import { removeUndefined } from '../utils/parserUtils.js';
|
import { removeUndefined } from '../utils/parserUtils.js';
|
||||||
import { failEmptyObjects } from '../utils/routerUtils.js';
|
import { failEmptyObjects } from '../utils/routerUtils.js';
|
||||||
import { DataProvider } from '../classes/data-provider/DataProvider.ts';
|
import { DataProvider } from '../classes/data-provider/DataProvider.js';
|
||||||
|
|
||||||
// Create controller for GET request to 'event'
|
// Create controller for GET request to 'event'
|
||||||
export const getEvent = async (req, res) => {
|
export const getEventData = async (req, res) => {
|
||||||
res.json(DataProvider.getEventData());
|
res.json(DataProvider.getEventData());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for POST request to 'event'
|
// Create controller for POST request to 'event'
|
||||||
export const postEvent = async (req, res) => {
|
export const postEventData = async (req, res) => {
|
||||||
if (failEmptyObjects(req.body, res)) {
|
if (failEmptyObjects(req.body, res)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { body, validationResult } from 'express-validator';
|
import { body, validationResult } from 'express-validator';
|
||||||
|
|
||||||
export const eventSanitizer = [
|
export const eventDataSanitizer = [
|
||||||
body('title').optional().isString().trim(),
|
body('title').optional().isString().trim(),
|
||||||
body('publicUrl').optional().isString().trim(),
|
body('publicUrl').optional().isString().trim(),
|
||||||
body('publicInfo').optional().isString().trim(),
|
body('publicInfo').optional().isString().trim(),
|
||||||
+27
-4
@@ -1,7 +1,8 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import type { EventData } from 'ontime-types';
|
||||||
import { networkInterfaces } from 'os';
|
import { networkInterfaces } from 'os';
|
||||||
import { generateId } from 'ontime-utils';
|
import { generateId } from 'ontime-utils';
|
||||||
import { fileHandler } from '../utils/parser.ts';
|
import { fileHandler } from '../utils/parser.js';
|
||||||
import { DataProvider } from '../classes/data-provider/DataProvider.js';
|
import { DataProvider } from '../classes/data-provider/DataProvider.js';
|
||||||
import { failEmptyObjects, failIsNotArray } from '../utils/routerUtils.js';
|
import { failEmptyObjects, failIsNotArray } from '../utils/routerUtils.js';
|
||||||
import { mergeObject } from '../utils/parserUtils.js';
|
import { mergeObject } from '../utils/parserUtils.js';
|
||||||
@@ -10,6 +11,7 @@ import { eventStore } from '../stores/EventStore.js';
|
|||||||
import { resolveDbPath } from '../setup.js';
|
import { resolveDbPath } from '../setup.js';
|
||||||
import { oscIntegration } from '../services/integration-service/OscIntegration.js';
|
import { oscIntegration } from '../services/integration-service/OscIntegration.js';
|
||||||
import { logger } from '../classes/Logger.js';
|
import { logger } from '../classes/Logger.js';
|
||||||
|
import { deleteAllEvents } from '../services/RundownService.js';
|
||||||
|
|
||||||
// Create controller for GET request to '/ontime/poll'
|
// Create controller for GET request to '/ontime/poll'
|
||||||
// Returns data for current state
|
// Returns data for current state
|
||||||
@@ -56,9 +58,9 @@ const uploadAndParse = async (file, req, res, options) => {
|
|||||||
try {
|
try {
|
||||||
const result = await fileHandler(file);
|
const result = await fileHandler(file);
|
||||||
|
|
||||||
if (result?.error) {
|
if ('error' in result && result.error) {
|
||||||
res.status(400).send({ message: result.message });
|
res.status(400).send({ message: result.message });
|
||||||
} else if (result.message === 'success') {
|
} else if ('data' in result && result.message === 'success') {
|
||||||
PlaybackService.stop();
|
PlaybackService.stop();
|
||||||
// explicitly write objects
|
// explicitly write objects
|
||||||
if (typeof result !== 'undefined') {
|
if (typeof result !== 'undefined') {
|
||||||
@@ -241,7 +243,10 @@ export const postViewSettings = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newData = { overrideStyles: req.body.overrideStyles };
|
const newData = {
|
||||||
|
overrideStyles: req.body.overrideStyles,
|
||||||
|
endMessage: req.body?.endMessage || '',
|
||||||
|
};
|
||||||
await DataProvider.setViewSettings(newData);
|
await DataProvider.setViewSettings(newData);
|
||||||
res.status(200).send(newData);
|
res.status(200).send(newData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -288,3 +293,21 @@ export const dbUpload = async (req, res) => {
|
|||||||
const file = req.file.path;
|
const file = req.file.path;
|
||||||
await uploadAndParse(file, req, res, options);
|
await uploadAndParse(file, req, res, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create controller for POST request to '/ontime/new'
|
||||||
|
export const postNew = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const newEventData: Omit<EventData, 'endMessage'> = {
|
||||||
|
title: req.body?.title ?? '',
|
||||||
|
publicUrl: req.body?.publicUrl ?? '',
|
||||||
|
publicInfo: req.body?.publicInfo ?? '',
|
||||||
|
backstageUrl: req.body?.backstageUrl ?? '',
|
||||||
|
backstageInfo: req.body?.backstageInfo ?? '',
|
||||||
|
};
|
||||||
|
const newData = await DataProvider.setEventData(newEventData);
|
||||||
|
await deleteAllEvents();
|
||||||
|
res.status(201).send(newData);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).send(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ import { validateOscSubscription } from '../utils/parserFunctions.js';
|
|||||||
*/
|
*/
|
||||||
export const viewValidator = [
|
export const viewValidator = [
|
||||||
check('overrideStyles').isBoolean().withMessage('overrideStyles value must be boolean'),
|
check('overrideStyles').isBoolean().withMessage('overrideStyles value must be boolean'),
|
||||||
|
check('endMessage').isString().trim().withMessage('endMessage value must be string'),
|
||||||
(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() });
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export const dbModel: DatabaseModel = {
|
|||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
backstageUrl: '',
|
backstageUrl: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
endMessage: '',
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
app: 'ontime',
|
app: 'ontime',
|
||||||
@@ -20,6 +19,7 @@ export const dbModel: DatabaseModel = {
|
|||||||
},
|
},
|
||||||
viewSettings: {
|
viewSettings: {
|
||||||
overrideStyles: false,
|
overrideStyles: false,
|
||||||
|
endMessage: '',
|
||||||
},
|
},
|
||||||
aliases: [],
|
aliases: [],
|
||||||
userFields: {
|
userFields: {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
// import event controller
|
||||||
|
import { getEventData, postEventData } from '../controllers/eventDataController.ts';
|
||||||
|
import { eventDataSanitizer } from '../controllers/eventDataController.validate.ts';
|
||||||
|
|
||||||
export const router = express.Router();
|
export const router = express.Router();
|
||||||
|
|
||||||
// import event controller
|
|
||||||
import { getEvent, postEvent } from '../controllers/eventDataController.js';
|
|
||||||
import { eventSanitizer } from '../controllers/eventDataController.validate.js';
|
|
||||||
|
|
||||||
// create route between controller and 'GET /event' endpoint
|
// create route between controller and 'GET /event' endpoint
|
||||||
router.get('/', getEvent);
|
router.get('/', getEventData);
|
||||||
|
|
||||||
// create route between controller and 'POST /event' endpoint
|
// create route between controller and 'POST /event' endpoint
|
||||||
router.post('/', eventSanitizer, postEvent);
|
router.post('/', eventDataSanitizer, postEventData);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
getViewSettings,
|
getViewSettings,
|
||||||
poll,
|
poll,
|
||||||
postAliases,
|
postAliases,
|
||||||
|
postNew,
|
||||||
postOSC,
|
postOSC,
|
||||||
postSettings,
|
postSettings,
|
||||||
postUserFields,
|
postUserFields,
|
||||||
@@ -24,6 +25,7 @@ import {
|
|||||||
validateUserFields,
|
validateUserFields,
|
||||||
viewValidator,
|
viewValidator,
|
||||||
} from '../controllers/ontimeController.validate.js';
|
} from '../controllers/ontimeController.validate.js';
|
||||||
|
import { eventDataSanitizer } from '../controllers/eventDataController.validate.js';
|
||||||
|
|
||||||
export const router = express.Router();
|
export const router = express.Router();
|
||||||
|
|
||||||
@@ -68,3 +70,6 @@ router.get('/osc', getOSC);
|
|||||||
|
|
||||||
// create route between controller and '/ontime/osc' endpoint
|
// create route between controller and '/ontime/osc' endpoint
|
||||||
router.post('/osc', validateOSC, postOSC);
|
router.post('/osc', validateOSC, postOSC);
|
||||||
|
|
||||||
|
// create route between controller and '/ontime/new' endpoint
|
||||||
|
router.post('/new', eventDataSanitizer, postNew);
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ type ResponseError = { error: true; message: string };
|
|||||||
* @param {string} file - reference to file
|
* @param {string} file - reference to file
|
||||||
* @return {object} - parse result message
|
* @return {object} - parse result message
|
||||||
*/
|
*/
|
||||||
export const fileHandler = async (file) => {
|
export const fileHandler = async (file): ResponseOK | ResponseError => {
|
||||||
let res: Partial<ResponseOK | ResponseError> = {};
|
let res: Partial<ResponseOK | ResponseError> = {};
|
||||||
|
|
||||||
// check which file type are we dealing with
|
// check which file type are we dealing with
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ export const parseEventData = (data, enforce): EventData => {
|
|||||||
publicInfo: e.publicInfo || dbModel.eventData.publicInfo,
|
publicInfo: e.publicInfo || dbModel.eventData.publicInfo,
|
||||||
backstageUrl: e.backstageUrl || dbModel.eventData.backstageUrl,
|
backstageUrl: e.backstageUrl || dbModel.eventData.backstageUrl,
|
||||||
backstageInfo: e.backstageInfo || dbModel.eventData.backstageInfo,
|
backstageInfo: e.backstageInfo || dbModel.eventData.backstageInfo,
|
||||||
endMessage: e.endMessage || dbModel.eventData.endMessage,
|
|
||||||
};
|
};
|
||||||
} else if (enforce) {
|
} else if (enforce) {
|
||||||
newEventData = { ...dbModel.eventData };
|
newEventData = { ...dbModel.eventData };
|
||||||
@@ -145,6 +144,7 @@ export const parseViewSettings = (data, enforce): ViewSettings => {
|
|||||||
|
|
||||||
const viewSettings = {
|
const viewSettings = {
|
||||||
overrideStyles: v.overrideStyles ?? dbModel.viewSettings.overrideStyles,
|
overrideStyles: v.overrideStyles ?? dbModel.viewSettings.overrideStyles,
|
||||||
|
endMessage: v.endMessage ?? dbModel.viewSettings.endMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
// write to db
|
// write to db
|
||||||
|
|||||||
@@ -224,8 +224,7 @@
|
|||||||
"publicUrl": "www.getontime.no",
|
"publicUrl": "www.getontime.no",
|
||||||
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
||||||
"backstageUrl": "www.getontime.no",
|
"backstageUrl": "www.getontime.no",
|
||||||
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject",
|
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject"
|
||||||
"endMessage": ""
|
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"app": "ontime",
|
"app": "ontime",
|
||||||
@@ -235,7 +234,8 @@
|
|||||||
"pinCode": "1234"
|
"pinCode": "1234"
|
||||||
},
|
},
|
||||||
"viewSettings": {
|
"viewSettings": {
|
||||||
"overrideStyles": false
|
"overrideStyles": false,
|
||||||
|
"endMessage": ""
|
||||||
},
|
},
|
||||||
"osc": {
|
"osc": {
|
||||||
"port": 8888,
|
"port": 8888,
|
||||||
|
|||||||
@@ -98,8 +98,7 @@
|
|||||||
"publicUrl": "www.getontime.no",
|
"publicUrl": "www.getontime.no",
|
||||||
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
||||||
"backstageUrl": "www.getontime.no",
|
"backstageUrl": "www.getontime.no",
|
||||||
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject",
|
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject"
|
||||||
"endMessage": ""
|
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"app": "ontime",
|
"app": "ontime",
|
||||||
@@ -110,7 +109,8 @@
|
|||||||
"timeFormat": "24"
|
"timeFormat": "24"
|
||||||
},
|
},
|
||||||
"viewSettings": {
|
"viewSettings": {
|
||||||
"overrideStyles": false
|
"overrideStyles": false,
|
||||||
|
"endMessage": ""
|
||||||
},
|
},
|
||||||
"aliases": [
|
"aliases": [
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-3
@@ -33,8 +33,7 @@
|
|||||||
"publicUrl": "www.getontime.no",
|
"publicUrl": "www.getontime.no",
|
||||||
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
||||||
"backstageUrl": "www.getontime.no",
|
"backstageUrl": "www.getontime.no",
|
||||||
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject",
|
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject"
|
||||||
"endMessage": ""
|
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"app": "ontime",
|
"app": "ontime",
|
||||||
@@ -45,7 +44,8 @@
|
|||||||
"timeFormat": "24"
|
"timeFormat": "24"
|
||||||
},
|
},
|
||||||
"viewSettings": {
|
"viewSettings": {
|
||||||
"overrideStyles": false
|
"overrideStyles": false,
|
||||||
|
"endMessage": ""
|
||||||
},
|
},
|
||||||
"aliases": [
|
"aliases": [
|
||||||
{
|
{
|
||||||
|
|||||||
Vendored
+3
-3
@@ -98,8 +98,7 @@
|
|||||||
"publicUrl": "www.getontime.no",
|
"publicUrl": "www.getontime.no",
|
||||||
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
||||||
"backstageUrl": "www.getontime.no",
|
"backstageUrl": "www.getontime.no",
|
||||||
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject",
|
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject"
|
||||||
"endMessage": ""
|
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"app": "ontime",
|
"app": "ontime",
|
||||||
@@ -110,7 +109,8 @@
|
|||||||
"timeFormat": "24"
|
"timeFormat": "24"
|
||||||
},
|
},
|
||||||
"viewSettings": {
|
"viewSettings": {
|
||||||
"overrideStyles": false
|
"overrideStyles": false,
|
||||||
|
"endMessage": ""
|
||||||
},
|
},
|
||||||
"aliases": [
|
"aliases": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,5 +4,4 @@ export type EventData = {
|
|||||||
publicInfo: string;
|
publicInfo: string;
|
||||||
backstageUrl: string;
|
backstageUrl: string;
|
||||||
backstageInfo: string;
|
backstageInfo: string;
|
||||||
endMessage: string;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export type ViewSettings = {
|
export type ViewSettings = {
|
||||||
overrideStyles: boolean;
|
overrideStyles: boolean;
|
||||||
|
endMessage: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user