mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 09:23:51 +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';
|
||||
|
||||
@@ -53,6 +53,18 @@ export function makeCustomFieldSelectOptions(customFields: CustomFields, filterI
|
||||
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 };
|
||||
|
||||
/**
|
||||
|
||||
@@ -149,10 +149,27 @@
|
||||
.info {
|
||||
grid-area: info;
|
||||
display: flex;
|
||||
gap: max(1vw, 16px);
|
||||
align-self: flex-end;
|
||||
flex-direction: column;
|
||||
gap: $view-element-gap;
|
||||
align-self: end;
|
||||
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 {
|
||||
font-size: $base-font-size;
|
||||
@@ -162,11 +179,16 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.qr {
|
||||
&__qr {
|
||||
padding: 0.5rem;
|
||||
background-color: $ui-white;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&__img {
|
||||
padding: 0.5rem;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function Backstage({
|
||||
settings,
|
||||
}: BackstageProps) {
|
||||
const { getLocalizedString } = useTranslation();
|
||||
const { secondarySource } = useBackstageOptions();
|
||||
const { secondarySource, extraInfo } = useBackstageOptions();
|
||||
const [blinkClass, setBlinkClass] = useState(false);
|
||||
const { height: screenHeight } = useViewportSize();
|
||||
|
||||
@@ -104,8 +104,8 @@ export default function Backstage({
|
||||
// gather option data
|
||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||
const backstageOptions = useMemo(
|
||||
() => getBackstageOptions(defaultFormat, customFields),
|
||||
[defaultFormat, customFields],
|
||||
() => getBackstageOptions(defaultFormat, customFields, general),
|
||||
[defaultFormat, customFields, general],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -180,8 +180,44 @@ export default function Backstage({
|
||||
{showSchedule && <ScheduleExport selectedId={selectedId} />}
|
||||
|
||||
<div className={cx(['info', !showSchedule && 'info--stretch'])}>
|
||||
{general.url && <QRCode value={general.url} size={qrSize} level='L' className='qr' />}
|
||||
{general.info && <div className='info__message'>{general.info}</div>}
|
||||
{extraInfo && <ExtraInfo projectData={general} size={qrSize} source={extraInfo} />}
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import { useMemo } from 'react';
|
||||
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 { OptionTitle } from '../../common/components/view-params-editor/constants';
|
||||
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';
|
||||
|
||||
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 projectDataOptions = makeProjectDataOptions(projectData);
|
||||
|
||||
return [
|
||||
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
|
||||
@@ -28,11 +36,26 @@ export const getBackstageOptions = (timeFormat: string, customFields: CustomFiel
|
||||
],
|
||||
},
|
||||
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 = {
|
||||
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
|
||||
return {
|
||||
secondarySource: searchParams.get('secondary-src') as keyof OntimeEvent | null,
|
||||
extraInfo: searchParams.get('extra-info'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user