mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-13 11:23:50 +00:00
Merge remote-tracking branch 'org/master' into feat/http-integration
This commit is contained in:
@@ -212,6 +212,7 @@ export default function TimeInput(props: TimeInputProps) {
|
||||
onKeyDown={onKeyDownHandler}
|
||||
value={value}
|
||||
maxLength={8}
|
||||
autoComplete='off'
|
||||
/>
|
||||
</InputGroup>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ $progress-bar-br: 3px;
|
||||
border-radius: $progress-bar-br;
|
||||
background-color: var(--timer-progress-bg-override, $viewer-card-bg-color);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
||||
&--hidden {
|
||||
display: none;
|
||||
@@ -31,7 +32,6 @@ $progress-bar-br: 3px;
|
||||
position: absolute;
|
||||
height: inherit;
|
||||
right: 0;
|
||||
border-radius: $progress-bar-br;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -39,12 +39,10 @@ $progress-bar-br: 3px;
|
||||
position: absolute;
|
||||
height: inherit;
|
||||
right: 0;
|
||||
border-radius: 0 $progress-bar-br $progress-bar-br 0;
|
||||
}
|
||||
|
||||
.multiprogress-bar__bg-danger {
|
||||
position: absolute;
|
||||
height: inherit;
|
||||
right: 0;
|
||||
border-radius: 0 $progress-bar-br $progress-bar-br 0;
|
||||
}
|
||||
+16
-12
@@ -3,7 +3,7 @@ import { clamp } from '../../utils/math';
|
||||
import './MultiPartProgressBar.scss';
|
||||
|
||||
interface MultiPartProgressBar {
|
||||
now: number;
|
||||
now: number | null;
|
||||
complete: number;
|
||||
normalColor: string;
|
||||
warning: number;
|
||||
@@ -17,23 +17,27 @@ interface MultiPartProgressBar {
|
||||
export default function MultiPartProgressBar(props: MultiPartProgressBar) {
|
||||
const { now, complete, normalColor, warning, warningColor, danger, dangerColor, hidden, className = '' } = props;
|
||||
|
||||
const percentComplete = 100 - clamp(100 - (Math.max(now, 0) * 100) / complete, 0, 100);
|
||||
const percentComplete = 100 - clamp(100 - (Math.max(now ?? 0, 0) * 100) / complete, 0, 100);
|
||||
|
||||
const dangerWidth = clamp((danger / complete) * 100, 0, 100);
|
||||
const warningWidth = clamp((warning / complete) * 100, 0, 100);
|
||||
|
||||
return (
|
||||
<div className={`multiprogress-bar ${hidden ? 'multiprogress-bar--hidden' : ''} ${className}`}>
|
||||
<div className='multiprogress-bar__bg-normal' style={{ backgroundColor: normalColor }} />
|
||||
<div
|
||||
className='multiprogress-bar__bg-warning'
|
||||
style={{ width: `${warningWidth}%`, backgroundColor: warningColor }}
|
||||
/>
|
||||
<div
|
||||
className='multiprogress-bar__bg-danger'
|
||||
style={{ width: `${dangerWidth}%`, backgroundColor: dangerColor }}
|
||||
/>
|
||||
<div className='multiprogress-bar__indicator' style={{ width: `${percentComplete}%` }} />
|
||||
{now !== null && (
|
||||
<>
|
||||
<div className='multiprogress-bar__bg-normal' style={{ backgroundColor: normalColor }} />
|
||||
<div
|
||||
className='multiprogress-bar__bg-warning'
|
||||
style={{ width: `${warningWidth}%`, backgroundColor: warningColor }}
|
||||
/>
|
||||
<div
|
||||
className='multiprogress-bar__bg-danger'
|
||||
style={{ width: `${dangerWidth}%`, backgroundColor: dangerColor }}
|
||||
/>
|
||||
<div className='multiprogress-bar__indicator' style={{ width: `${percentComplete}%` }} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
|
||||
import { navigatorConstants } from '../../../viewerConfig';
|
||||
import useClickOutside from '../../hooks/useClickOutside';
|
||||
import useFullscreen from '../../hooks/useFullscreen';
|
||||
import { useKeyDown } from '../../hooks/useKeyDown';
|
||||
import { useViewOptionsStore } from '../../stores/viewOptions';
|
||||
|
||||
import RenameClientModal from './rename-client-modal/RenameClientModal';
|
||||
@@ -28,13 +27,14 @@ function NavigationMenu() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useClickOutside(menuRef, () => setShowMenu(false));
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const toggleMenu = () => setShowMenu((prev) => !prev);
|
||||
useKeyDown(toggleMenu, ' ', { isDisabled: searchParams.get('edit') === 'true' || isOpen });
|
||||
|
||||
// show on mouse move
|
||||
useEffect(() => {
|
||||
let fadeOut: NodeJS.Timeout | null = null;
|
||||
const setShowMenuTrue = () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Input, Select, Switch } from '@chakra-ui/react';
|
||||
import { Input, InputGroup, InputLeftElement, Select, Switch } from '@chakra-ui/react';
|
||||
|
||||
import { isStringBoolean } from '../../utils/viewUtils';
|
||||
|
||||
@@ -9,16 +9,22 @@ interface EditFormInputProps {
|
||||
paramField: ParamField;
|
||||
}
|
||||
|
||||
export default function ParamInput({ paramField }: EditFormInputProps) {
|
||||
export default function ParamInput(props: EditFormInputProps) {
|
||||
const [searchParams] = useSearchParams();
|
||||
const { id, type } = paramField;
|
||||
const { paramField } = props;
|
||||
const { id, type, defaultValue } = paramField;
|
||||
|
||||
if (type === 'option') {
|
||||
const optionFromParams = searchParams.get(id);
|
||||
const defaultOptionValue = optionFromParams || undefined;
|
||||
const defaultOptionValue = optionFromParams || defaultValue;
|
||||
|
||||
return (
|
||||
<Select placeholder='Select an option' variant='ontime' name={id} defaultValue={defaultOptionValue}>
|
||||
<Select
|
||||
placeholder={defaultValue ? undefined : 'Select an option'}
|
||||
variant='ontime'
|
||||
name={id}
|
||||
defaultValue={defaultOptionValue}
|
||||
>
|
||||
{Object.entries(paramField.values).map(([key, value]) => (
|
||||
<option key={key} value={key}>
|
||||
{value}
|
||||
@@ -29,19 +35,38 @@ export default function ParamInput({ paramField }: EditFormInputProps) {
|
||||
}
|
||||
|
||||
if (type === 'boolean') {
|
||||
const defaultCheckedValue = isStringBoolean(searchParams.get(id)) ?? false;
|
||||
const defaultCheckedValue = isStringBoolean(searchParams.get(id)) || defaultValue;
|
||||
|
||||
// checked value should be 'true', so it can be captured by the form event
|
||||
return <Switch variant='ontime' name={id} defaultChecked={defaultCheckedValue} value='true' />;
|
||||
}
|
||||
|
||||
if (type === 'number') {
|
||||
const defaultNumberValue = searchParams.get(id) ?? '';
|
||||
const { prefix, placeholder } = paramField;
|
||||
const defaultNumberValue = searchParams.get(id) ?? defaultValue;
|
||||
|
||||
return <Input type='number' step='any' variant='ontime-filled' name={id} defaultValue={defaultNumberValue} />;
|
||||
return (
|
||||
<InputGroup variant='ontime-filled'>
|
||||
{prefix && <InputLeftElement pointerEvents='none'>{prefix}</InputLeftElement>}
|
||||
<Input
|
||||
type='number'
|
||||
step='any'
|
||||
variant='ontime-filled'
|
||||
name={id}
|
||||
defaultValue={defaultNumberValue}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</InputGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const defaultStringValue = searchParams.get(id) ?? '';
|
||||
const defaultStringValue = searchParams.get(id) ?? defaultValue;
|
||||
const { prefix, placeholder } = paramField;
|
||||
|
||||
return <Input variant='ontime-filled' name={id} defaultValue={defaultStringValue} />;
|
||||
return (
|
||||
<InputGroup variant='ontime-filled'>
|
||||
{prefix && <InputLeftElement pointerEvents='none'>{prefix}</InputLeftElement>}
|
||||
<Input name={id} defaultValue={defaultStringValue} placeholder={placeholder} />
|
||||
</InputGroup>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,15 @@ import style from './ViewParamsEditor.module.scss';
|
||||
type ViewParamsObj = { [key: string]: string | FormDataEntryValue };
|
||||
type SavedViewParams = Record<string, ViewParamsObj>;
|
||||
|
||||
const getURLSearchParamsFromObj = (paramsObj: ViewParamsObj) =>
|
||||
Object.entries(paramsObj).reduce((newSearchParams, [id, value]) => {
|
||||
const getURLSearchParamsFromObj = (paramsObj: ViewParamsObj, paramFields: ParamField[]) => {
|
||||
const defaultValues = paramFields.map(({ defaultValue }) => String(defaultValue));
|
||||
|
||||
return Object.entries(paramsObj).reduce((newSearchParams, [id, value]) => {
|
||||
if (typeof value === 'string' && value.length) {
|
||||
if (defaultValues.includes(value)) {
|
||||
return newSearchParams;
|
||||
}
|
||||
|
||||
newSearchParams.set(id, value);
|
||||
|
||||
return newSearchParams;
|
||||
@@ -32,6 +38,7 @@ const getURLSearchParamsFromObj = (paramsObj: ViewParamsObj) =>
|
||||
|
||||
return newSearchParams;
|
||||
}, new URLSearchParams());
|
||||
};
|
||||
|
||||
interface EditFormDrawerProps {
|
||||
paramFields: ParamField[];
|
||||
@@ -51,6 +58,12 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) {
|
||||
}
|
||||
}, [searchParams, onOpen]);
|
||||
|
||||
/**
|
||||
* disabling this for now, this feature needs more testing
|
||||
* - we seem to have a bug where this is conflicting with the aliases
|
||||
* - I wonder if the logic below needs to be inside an effect,
|
||||
* both localStorage and searchParams should trigger a component update when they change
|
||||
|
||||
useEffect(() => {
|
||||
const viewParamsObjFromLocalStorage = storedViewParams[pathname];
|
||||
|
||||
@@ -64,31 +77,32 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname]);
|
||||
|
||||
const onEditDrawerClose = () => {
|
||||
*/
|
||||
|
||||
const onCloseWithoutSaving = () => {
|
||||
onClose();
|
||||
|
||||
searchParams.delete('edit');
|
||||
setSearchParams(searchParams);
|
||||
};
|
||||
|
||||
const clearParams = () => {
|
||||
const resetParams = () => {
|
||||
setStoredViewParams({ ...storedViewParams, [pathname]: {} });
|
||||
setSearchParams();
|
||||
onClose();
|
||||
};
|
||||
|
||||
const onParamsFormSubmit = (formEvent: FormEvent<HTMLFormElement>) => {
|
||||
formEvent.preventDefault();
|
||||
|
||||
const newParamsObject = Object.fromEntries(new FormData(formEvent.currentTarget));
|
||||
const newSearchParams = getURLSearchParamsFromObj(newParamsObject);
|
||||
const newSearchParams = getURLSearchParamsFromObj(newParamsObject, paramFields);
|
||||
|
||||
setStoredViewParams({ ...storedViewParams, [pathname]: newParamsObject });
|
||||
setSearchParams(newSearchParams);
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer isOpen={isOpen} placement='right' onClose={onEditDrawerClose} size='lg'>
|
||||
<Drawer isOpen={isOpen} placement='right' onClose={onCloseWithoutSaving} size='lg'>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent>
|
||||
<DrawerHeader className={style.drawerHeader}>
|
||||
@@ -111,10 +125,10 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) {
|
||||
</DrawerBody>
|
||||
|
||||
<DrawerFooter className={style.drawerFooter}>
|
||||
<Button variant='ontime-ghosted' onClick={clearParams} type='reset'>
|
||||
Clear
|
||||
<Button variant='ontime-ghosted' onClick={resetParams} type='reset'>
|
||||
Reset
|
||||
</Button>
|
||||
<Button variant='ontime-subtle' onClick={onEditDrawerClose}>
|
||||
<Button variant='ontime-subtle' onClick={onCloseWithoutSaving}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='ontime-filled' form='edit-params-form' type='submit'>
|
||||
|
||||
@@ -1,46 +1,56 @@
|
||||
import { UserFields } from 'ontime-types';
|
||||
import { TimeFormat } from 'ontime-types/src/definitions/core/TimeFormat.type';
|
||||
|
||||
import { ParamField } from './types';
|
||||
|
||||
export const TIME_FORMAT_OPTION: ParamField = {
|
||||
export const getTimeOption = (timeFormat: TimeFormat): ParamField => ({
|
||||
id: 'format',
|
||||
title: '12 / 24 hour timer',
|
||||
description: 'Whether to show the time in 12 or 24 hour mode. Overrides the global setting from preferences',
|
||||
type: 'option',
|
||||
values: { '12': '12 hour AM/PM', '24': '24 hour' },
|
||||
};
|
||||
defaultValue: timeFormat,
|
||||
});
|
||||
|
||||
export const CLOCK_OPTIONS: ParamField[] = [
|
||||
TIME_FORMAT_OPTION,
|
||||
export const getClockOptions = (timeFormat: TimeFormat): ParamField[] => [
|
||||
getTimeOption(timeFormat),
|
||||
{
|
||||
id: 'key',
|
||||
title: 'Key Colour',
|
||||
description: 'Background colour in hexadecimal',
|
||||
prefix: '#',
|
||||
type: 'string',
|
||||
placeholder: '00000000 (default)',
|
||||
},
|
||||
{
|
||||
id: 'text',
|
||||
title: 'Text Colour',
|
||||
description: 'Text colour in hexadecimal',
|
||||
prefix: '#',
|
||||
type: 'string',
|
||||
placeholder: 'fffff (default)',
|
||||
},
|
||||
{
|
||||
id: 'textbg',
|
||||
title: 'Text Background',
|
||||
description: 'Colour of text background in hexadecimal',
|
||||
prefix: '#',
|
||||
type: 'string',
|
||||
placeholder: '00000000 (default)',
|
||||
},
|
||||
{
|
||||
id: 'font',
|
||||
title: 'Font',
|
||||
description: 'Font family, will use the fonts available in the system',
|
||||
type: 'string',
|
||||
placeholder: 'Arial Black (default)',
|
||||
},
|
||||
{
|
||||
id: 'size',
|
||||
title: 'Text Size',
|
||||
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
|
||||
type: 'number',
|
||||
placeholder: '1 (default)',
|
||||
},
|
||||
{
|
||||
id: 'alignx',
|
||||
@@ -48,12 +58,14 @@ export const CLOCK_OPTIONS: ParamField[] = [
|
||||
description: 'Moves the horizontally in page to start = left | center | end = right',
|
||||
type: 'option',
|
||||
values: { start: 'Start', center: 'Center', end: 'End' },
|
||||
defaultValue: 'center',
|
||||
},
|
||||
{
|
||||
id: 'offsetx',
|
||||
title: 'Offset Horizontal',
|
||||
description: 'Offsets the timer horizontal position by a given amount in pixels',
|
||||
type: 'number',
|
||||
placeholder: '0 (default)',
|
||||
},
|
||||
{
|
||||
id: 'aligny',
|
||||
@@ -61,47 +73,94 @@ export const CLOCK_OPTIONS: ParamField[] = [
|
||||
description: 'Moves the vertically in page to start = left | center | end = right',
|
||||
type: 'option',
|
||||
values: { start: 'Start', center: 'Center', end: 'End' },
|
||||
defaultValue: 'center',
|
||||
},
|
||||
{
|
||||
id: 'offsety',
|
||||
title: 'Offset Vertical',
|
||||
description: 'Offsets the timer vertical position by a given amount in pixels',
|
||||
type: 'number',
|
||||
placeholder: '0 (default)',
|
||||
},
|
||||
];
|
||||
|
||||
export const TIMER_OPTIONS: ParamField[] = [TIME_FORMAT_OPTION];
|
||||
export const getTimerOptions = (timeFormat: TimeFormat): ParamField[] => [
|
||||
getTimeOption(timeFormat),
|
||||
{
|
||||
id: 'hideClock',
|
||||
title: 'Hide Time Now',
|
||||
description: 'Hides the Time Now field',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'hideCards',
|
||||
title: 'Hide Cards',
|
||||
description: 'Hides the Now and Next cards',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'hideProgress',
|
||||
title: 'Hide progress bar',
|
||||
description: 'Hides the progress bar',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'hideMessage',
|
||||
title: 'Hide Presenter Message',
|
||||
description: 'Prevents the screen from displaying messages from the presenter',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'hideExternal',
|
||||
title: 'Hide External',
|
||||
description: 'Prevents the screen from displaying the external field',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
|
||||
{
|
||||
id: 'key',
|
||||
title: 'Key Colour',
|
||||
description: 'Background colour in hexadecimal',
|
||||
prefix: '#',
|
||||
type: 'string',
|
||||
placeholder: '00000000 (default)',
|
||||
},
|
||||
{
|
||||
id: 'text',
|
||||
title: 'Text Colour',
|
||||
description: 'Text colour in hexadecimal',
|
||||
prefix: '#',
|
||||
type: 'string',
|
||||
placeholder: 'fffff (default)',
|
||||
},
|
||||
{
|
||||
id: 'textbg',
|
||||
title: 'Text Background',
|
||||
description: 'Colour of text background in hexadecimal',
|
||||
prefix: '#',
|
||||
type: 'string',
|
||||
placeholder: '00000000 (default)',
|
||||
},
|
||||
{
|
||||
id: 'font',
|
||||
title: 'Font',
|
||||
description: 'Font family, will use the fonts available in the system',
|
||||
type: 'string',
|
||||
placeholder: 'Arial Black (default)',
|
||||
},
|
||||
{
|
||||
id: 'size',
|
||||
title: 'Text Size',
|
||||
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
|
||||
type: 'number',
|
||||
placeholder: '1 (default)',
|
||||
},
|
||||
{
|
||||
id: 'alignx',
|
||||
@@ -109,12 +168,14 @@ export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
|
||||
description: 'Moves the horizontally in page to start = left | center | end = right',
|
||||
type: 'option',
|
||||
values: { start: 'Start', center: 'Center', end: 'End' },
|
||||
defaultValue: 'center',
|
||||
},
|
||||
{
|
||||
id: 'offsetx',
|
||||
title: 'Offset Horizontal',
|
||||
description: 'Offsets the timer horizontal position by a given amount in pixels',
|
||||
type: 'number',
|
||||
placeholder: '0 (default)',
|
||||
},
|
||||
{
|
||||
id: 'aligny',
|
||||
@@ -122,145 +183,162 @@ export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
|
||||
description: 'Moves the vertically in page to start = left | center | end = right',
|
||||
type: 'option',
|
||||
values: { start: 'Start', center: 'Center', end: 'End' },
|
||||
defaultValue: 'center',
|
||||
},
|
||||
{
|
||||
id: 'offsety',
|
||||
title: 'Offset Vertical',
|
||||
description: 'Offsets the timer vertical position by a given amount in pixels',
|
||||
type: 'number',
|
||||
placeholder: '0 (default)',
|
||||
},
|
||||
{
|
||||
id: 'hideovertime',
|
||||
title: 'Hide Overtime',
|
||||
description: 'Whether to suppress overtime styles (red borders and red text)',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'hidemessages',
|
||||
title: 'Hide Message Overlay',
|
||||
description: 'Whether to hide the overlay from showing timer screen messages',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'hideendmessage',
|
||||
title: 'Hide End Message',
|
||||
description: 'Whether to hide end message and continue showing the clock if timer is in overtime',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const LOWER_THIRDS_OPTIONS: ParamField[] = [
|
||||
{
|
||||
id: 'preset',
|
||||
title: 'Preset',
|
||||
description: 'Selects a style preset (0-1)',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
id: 'size',
|
||||
title: 'Size',
|
||||
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
|
||||
type: 'number',
|
||||
placeholder: '1 (default)',
|
||||
},
|
||||
{
|
||||
id: 'transition',
|
||||
title: 'Transition',
|
||||
description: 'Transition in time in seconds (default 5)',
|
||||
description: 'Transition in time in seconds (default 3)',
|
||||
type: 'number',
|
||||
placeholder: '3 (default)',
|
||||
},
|
||||
{
|
||||
id: 'text',
|
||||
title: 'Text Colour',
|
||||
description: 'Text colour in hexadecimal',
|
||||
prefix: '#',
|
||||
type: 'string',
|
||||
placeholder: 'fffffa (default)',
|
||||
},
|
||||
{
|
||||
id: 'bg',
|
||||
title: 'Text Background',
|
||||
description: 'Text background colour in hexadecimal',
|
||||
prefix: '#',
|
||||
type: 'string',
|
||||
placeholder: '00000033 (default)',
|
||||
},
|
||||
{
|
||||
id: 'key',
|
||||
title: 'Key Colour',
|
||||
description: 'Screen background colour in hexadecimal',
|
||||
prefix: '#',
|
||||
type: 'string',
|
||||
placeholder: '00000033 (default)',
|
||||
},
|
||||
{
|
||||
id: 'fadeout',
|
||||
title: 'Fadeout',
|
||||
description: 'Time (in seconds) the lower third displays before fading out',
|
||||
type: 'number',
|
||||
placeholder: '3 (default)',
|
||||
},
|
||||
];
|
||||
|
||||
export const BACKSTAGE_OPTIONS: ParamField[] = [
|
||||
TIME_FORMAT_OPTION,
|
||||
export const getBackstageOptions = (timeFormat: TimeFormat): ParamField[] => [
|
||||
getTimeOption(timeFormat),
|
||||
{
|
||||
id: 'hidePast',
|
||||
title: 'Hide past events',
|
||||
description: 'Scheduler will only show upcoming events',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'stopCycle',
|
||||
title: 'Stop cycling through event pages',
|
||||
description: 'Schedule will not auto-cycle through events',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'eventsPerPage',
|
||||
title: 'Events per page',
|
||||
description: 'Sets the number of events on the page, can cause overlow',
|
||||
type: 'number',
|
||||
placeholder: '7 (default)',
|
||||
},
|
||||
];
|
||||
|
||||
export const PUBLIC_OPTIONS: ParamField[] = [
|
||||
TIME_FORMAT_OPTION,
|
||||
export const getPublicOptions = (timeFormat: TimeFormat): ParamField[] => [
|
||||
getTimeOption(timeFormat),
|
||||
{
|
||||
id: 'hidePast',
|
||||
title: 'Hide past events',
|
||||
description: 'Scheduler will only show upcoming events',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'stopCycle',
|
||||
title: 'Stop cycling through event pages',
|
||||
description: 'Schedule will not auto-cycle through events',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'eventsPerPage',
|
||||
title: 'Events per page',
|
||||
description: 'Sets the number of events on the page, can cause overlow',
|
||||
type: 'number',
|
||||
placeholder: '7 (default)',
|
||||
},
|
||||
];
|
||||
export const STUDIO_CLOCK_OPTIONS: ParamField[] = [
|
||||
TIME_FORMAT_OPTION,
|
||||
export const getStudioClockOptions = (timeFormat: TimeFormat): ParamField[] => [
|
||||
getTimeOption(timeFormat),
|
||||
{
|
||||
id: 'seconds',
|
||||
title: 'Show Seconds',
|
||||
description: 'Shows seconds in clock',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const getOperatorOptions = (userFields: UserFields): ParamField[] => {
|
||||
export const getOperatorOptions = (userFields: UserFields, timeFormat: TimeFormat): ParamField[] => {
|
||||
return [
|
||||
TIME_FORMAT_OPTION,
|
||||
getTimeOption(timeFormat),
|
||||
{
|
||||
id: 'showseconds',
|
||||
title: 'Show seconds',
|
||||
description: 'Schedule shows hh:mm:ss',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'hidepast',
|
||||
title: 'Hide Past Events',
|
||||
description: 'Whether to events that have passed',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'main',
|
||||
|
||||
@@ -4,9 +4,13 @@ type BaseField = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
type OptionsField = { type: 'option'; values: Record<string, string> };
|
||||
type StringField = { type: 'string' };
|
||||
type BooleanField = { type: 'boolean' };
|
||||
type NumberField = { type: 'number' };
|
||||
type OptionsField = {
|
||||
type: 'option';
|
||||
values: Record<string, string>;
|
||||
defaultValue?: string;
|
||||
};
|
||||
type StringField = { type: 'string'; defaultValue?: string; prefix?: string; placeholder?: string };
|
||||
type NumberField = { type: 'number'; defaultValue?: number; prefix?: string; placeholder?: string };
|
||||
type BooleanField = { type: 'boolean'; defaultValue: boolean };
|
||||
|
||||
export type ParamField = BaseField & (StringField | BooleanField | NumberField | OptionsField);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { MouseEvent, SyntheticEvent, TouchEvent, useMemo, useRef } from 'react';
|
||||
|
||||
type LongPressOptions = {
|
||||
threshold?: number;
|
||||
onStart?: (e: SyntheticEvent) => void;
|
||||
onFinish?: (e: SyntheticEvent) => void;
|
||||
onCancel?: (e: SyntheticEvent) => void;
|
||||
};
|
||||
|
||||
type LongPressFns = {
|
||||
onMouseDown: (e: MouseEvent) => void;
|
||||
onMouseUp: (e: MouseEvent) => void;
|
||||
onMouseLeave: (e: MouseEvent) => void;
|
||||
onTouchStart: (e: TouchEvent) => void;
|
||||
onTouchEnd: (e: TouchEvent) => void;
|
||||
};
|
||||
|
||||
export default function useLongPress(callback: () => void, options: LongPressOptions = {}): LongPressFns {
|
||||
const { threshold = 400, onStart, onFinish, onCancel } = options;
|
||||
const isLongPressActive = useRef(false);
|
||||
const isPressed = useRef(false);
|
||||
const timerId = useRef<NodeJS.Timer>();
|
||||
|
||||
return useMemo(() => {
|
||||
const start = (event: SyntheticEvent) => {
|
||||
if (onStart) {
|
||||
onStart(event);
|
||||
}
|
||||
|
||||
isPressed.current = true;
|
||||
timerId.current = setTimeout(() => {
|
||||
callback();
|
||||
isLongPressActive.current = true;
|
||||
}, threshold);
|
||||
};
|
||||
|
||||
const cancel = (event: SyntheticEvent) => {
|
||||
if (isLongPressActive.current) {
|
||||
if (onFinish) {
|
||||
onFinish(event);
|
||||
}
|
||||
} else if (isPressed.current) {
|
||||
if (onCancel) {
|
||||
onCancel(event);
|
||||
}
|
||||
}
|
||||
|
||||
isLongPressActive.current = false;
|
||||
isPressed.current = false;
|
||||
|
||||
if (timerId.current) {
|
||||
clearTimeout(timerId.current);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
onMouseDown: start,
|
||||
onMouseUp: cancel,
|
||||
onMouseLeave: cancel,
|
||||
onTouchStart: start,
|
||||
onTouchEnd: cancel,
|
||||
};
|
||||
}, [callback, threshold, onCancel, onFinish, onStart]);
|
||||
}
|
||||
Reference in New Issue
Block a user