mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-22 15:39:11 +00:00
feat: extra info in backstage
This commit is contained in:
committed by
Carlos Valente
parent
dc3a32d2bc
commit
6b4d96dddd
@@ -1,4 +1,4 @@
|
|||||||
import type { CustomFields } from 'ontime-types';
|
import type { CustomFields, ProjectData } from 'ontime-types';
|
||||||
|
|
||||||
import type { SelectOption } from '../select/Select';
|
import type { SelectOption } from '../select/Select';
|
||||||
|
|
||||||
@@ -53,6 +53,18 @@ export function makeCustomFieldSelectOptions(customFields: CustomFields, filterI
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates data for a select element that displays project custom data
|
||||||
|
*/
|
||||||
|
export function makeProjectDataOptions(projectData: ProjectData): SelectOption[] {
|
||||||
|
return projectData.custom.map((entry, index) => {
|
||||||
|
return {
|
||||||
|
value: `${index}-${entry.title}`,
|
||||||
|
label: entry.title,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
type ViewParamsObj = { [key: string]: string | FormDataEntryValue };
|
type ViewParamsObj = { [key: string]: string | FormDataEntryValue };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -149,10 +149,27 @@
|
|||||||
.info {
|
.info {
|
||||||
grid-area: info;
|
grid-area: info;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: max(1vw, 16px);
|
flex-direction: column;
|
||||||
align-self: flex-end;
|
gap: $view-element-gap;
|
||||||
|
align-self: end;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
align-items: end;
|
}
|
||||||
|
|
||||||
|
.info__column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
display: flex;
|
||||||
|
gap: $view-element-gap;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: $timer-label-size;
|
||||||
|
color: var(--label-color-override, $viewer-label-color);
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
&__message {
|
&__message {
|
||||||
font-size: $base-font-size;
|
font-size: $base-font-size;
|
||||||
@@ -162,11 +179,16 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr {
|
&__qr {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
background-color: $ui-white;
|
background-color: $ui-white;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__img {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default function Backstage({
|
|||||||
settings,
|
settings,
|
||||||
}: BackstageProps) {
|
}: BackstageProps) {
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const { secondarySource } = useBackstageOptions();
|
const { secondarySource, extraInfo } = useBackstageOptions();
|
||||||
const [blinkClass, setBlinkClass] = useState(false);
|
const [blinkClass, setBlinkClass] = useState(false);
|
||||||
const { height: screenHeight } = useViewportSize();
|
const { height: screenHeight } = useViewportSize();
|
||||||
|
|
||||||
@@ -104,8 +104,8 @@ export default function Backstage({
|
|||||||
// gather option data
|
// gather option data
|
||||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||||
const backstageOptions = useMemo(
|
const backstageOptions = useMemo(
|
||||||
() => getBackstageOptions(defaultFormat, customFields),
|
() => getBackstageOptions(defaultFormat, customFields, general),
|
||||||
[defaultFormat, customFields],
|
[defaultFormat, customFields, general],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -180,8 +180,44 @@ export default function Backstage({
|
|||||||
{showSchedule && <ScheduleExport selectedId={selectedId} />}
|
{showSchedule && <ScheduleExport selectedId={selectedId} />}
|
||||||
|
|
||||||
<div className={cx(['info', !showSchedule && 'info--stretch'])}>
|
<div className={cx(['info', !showSchedule && 'info--stretch'])}>
|
||||||
{general.url && <QRCode value={general.url} size={qrSize} level='L' className='qr' />}
|
{extraInfo && <ExtraInfo projectData={general} size={qrSize} source={extraInfo} />}
|
||||||
{general.info && <div className='info__message'>{general.info}</div>}
|
<div className='info-card'>
|
||||||
|
{general.url && <QRCode value={general.url} size={qrSize} level='L' className='info-card__qr' />}
|
||||||
|
{general.info && <div className='info-card__message'>{general.info}</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExtraInfoProps {
|
||||||
|
projectData: ProjectData;
|
||||||
|
size: number;
|
||||||
|
source: string;
|
||||||
|
}
|
||||||
|
function ExtraInfo({ projectData, size, source }: ExtraInfoProps) {
|
||||||
|
const info = projectData.custom.find((entry, index) => {
|
||||||
|
const label = `${index}-${entry.title}`;
|
||||||
|
return label === source;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='info-card'>
|
||||||
|
{info.url && (
|
||||||
|
<img
|
||||||
|
className='info-card__img'
|
||||||
|
width={size}
|
||||||
|
src={info.url}
|
||||||
|
onError={(event) => (event.currentTarget.style.display = 'none')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className='info__column'>
|
||||||
|
{info.title && <div className='info-card__label'>{info.title}</div>}
|
||||||
|
{info.value && <div className='info-card__message'>{info.value}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { CustomFields, OntimeEvent } from 'ontime-types';
|
import { CustomFields, OntimeEvent, ProjectData } from 'ontime-types';
|
||||||
|
|
||||||
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
|
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
|
||||||
import { OptionTitle } from '../../common/components/view-params-editor/constants';
|
import { OptionTitle } from '../../common/components/view-params-editor/constants';
|
||||||
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
|
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
|
||||||
import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils';
|
import {
|
||||||
|
makeOptionsFromCustomFields,
|
||||||
|
makeProjectDataOptions,
|
||||||
|
} from '../../common/components/view-params-editor/viewParams.utils';
|
||||||
import { scheduleOptions } from '../common/schedule/schedule.options';
|
import { scheduleOptions } from '../common/schedule/schedule.options';
|
||||||
|
|
||||||
export const getBackstageOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => {
|
export const getBackstageOptions = (
|
||||||
|
timeFormat: string,
|
||||||
|
customFields: CustomFields,
|
||||||
|
projectData: ProjectData,
|
||||||
|
): ViewOption[] => {
|
||||||
const secondaryOptions = makeOptionsFromCustomFields(customFields, [{ value: 'note', label: 'Note' }]);
|
const secondaryOptions = makeOptionsFromCustomFields(customFields, [{ value: 'note', label: 'Note' }]);
|
||||||
|
const projectDataOptions = makeProjectDataOptions(projectData);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
|
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
|
||||||
@@ -28,11 +36,26 @@ export const getBackstageOptions = (timeFormat: string, customFields: CustomFiel
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
scheduleOptions,
|
scheduleOptions,
|
||||||
|
{
|
||||||
|
title: OptionTitle.ElementVisibility,
|
||||||
|
collapsible: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 'extra-info',
|
||||||
|
title: 'Extra info',
|
||||||
|
description: 'Select a project data source to show in the view',
|
||||||
|
type: 'option',
|
||||||
|
values: projectDataOptions,
|
||||||
|
defaultValue: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
type BackstageOptions = {
|
type BackstageOptions = {
|
||||||
secondarySource: keyof OntimeEvent | null;
|
secondarySource: keyof OntimeEvent | null;
|
||||||
|
extraInfo: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +66,7 @@ function getOptionsFromParams(searchParams: URLSearchParams): BackstageOptions {
|
|||||||
// we manually make an object that matches the key above
|
// we manually make an object that matches the key above
|
||||||
return {
|
return {
|
||||||
secondarySource: searchParams.get('secondary-src') as keyof OntimeEvent | null,
|
secondarySource: searchParams.get('secondary-src') as keyof OntimeEvent | null,
|
||||||
|
extraInfo: searchParams.get('extra-info'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user