mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-22 23:49:15 +00:00
display multiple custom fields in operator view (#1001)
--------- Co-authored-by: Carlos Valente <carlosvalente@pm.me>
This commit is contained in:
committed by
GitHub
parent
9d7b5568e4
commit
b1838a5e9a
@@ -1,5 +1,18 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { Input, InputGroup, InputLeftElement, Select, Switch } from '@chakra-ui/react';
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputLeftElement,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuItemOption,
|
||||||
|
MenuList,
|
||||||
|
MenuOptionGroup,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
|
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
|
||||||
|
|
||||||
@@ -34,6 +47,10 @@ export default function ParamInput(props: EditFormInputProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'multi-option') {
|
||||||
|
return <MultiOption paramField={paramField} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'boolean') {
|
if (type === 'boolean') {
|
||||||
const defaultCheckedValue = isStringBoolean(searchParams.get(id)) || defaultValue;
|
const defaultCheckedValue = isStringBoolean(searchParams.get(id)) || defaultValue;
|
||||||
|
|
||||||
@@ -70,3 +87,47 @@ export default function ParamInput(props: EditFormInputProps) {
|
|||||||
</InputGroup>
|
</InputGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EditFormMultiOptionProps {
|
||||||
|
paramField: ParamField & { type: 'multi-option' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function MultiOption(props: EditFormMultiOptionProps) {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const { paramField } = props;
|
||||||
|
const { id, defaultValue } = paramField;
|
||||||
|
|
||||||
|
const optionFromParams = (searchParams.get(id) ?? '').toLocaleLowerCase();
|
||||||
|
const defaultOptionValue = optionFromParams || defaultValue?.toLocaleLowerCase() || '';
|
||||||
|
|
||||||
|
const [paramState, setParamState] = useState<string>(defaultOptionValue);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input name={id} hidden readOnly value={paramState} />
|
||||||
|
<Menu isLazy closeOnSelect={false} variant='ontime-on-dark'>
|
||||||
|
<MenuButton as={Button} variant='ontime-subtle-white' position='relative' width='fit-content' fontWeight={400}>
|
||||||
|
{paramField.title}
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
<MenuOptionGroup
|
||||||
|
type='checkbox'
|
||||||
|
value={paramState.split('_')}
|
||||||
|
onChange={(value) => {
|
||||||
|
setParamState(typeof value === 'object' ? value.filter((v) => v !== '').join('_') : value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.values(paramField.values).map((option) => {
|
||||||
|
const { value, label } = option;
|
||||||
|
return (
|
||||||
|
<MenuItemOption value={value} key={value}>
|
||||||
|
{label}
|
||||||
|
</MenuItemOption>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</MenuOptionGroup>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { CustomFields } from 'ontime-types';
|
import { CustomFields } from 'ontime-types';
|
||||||
|
|
||||||
import { capitaliseFirstLetter } from '../../../features/viewers/common/viewUtils';
|
import { type ParamField } from './types';
|
||||||
|
|
||||||
import { ParamField } from './types';
|
|
||||||
|
|
||||||
const makeOptionsFromCustomFields = (customFields: CustomFields, additionalOptions?: Record<string, string>) => {
|
const makeOptionsFromCustomFields = (customFields: CustomFields, additionalOptions?: Record<string, string>) => {
|
||||||
const customFieldOptions = Object.entries(customFields).reduce((acc, [key, value]) => {
|
const customFieldOptions = Object.entries(customFields).reduce((acc, [key, value]) => {
|
||||||
@@ -464,8 +462,8 @@ export const getStudioClockOptions = (timeFormat: string): ParamField[] => [
|
|||||||
export const getOperatorOptions = (customFields: CustomFields, timeFormat: string): ParamField[] => {
|
export const getOperatorOptions = (customFields: CustomFields, timeFormat: string): ParamField[] => {
|
||||||
const fieldOptions = makeOptionsFromCustomFields(customFields, { title: 'Title', note: 'Note' });
|
const fieldOptions = makeOptionsFromCustomFields(customFields, { title: 'Title', note: 'Note' });
|
||||||
|
|
||||||
const customFieldSelect = Object.keys(customFields).reduce((acc, key) => {
|
const customFieldSelect = Object.entries(customFields).reduce((acc, [key, field]) => {
|
||||||
return { ...acc, [key]: `Custom: ${capitaliseFirstLetter(key)}` };
|
return { ...acc, [key]: { value: key, label: field.label, colour: field.colour } };
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -497,9 +495,8 @@ export const getOperatorOptions = (customFields: CustomFields, timeFormat: strin
|
|||||||
id: 'subscribe',
|
id: 'subscribe',
|
||||||
title: 'Highlight Field',
|
title: 'Highlight Field',
|
||||||
description: 'Choose a custom field to highlight',
|
description: 'Choose a custom field to highlight',
|
||||||
type: 'option',
|
type: 'multi-option',
|
||||||
values: customFieldSelect,
|
values: customFieldSelect,
|
||||||
defaultValue: '',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'shouldEdit',
|
id: 'shouldEdit',
|
||||||
|
|||||||
@@ -9,8 +9,15 @@ type OptionsField = {
|
|||||||
values: Record<string, string>;
|
values: Record<string, string>;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type MultiOptionsField = {
|
||||||
|
type: 'multi-option';
|
||||||
|
values: Record<string, { value: string; label: string; colour: string }>;
|
||||||
|
defaultValue?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type StringField = { type: 'string'; defaultValue?: string; prefix?: string; placeholder?: string };
|
type StringField = { type: 'string'; defaultValue?: string; prefix?: string; placeholder?: string };
|
||||||
type NumberField = { type: 'number'; defaultValue?: number; prefix?: string; placeholder?: string };
|
type NumberField = { type: 'number'; defaultValue?: number; prefix?: string; placeholder?: string };
|
||||||
type BooleanField = { type: 'boolean'; defaultValue: boolean };
|
type BooleanField = { type: 'boolean'; defaultValue: boolean };
|
||||||
|
|
||||||
export type ParamField = BaseField & (StringField | BooleanField | NumberField | OptionsField);
|
export type ParamField = BaseField & (StringField | BooleanField | NumberField | OptionsField | MultiOptionsField);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { CustomField, CustomFields, isOntimeEvent, OntimeEvent, SupportedEvent } from 'ontime-types';
|
import { isOntimeEvent, OntimeEvent, SupportedEvent } from 'ontime-types';
|
||||||
import { getFirstEventNormal, getLastEventNormal } from 'ontime-utils';
|
import { getFirstEventNormal, getLastEventNormal } from 'ontime-utils';
|
||||||
|
|
||||||
import Empty from '../../common/components/state/Empty';
|
import Empty from '../../common/components/state/Empty';
|
||||||
@@ -27,11 +27,9 @@ import style from './Operator.module.scss';
|
|||||||
|
|
||||||
const selectedOffset = 50;
|
const selectedOffset = 50;
|
||||||
|
|
||||||
|
export type Subscribed = { id: string; label: string; colour: string; value: string }[];
|
||||||
type TitleFields = Pick<OntimeEvent, 'title'>;
|
type TitleFields = Pick<OntimeEvent, 'title'>;
|
||||||
export type EditEvent = Pick<OntimeEvent, 'id' | 'cue'> & { fieldLabel?: string; fieldValue: string };
|
export type EditEvent = Pick<OntimeEvent, 'id' | 'cue'> & { subscriptions: Subscribed };
|
||||||
export type PartialEdit = EditEvent & {
|
|
||||||
field: keyof CustomFields;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Operator() {
|
export default function Operator() {
|
||||||
const { data, status } = useRundown();
|
const { data, status } = useRundown();
|
||||||
@@ -45,7 +43,7 @@ export default function Operator() {
|
|||||||
const { data: settings } = useSettings();
|
const { data: settings } = useSettings();
|
||||||
|
|
||||||
const [showEditPrompt, setShowEditPrompt] = useState(false);
|
const [showEditPrompt, setShowEditPrompt] = useState(false);
|
||||||
const [editEvent, setEditEvent] = useState<PartialEdit | null>(null);
|
const [editEvent, setEditEvent] = useState<EditEvent | null>(null);
|
||||||
|
|
||||||
const [lockAutoScroll, setLockAutoScroll] = useState(false);
|
const [lockAutoScroll, setLockAutoScroll] = useState(false);
|
||||||
const selectedRef = useRef<HTMLDivElement | null>(null);
|
const selectedRef = useRef<HTMLDivElement | null>(null);
|
||||||
@@ -102,16 +100,9 @@ export default function Operator() {
|
|||||||
debouncedHandleScroll();
|
debouncedHandleScroll();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = useCallback(
|
const handleEdit = useCallback((event: EditEvent) => {
|
||||||
(event: EditEvent) => {
|
setEditEvent({ ...event });
|
||||||
const field = searchParams.get('subscribe') as keyof CustomField | null;
|
}, []);
|
||||||
|
|
||||||
if (field) {
|
|
||||||
setEditEvent({ ...event, field });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[searchParams],
|
|
||||||
);
|
|
||||||
|
|
||||||
const missingData = !data || !customFields || !projectData;
|
const missingData = !data || !customFields || !projectData;
|
||||||
const isLoading = status === 'pending' || customFieldStatus === 'pending' || projectDataStatus === 'pending';
|
const isLoading = status === 'pending' || customFieldStatus === 'pending' || projectDataStatus === 'pending';
|
||||||
@@ -122,8 +113,13 @@ export default function Operator() {
|
|||||||
|
|
||||||
// get fields which the user subscribed to
|
// get fields which the user subscribed to
|
||||||
const shouldEdit = searchParams.get('shouldEdit');
|
const shouldEdit = searchParams.get('shouldEdit');
|
||||||
const subscribe = searchParams.get('subscribe') as keyof CustomFields;
|
|
||||||
const canEdit = shouldEdit && subscribe;
|
const subscriptions = (searchParams.get('subscribe') ?? '')
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.split('_')
|
||||||
|
.filter((value) => Object.hasOwn(customFields, value));
|
||||||
|
|
||||||
|
const canEdit = shouldEdit && subscriptions;
|
||||||
|
|
||||||
const main = searchParams.get('main') as keyof TitleFields | null;
|
const main = searchParams.get('main') as keyof TitleFields | null;
|
||||||
const secondary = searchParams.get('secondary');
|
const secondary = searchParams.get('secondary');
|
||||||
@@ -173,7 +169,12 @@ export default function Operator() {
|
|||||||
|
|
||||||
const mainField = main ? getPropertyValue(entry, main) ?? '' : entry.title;
|
const mainField = main ? getPropertyValue(entry, main) ?? '' : entry.title;
|
||||||
const secondaryField = getPropertyValue(entry, secondary) ?? '';
|
const secondaryField = getPropertyValue(entry, secondary) ?? '';
|
||||||
const subscribedData = entry.custom[subscribe];
|
const subscribedData = subscriptions
|
||||||
|
? subscriptions.map((id) => {
|
||||||
|
const { label, colour } = customFields[id];
|
||||||
|
return { id, label, colour, value: entry.custom[id] };
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OperatorEvent
|
<OperatorEvent
|
||||||
@@ -189,7 +190,6 @@ export default function Operator() {
|
|||||||
delay={entry.delay}
|
delay={entry.delay}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
subscribed={subscribedData}
|
subscribed={subscribedData}
|
||||||
subscribeLabel={subscribe}
|
|
||||||
isPast={isPast}
|
isPast={isPast}
|
||||||
selectedRef={isSelected ? selectedRef : undefined}
|
selectedRef={isSelected ? selectedRef : undefined}
|
||||||
onLongPress={canEdit ? handleEdit : () => undefined}
|
onLongPress={canEdit ? handleEdit : () => undefined}
|
||||||
|
|||||||
@@ -1,48 +1,69 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { Button, Textarea } from '@chakra-ui/react';
|
import { Button, Textarea } from '@chakra-ui/react';
|
||||||
|
import { OntimeEvent } from 'ontime-types';
|
||||||
|
|
||||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||||
import type { PartialEdit } from '../Operator';
|
import type { EditEvent } from '../Operator';
|
||||||
|
|
||||||
import style from './EditModal.module.scss';
|
import style from './EditModal.module.scss';
|
||||||
|
|
||||||
interface EditModalProps {
|
interface EditModalProps {
|
||||||
event: PartialEdit;
|
event: EditEvent;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EditModal(props: EditModalProps) {
|
export default function EditModal(props: EditModalProps) {
|
||||||
const { event, onClose } = props;
|
const { event, onClose } = props;
|
||||||
|
|
||||||
const { updateCustomField } = useEventAction();
|
const { updateEvent } = useEventAction();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
const inputRef = useRef<HTMLTextAreaElement[]>(new Array<HTMLTextAreaElement>());
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
|
if (!inputRef.current) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const newValue = inputRef.current?.value;
|
|
||||||
if (newValue === undefined) {
|
const patchObject: Partial<OntimeEvent> = { id: event.id };
|
||||||
return;
|
|
||||||
|
inputRef.current.forEach((element) => {
|
||||||
|
if (element.dataset.field && element.defaultValue != element.value) {
|
||||||
|
if (patchObject.custom) {
|
||||||
|
patchObject.custom[element.dataset.field] = element.value;
|
||||||
|
} else {
|
||||||
|
Object.assign(patchObject, { custom: { [element.dataset.field]: element.value } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (patchObject.custom) {
|
||||||
|
await updateEvent(patchObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateCustomField(event.id, event.field, newValue);
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldLabel = event?.fieldLabel ?? event.field;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.editModal}>
|
<div className={style.editModal}>
|
||||||
<div>{`Editing field ${fieldLabel} in cue ${event.cue}`}</div>
|
<div>{`Editing fields in cue ${event.cue}`}</div>
|
||||||
<Textarea
|
{event.subscriptions.map((field) => {
|
||||||
ref={inputRef}
|
return (
|
||||||
variant='ontime-filled'
|
<div key={field.label}>
|
||||||
placeholder={`Add value for ${fieldLabel} field`}
|
<label>{field.label}</label>
|
||||||
defaultValue={event.fieldValue}
|
<Textarea
|
||||||
isDisabled={loading}
|
ref={(element) => {
|
||||||
resize='none'
|
if (element) inputRef.current.push(element);
|
||||||
/>
|
}}
|
||||||
|
variant='ontime-filled'
|
||||||
|
placeholder={`Add value for ${field.label} field`}
|
||||||
|
defaultValue={field.value}
|
||||||
|
data-field={field.id}
|
||||||
|
isDisabled={loading}
|
||||||
|
resize='none'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<div className={style.buttonRow}>
|
<div className={style.buttonRow}>
|
||||||
<Button variant='ontime-subtle' onClick={onClose} isDisabled={loading}>
|
<Button variant='ontime-subtle' onClick={onClose} isDisabled={loading}>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
grid-template-rows: auto auto auto;
|
grid-template-rows: auto auto auto;
|
||||||
column-gap: 0.5rem;
|
column-gap: 0.5rem;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"binder main schedule"
|
'binder main schedule'
|
||||||
"binder secondary running"
|
'binder secondary running'
|
||||||
"binder fields fields";
|
'binder fields fields';
|
||||||
|
|
||||||
&.subscribed {
|
&.subscribed {
|
||||||
background-color: $gray-1250;
|
background-color: $gray-1250;
|
||||||
@@ -93,16 +93,25 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: $ui-black;
|
color: $ui-black;
|
||||||
margin: 0.25rem 0;
|
margin: 0.25rem 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 0 0.25rem;
|
padding: 0 0.25rem;
|
||||||
background-color: var(--operator-highlight-override, $orange-600);
|
margin-right: 0.25rem;
|
||||||
margin-right: 0.5rem;
|
}
|
||||||
|
|
||||||
|
.noColour {
|
||||||
|
outline: 0.15rem solid var(--operator-highlight-override, $ui-white);
|
||||||
|
outline-offset: -0.15rem;
|
||||||
|
padding-right: 0.3rem;
|
||||||
|
color: $ui-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
color: $orange-500;
|
color: var(--operator-highlight-override, $ui-white);
|
||||||
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useTimer } from '../../../common/hooks/useSocket';
|
|||||||
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
||||||
import ClockTime from '../../viewers/common/clock-time/ClockTime';
|
import ClockTime from '../../viewers/common/clock-time/ClockTime';
|
||||||
import RunningTime from '../../viewers/common/running-time/RunningTime';
|
import RunningTime from '../../viewers/common/running-time/RunningTime';
|
||||||
import type { EditEvent } from '../Operator';
|
import type { EditEvent, Subscribed } from '../Operator';
|
||||||
|
|
||||||
import style from './OperatorEvent.module.scss';
|
import style from './OperatorEvent.module.scss';
|
||||||
|
|
||||||
@@ -21,8 +21,7 @@ interface OperatorEventProps {
|
|||||||
duration: number;
|
duration: number;
|
||||||
delay?: number;
|
delay?: number;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
subscribed?: string;
|
subscribed: Subscribed | null;
|
||||||
subscribeLabel: string;
|
|
||||||
isPast: boolean;
|
isPast: boolean;
|
||||||
selectedRef?: RefObject<HTMLDivElement>;
|
selectedRef?: RefObject<HTMLDivElement>;
|
||||||
onLongPress: (event: EditEvent) => void;
|
onLongPress: (event: EditEvent) => void;
|
||||||
@@ -47,7 +46,6 @@ function OperatorEvent(props: OperatorEventProps) {
|
|||||||
delay,
|
delay,
|
||||||
isSelected,
|
isSelected,
|
||||||
subscribed,
|
subscribed,
|
||||||
subscribeLabel: subscribedAlias,
|
|
||||||
isPast,
|
isPast,
|
||||||
selectedRef,
|
selectedRef,
|
||||||
onLongPress,
|
onLongPress,
|
||||||
@@ -56,7 +54,9 @@ function OperatorEvent(props: OperatorEventProps) {
|
|||||||
const handleLongPress = (event?: SyntheticEvent) => {
|
const handleLongPress = (event?: SyntheticEvent) => {
|
||||||
// we dont have an event out of useLongPress
|
// we dont have an event out of useLongPress
|
||||||
event?.preventDefault();
|
event?.preventDefault();
|
||||||
onLongPress({ id, cue, fieldLabel: subscribedAlias, fieldValue: subscribed ?? '' });
|
if (subscribed) {
|
||||||
|
onLongPress({ id, cue, subscriptions: subscribed });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mouseHandlers = useLongPress(handleLongPress, { threshold: 800 });
|
const mouseHandlers = useLongPress(handleLongPress, { threshold: 800 });
|
||||||
@@ -89,12 +89,22 @@ function OperatorEvent(props: OperatorEventProps) {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className={style.fields}>
|
<div className={style.fields}>
|
||||||
{subscribed && (
|
{subscribed &&
|
||||||
<>
|
subscribed
|
||||||
<span className={style.field}>{subscribedAlias}</span>
|
.filter((field) => field.value)
|
||||||
<span className={style.value}>{subscribed}</span>
|
.map((field) => {
|
||||||
</>
|
const fieldClasses = cx([style.field, !field.colour ? style.noColour : null]);
|
||||||
)}
|
return (
|
||||||
|
<div key={field.id}>
|
||||||
|
<span className={fieldClasses} style={{ backgroundColor: field.colour }}>
|
||||||
|
{field.label}
|
||||||
|
</span>
|
||||||
|
<span className={style.value} style={{ color: field.colour }}>
|
||||||
|
{field.value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -53,10 +53,6 @@ export function getPropertyValue(event: OntimeEvent | null, property: MaybeStrin
|
|||||||
return event[property as keyof OntimeEvent] as string;
|
return event[property as keyof OntimeEvent] as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function capitaliseFirstLetter(string: string) {
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
type FormattingOptions = {
|
type FormattingOptions = {
|
||||||
removeSeconds: boolean;
|
removeSeconds: boolean;
|
||||||
removeLeadingZero: boolean;
|
removeLeadingZero: boolean;
|
||||||
|
|||||||
Generated
+1
-3
@@ -679,7 +679,6 @@ packages:
|
|||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.14.1
|
regenerator-runtime: 0.14.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@babel/template@7.22.15:
|
/@babel/template@7.22.15:
|
||||||
resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
|
resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
|
||||||
@@ -8031,7 +8030,7 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.22.5
|
'@babel/runtime': 7.24.5
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -8278,7 +8277,6 @@ packages:
|
|||||||
|
|
||||||
/regenerator-runtime@0.14.1:
|
/regenerator-runtime@0.14.1:
|
||||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/regexp.prototype.flags@1.4.3:
|
/regexp.prototype.flags@1.4.3:
|
||||||
resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
|
resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
|
||||||
|
|||||||
Reference in New Issue
Block a user