mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-18 05:34:09 +00:00
Feature/#276/qrcodes (#289)
* bugfix: wrong format when on 12h format * update: website link in placeholder --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com>
This commit is contained in:
@@ -1,15 +1,17 @@
|
|||||||
export type EventDataType = {
|
export type EventDataType = {
|
||||||
title: string;
|
title: string;
|
||||||
url: string;
|
publicUrl: string;
|
||||||
publicInfo: string;
|
publicInfo: string;
|
||||||
|
backstageUrl: string;
|
||||||
backstageInfo: string;
|
backstageInfo: string;
|
||||||
endMessage: string;
|
endMessage: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const eventDataPlaceholder: EventDataType = {
|
export const eventDataPlaceholder: EventDataType = {
|
||||||
title: '',
|
title: '',
|
||||||
url: '',
|
publicUrl: '',
|
||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
|
backstageUrl: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
endMessage: '',
|
endMessage: '',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ export default function SettingsModal() {
|
|||||||
|
|
||||||
setFormData({
|
setFormData({
|
||||||
title: data.title,
|
title: data.title,
|
||||||
url: data.url,
|
publicUrl: data.publicUrl,
|
||||||
publicInfo: data.publicInfo,
|
publicInfo: data.publicInfo,
|
||||||
|
backstageUrl: data.backstageUrl,
|
||||||
backstageInfo: data.backstageInfo,
|
backstageInfo: data.backstageInfo,
|
||||||
endMessage: data.endMessage,
|
endMessage: data.endMessage,
|
||||||
});
|
});
|
||||||
@@ -45,7 +46,7 @@ export default function SettingsModal() {
|
|||||||
try {
|
try {
|
||||||
await postEvent(formData);
|
await postEvent(formData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
emitError(`Error saving event settings: ${error}`)
|
emitError(`Error saving event settings: ${error}`);
|
||||||
} finally {
|
} finally {
|
||||||
await refetch();
|
await refetch();
|
||||||
setChanged(false);
|
setChanged(false);
|
||||||
@@ -53,7 +54,7 @@ export default function SettingsModal() {
|
|||||||
|
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
},
|
},
|
||||||
[emitError, formData, refetch]
|
[emitError, formData, refetch],
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,12 +70,15 @@ export default function SettingsModal() {
|
|||||||
* @param {string} field - object parameter to update
|
* @param {string} field - object parameter to update
|
||||||
* @param {string} value - new object parameter value
|
* @param {string} value - new object parameter value
|
||||||
*/
|
*/
|
||||||
const handleChange = useCallback((field, value) => {
|
const handleChange = useCallback(
|
||||||
const temp = { ...formData };
|
(field, value) => {
|
||||||
temp[field] = value;
|
const temp = { ...formData };
|
||||||
setFormData(temp);
|
temp[field] = value;
|
||||||
setChanged(true);
|
setFormData(temp);
|
||||||
},[formData]);
|
setChanged(true);
|
||||||
|
},
|
||||||
|
[formData],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalBody className={style.modalBody}>
|
<ModalBody className={style.modalBody}>
|
||||||
@@ -99,19 +103,19 @@ export default function SettingsModal() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={style.hSeparator}>Additional Screen Info</div>
|
<div className={style.hSeparator}>Additional Screen Info</div>
|
||||||
<div className={style.spacedEntry}>
|
<div className={style.spacedEntry}>
|
||||||
<FormLabel htmlFor='url'>
|
<FormLabel htmlFor='publicUrl'>
|
||||||
Event URL
|
Public URL
|
||||||
<span className={style.labelNote}>
|
<span className={style.labelNote}>
|
||||||
<br />
|
<br />
|
||||||
Shown as a QR code in some views
|
QR code to be shown on public screens
|
||||||
</span>
|
</span>
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
name='url'
|
name='publicUrl'
|
||||||
placeholder='www.onsite.no'
|
placeholder='www.getontime.no'
|
||||||
value={formData.url}
|
value={formData.publicUrl}
|
||||||
onChange={(event) => handleChange('url', event.target.value)}
|
onChange={(event) => handleChange('publicUrl', event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.spacedEntry}>
|
<div className={style.spacedEntry}>
|
||||||
@@ -130,6 +134,22 @@ export default function SettingsModal() {
|
|||||||
onChange={(event) => handleChange('publicInfo', event.target.value)}
|
onChange={(event) => handleChange('publicInfo', event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={style.spacedEntry}>
|
||||||
|
<FormLabel htmlFor='backstageUrl'>
|
||||||
|
Backstage URL
|
||||||
|
<span className={style.labelNote}>
|
||||||
|
<br />
|
||||||
|
QR to be shown on backstage screens
|
||||||
|
</span>
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...inputProps}
|
||||||
|
name='backstageUrl'
|
||||||
|
placeholder='www.getontime.no'
|
||||||
|
value={formData.backstageUrl}
|
||||||
|
onChange={(event) => handleChange('backstageUrl', event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className={style.spacedEntry}>
|
<div className={style.spacedEntry}>
|
||||||
<FormLabel htmlFor='backstageInfo'>
|
<FormLabel htmlFor='backstageInfo'>
|
||||||
Backstage Info
|
Backstage Info
|
||||||
@@ -165,12 +185,7 @@ export default function SettingsModal() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SubmitContainer
|
<SubmitContainer revert={revert} submitting={submitting} changed={changed} status={status} />
|
||||||
revert={revert}
|
|
||||||
submitting={submitting}
|
|
||||||
changed={changed}
|
|
||||||
status={status}
|
|
||||||
/>
|
|
||||||
</form>
|
</form>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ export const makeTable = (headerData, tableData, userFields) => {
|
|||||||
const data = [
|
const data = [
|
||||||
['Ontime · Schedule Template'],
|
['Ontime · Schedule Template'],
|
||||||
['Event Name', headerData?.title || ''],
|
['Event Name', headerData?.title || ''],
|
||||||
['Event URL', headerData?.url || ''],
|
['Public URL', headerData?.publicUrl || ''],
|
||||||
|
['Backstage URL', headerData?.backstageUrl || ''],
|
||||||
[],
|
[],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -83,12 +83,7 @@ export default function Backstage(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar className='progress-container' now={time.current} complete={time.duration} hidden={!showProgress} />
|
||||||
className='progress-container'
|
|
||||||
now={time.current}
|
|
||||||
complete={time.duration}
|
|
||||||
hidden={!showProgress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className='now-container'>
|
<div className='now-container'>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -146,30 +141,21 @@ export default function Backstage(props) {
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScheduleProvider
|
<ScheduleProvider events={filteredEvents} selectedEventId={selectedId} isBackstage>
|
||||||
events={filteredEvents}
|
|
||||||
selectedEventId={selectedId}
|
|
||||||
isBackstage
|
|
||||||
>
|
|
||||||
<ScheduleNav className='schedule-nav-container' />
|
<ScheduleNav className='schedule-nav-container' />
|
||||||
<Schedule className='schedule-container' />
|
<Schedule className='schedule-container' />
|
||||||
</ScheduleProvider>
|
</ScheduleProvider>
|
||||||
|
|
||||||
<div
|
<div className={showPublicMessage ? 'public-container' : 'public-container public-container--hidden'}>
|
||||||
className={showPublicMessage ? 'public-container' : 'public-container public-container--hidden'}>
|
|
||||||
<div className='label'>Public message</div>
|
<div className='label'>Public message</div>
|
||||||
<div className='message'>{publ.text}</div>
|
<div className='message'>{publ.text}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='info'>
|
<div className='info'>
|
||||||
<div className='qr'>
|
<div className='qr'>
|
||||||
{general.url != null && general.url !== '' && (
|
{general.backstageUrl && <QRCode value={general.backstageUrl} size={qrSize} level='L' />}
|
||||||
<QRCode value={general.url} size={qrSize} level='L' />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{general.backstageInfo && (
|
{general.backstageInfo && <div className='info__message'>{general.backstageInfo}</div>}
|
||||||
<div className='info__message'>{general.backstageInfo}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ export default function Public(props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='now-container'>
|
<div className='now-container'>
|
||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{publicTitle.showNow && (
|
{publicTitle.showNow && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -106,29 +105,19 @@ export default function Public(props) {
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScheduleProvider
|
<ScheduleProvider events={events} selectedEventId={publicSelectedId}>
|
||||||
events={events}
|
|
||||||
selectedEventId={publicSelectedId}
|
|
||||||
>
|
|
||||||
<ScheduleNav className='schedule-nav-container' />
|
<ScheduleNav className='schedule-nav-container' />
|
||||||
<Schedule className='schedule-container' />
|
<Schedule className='schedule-container' />
|
||||||
</ScheduleProvider>
|
</ScheduleProvider>
|
||||||
|
|
||||||
<div
|
<div className={showPublicMessage ? 'public-container' : 'public-container public-container--hidden'}>
|
||||||
className={showPublicMessage ? 'public-container' : 'public-container public-container--hidden'}>
|
|
||||||
<div className='label'>Public message</div>
|
<div className='label'>Public message</div>
|
||||||
<div className='message'>{publ.text}</div>
|
<div className='message'>{publ.text}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='info'>
|
<div className='info'>
|
||||||
<div className='qr'>
|
<div className='qr'>{general.publicUrl && <QRCode value={general.publicUrl} size={qrSize} level='L' />}</div>
|
||||||
{general.url != null && general.url !== '' && (
|
{general.publicInfo && <div className='info__message'>{general.publicInfo}</div>}
|
||||||
<QRCode value={general.url} size={qrSize} level='L' />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{general.backstageInfo && (
|
|
||||||
<div className='info__message'>{general.backstageInfo}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function StudioClock(props) {
|
|||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const showSeconds = searchParams.get('seconds');
|
const showSeconds = searchParams.get('seconds');
|
||||||
formatOptions.showSeconds = Boolean(showSeconds);
|
formatOptions.showSeconds = Boolean(showSeconds);
|
||||||
formatOptions.format = `hh:mm${formatOptions.showSeconds ? 'mm' : ''}`;
|
formatOptions.format = `hh:mm${formatOptions.showSeconds ? ':ss' : ''}`;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = 'ontime - Studio Clock';
|
document.title = 'ontime - Studio Clock';
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ export const postEvent = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const newEvent = removeUndefined({
|
const newEvent = removeUndefined({
|
||||||
title: req.body?.title,
|
title: req.body?.title,
|
||||||
url: req.body?.url,
|
publicUrl: req.body?.publicUrl,
|
||||||
publicInfo: req.body?.publicInfo,
|
publicInfo: req.body?.publicInfo,
|
||||||
|
backstageUrl: req.body?.backstageUrl,
|
||||||
backstageInfo: req.body?.backstageInfo,
|
backstageInfo: req.body?.backstageInfo,
|
||||||
endMessage: req.body?.endMessage,
|
endMessage: req.body?.endMessage,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { body, validationResult } from 'express-validator';
|
|||||||
|
|
||||||
export const eventSanitizer = [
|
export const eventSanitizer = [
|
||||||
body('title').optional().isString().trim(),
|
body('title').optional().isString().trim(),
|
||||||
body('url').optional().isString().trim(),
|
body('publicUrl').optional().isString().trim(),
|
||||||
body('publicInfo').optional().isString().trim(),
|
body('publicInfo').optional().isString().trim(),
|
||||||
|
body('backstageUrl').optional().isString().trim(),
|
||||||
body('backstageInfo').optional().isString().trim(),
|
body('backstageInfo').optional().isString().trim(),
|
||||||
body('endMessage').optional().isString().trim(),
|
body('endMessage').optional().isString().trim(),
|
||||||
(req, res, next) => {
|
(req, res, next) => {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ export const dbModel = {
|
|||||||
rundown: [],
|
rundown: [],
|
||||||
event: {
|
event: {
|
||||||
title: '',
|
title: '',
|
||||||
url: '',
|
publicUrl: '',
|
||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
|
backstageUrl: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
endMessage: '',
|
endMessage: '',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ export const JSON_MIME = 'application/json';
|
|||||||
export const parseExcel = async (excelData) => {
|
export const parseExcel = async (excelData) => {
|
||||||
const eventData = {
|
const eventData = {
|
||||||
title: '',
|
title: '',
|
||||||
url: '',
|
publicUrl: '',
|
||||||
|
backstageUrl: '',
|
||||||
};
|
};
|
||||||
const customUserFields = {};
|
const customUserFields = {};
|
||||||
const rundown = [];
|
const rundown = [];
|
||||||
@@ -55,8 +56,9 @@ export const parseExcel = async (excelData) => {
|
|||||||
.filter((e) => e.length > 0)
|
.filter((e) => e.length > 0)
|
||||||
.forEach((row) => {
|
.forEach((row) => {
|
||||||
let eventTitleNext = false;
|
let eventTitleNext = false;
|
||||||
let eventUrlNext = false;
|
let publicUrlNext = false;
|
||||||
let publicInfoNext = false;
|
let publicInfoNext = false;
|
||||||
|
let backstageUrlNext = false;
|
||||||
let backstageInfoNext = false;
|
let backstageInfoNext = false;
|
||||||
let endMessageNext = false;
|
let endMessageNext = false;
|
||||||
const event = {};
|
const event = {};
|
||||||
@@ -66,12 +68,15 @@ export const parseExcel = async (excelData) => {
|
|||||||
if (eventTitleNext) {
|
if (eventTitleNext) {
|
||||||
eventData.title = column;
|
eventData.title = column;
|
||||||
eventTitleNext = false;
|
eventTitleNext = false;
|
||||||
} else if (eventUrlNext) {
|
} else if (publicUrlNext) {
|
||||||
eventData.url = column;
|
eventData.publicUrl = column;
|
||||||
eventUrlNext = false;
|
publicUrlNext = false;
|
||||||
} else if (publicInfoNext) {
|
} else if (publicInfoNext) {
|
||||||
eventData.publicInfo = column;
|
eventData.publicInfo = column;
|
||||||
publicInfoNext = false;
|
publicInfoNext = false;
|
||||||
|
} else if (backstageUrlNext) {
|
||||||
|
eventData.publicUrl = column;
|
||||||
|
backstageUrlNext = false;
|
||||||
} else if (backstageInfoNext) {
|
} else if (backstageInfoNext) {
|
||||||
eventData.backstageInfo = column;
|
eventData.backstageInfo = column;
|
||||||
backstageInfoNext = false;
|
backstageInfoNext = false;
|
||||||
@@ -125,12 +130,15 @@ export const parseExcel = async (excelData) => {
|
|||||||
case 'event name':
|
case 'event name':
|
||||||
eventTitleNext = true;
|
eventTitleNext = true;
|
||||||
break;
|
break;
|
||||||
case 'event url':
|
case 'public url':
|
||||||
eventUrlNext = true;
|
publicUrlNext = true;
|
||||||
break;
|
break;
|
||||||
case 'public info':
|
case 'public info':
|
||||||
publicInfoNext = true;
|
publicInfoNext = true;
|
||||||
break;
|
break;
|
||||||
|
case 'backstage url':
|
||||||
|
backstageUrlNext = true;
|
||||||
|
break;
|
||||||
case 'backstage info':
|
case 'backstage info':
|
||||||
backstageInfoNext = true;
|
backstageInfoNext = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -71,8 +71,9 @@ export const parseEvent = (data, enforce) => {
|
|||||||
newEvent = {
|
newEvent = {
|
||||||
...dbModel.event,
|
...dbModel.event,
|
||||||
title: e.title || dbModel.event.title,
|
title: e.title || dbModel.event.title,
|
||||||
url: e.url || dbModel.event.url,
|
publicUrl: e.publicUrl || dbModel.event.publicUrl,
|
||||||
publicInfo: e.publicInfo || dbModel.event.publicInfo,
|
publicInfo: e.publicInfo || dbModel.event.publicInfo,
|
||||||
|
backstageUrl: e.backstageUrl || dbModel.event.backstageUrl,
|
||||||
backstageInfo: e.backstageInfo || dbModel.event.backstageInfo,
|
backstageInfo: e.backstageInfo || dbModel.event.backstageInfo,
|
||||||
endMessage: e.endMessage || dbModel.event.endMessage,
|
endMessage: e.endMessage || dbModel.event.endMessage,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -109,8 +109,9 @@
|
|||||||
],
|
],
|
||||||
"event": {
|
"event": {
|
||||||
"title": "All about Carlos demo event",
|
"title": "All about Carlos demo event",
|
||||||
"url": "www.carlosvalente.com",
|
"publicUrl": "www.getontime.no",
|
||||||
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
||||||
|
"backstageUrl": "www.getontime.no",
|
||||||
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject",
|
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject",
|
||||||
"endMessage": ""
|
"endMessage": ""
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-1
@@ -221,8 +221,9 @@
|
|||||||
],
|
],
|
||||||
"event": {
|
"event": {
|
||||||
"title": "All about Carlos demo event",
|
"title": "All about Carlos demo event",
|
||||||
"url": "www.carlosvalente.com",
|
"publicUrl": "www.getontime.no",
|
||||||
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
"publicInfo": "WiFi: demoproject \nPassword: ontimeproject",
|
||||||
|
"backstageUrl": "www.getontime.no",
|
||||||
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject",
|
"backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject",
|
||||||
"endMessage": ""
|
"endMessage": ""
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user