mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 18:03:47 +00:00
feat: quick project wizard
This commit is contained in:
committed by
Carlos Valente
parent
27f99b4c87
commit
ed2efe183b
@@ -1,5 +1,5 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { DatabaseModel, MessageResponse, ProjectData, ProjectFileListResponse } from 'ontime-types';
|
||||
import { DatabaseModel, MessageResponse, ProjectData, ProjectFileListResponse, QuickStartData } from 'ontime-types';
|
||||
|
||||
import { makeCSV, makeTable } from '../../features/cuesheet/cuesheetUtils';
|
||||
|
||||
@@ -86,6 +86,14 @@ export async function createProject(
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP request to create a project file
|
||||
*/
|
||||
export async function quickProject(data: QuickStartData): Promise<MessageResponse> {
|
||||
const res = await axios.post(`${dbPath}/quick`, data);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP request to get the list of available project files
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,7 @@ import useAppSettingsNavigation from './useAppSettingsNavigation';
|
||||
import style from './AppSettings.module.scss';
|
||||
|
||||
export default function AppSettings() {
|
||||
const { close, panel, location } = useAppSettingsNavigation();
|
||||
const { close, panel, location, setLocation } = useAppSettingsNavigation();
|
||||
useKeyDown(close, 'Escape');
|
||||
|
||||
return (
|
||||
@@ -26,7 +26,7 @@ export default function AppSettings() {
|
||||
<ErrorBoundary>
|
||||
<PanelList selectedPanel={panel} location={location} />
|
||||
<PanelContent onClose={close}>
|
||||
{panel === 'project' && <ProjectPanel location={location} />}
|
||||
{panel === 'project' && <ProjectPanel location={location} setLocation={setLocation} />}
|
||||
{panel === 'general' && <GeneralPanel location={location} />}
|
||||
{panel === 'feature_settings' && <FeatureSettingsPanel location={location} />}
|
||||
{panel === 'sources' && <SourcesPanel />}
|
||||
|
||||
@@ -126,7 +126,7 @@ export default function ViewSettingsForm() {
|
||||
<Panel.ListItem>
|
||||
<Panel.Field
|
||||
title='Freeze timer on end'
|
||||
description='Timer in views will stop from going negative and instead stop at 00:00:00'
|
||||
description='When a timer hits 00:00:00, it freezes instead of going negative. It invalidates the End Message.'
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -139,7 +139,7 @@ export default function ViewSettingsForm() {
|
||||
<Panel.ListItem>
|
||||
<Panel.Field
|
||||
title='End message'
|
||||
description='Message to show in negative timers if not frozen. If not provided, timer will continue'
|
||||
description='Message for negative timers; applies only if the timer isn`t frozen on End. If no message is provided, it continues into negative time'
|
||||
/>
|
||||
<Input
|
||||
size='sm'
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
import useScrollIntoView from '../../../../common/hooks/useScrollIntoView';
|
||||
import type { PanelBaseProps } from '../../panel-list/PanelList';
|
||||
import * as Panel from '../../panel-utils/PanelUtils';
|
||||
import QuickStart from '../../quick-start/QuickStart';
|
||||
import type { SettingsOptionId } from '../../useAppSettingsMenu';
|
||||
|
||||
import ManageProjects from './ManageProjects';
|
||||
import ProjectData from './ProjectData';
|
||||
|
||||
export default function ProjectPanel({ location }: PanelBaseProps) {
|
||||
interface ProjectPanelProps extends PanelBaseProps {
|
||||
setLocation: (location: SettingsOptionId) => void;
|
||||
}
|
||||
|
||||
export default function ProjectPanel({ location, setLocation }: ProjectPanelProps) {
|
||||
const projectRef = useScrollIntoView<HTMLDivElement>('data', location);
|
||||
const manageRef = useScrollIntoView<HTMLDivElement>('manage', location);
|
||||
|
||||
const handleQuickClose = () => {
|
||||
setLocation('project');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Panel.Header>Project</Panel.Header>
|
||||
<QuickStart isOpen={location === 'create'} onClose={handleQuickClose} />
|
||||
<div ref={projectRef}>
|
||||
<ProjectData />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.scrollContainer {
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Select,
|
||||
Switch,
|
||||
} from '@chakra-ui/react';
|
||||
import { QuickStartData } from 'ontime-types';
|
||||
import { parseUserTime } from 'ontime-utils';
|
||||
|
||||
import { quickProject } from '../../../common/api/db';
|
||||
import { invalidateAllCaches, maybeAxiosError } from '../../../common/api/utils';
|
||||
import TimeInput from '../../../common/components/input/time-input/TimeInput';
|
||||
import { editorSettingsDefaults, useEditorSettings } from '../../../common/stores/editorSettings';
|
||||
import * as Panel from '../panel-utils/PanelUtils';
|
||||
|
||||
import { quickStartDefaults } from './quickStart.utils';
|
||||
|
||||
import style from './QuickStart.module.scss';
|
||||
|
||||
interface QuickStartProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function QuickStart(props: QuickStartProps) {
|
||||
const { isOpen, onClose } = props;
|
||||
const { defaultWarnTime, defaultDangerTime, setDangerTime, setWarnTime } = useEditorSettings();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
register,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
setError,
|
||||
} = useForm<QuickStartData>({
|
||||
defaultValues: quickStartDefaults,
|
||||
values: quickStartDefaults,
|
||||
resetOptions: {
|
||||
keepDirtyValues: true,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: QuickStartData) => {
|
||||
try {
|
||||
if (formData.project.title === '') {
|
||||
formData.project.title = 'untitled';
|
||||
}
|
||||
await quickProject(formData);
|
||||
await invalidateAllCaches();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
setError('root', { message: maybeAxiosError(error) });
|
||||
}
|
||||
};
|
||||
|
||||
const warnTimeInMs = parseUserTime(defaultWarnTime);
|
||||
const dangerTimeInMs = parseUserTime(defaultDangerTime);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} closeOnOverlayClick={false} variant='ontime'>
|
||||
<ModalOverlay />
|
||||
<ModalCloseButton />
|
||||
<ModalContent maxWidth='max(640px, 40vw)'>
|
||||
<form onSubmit={handleSubmit(onSubmit)} id='quick-start'>
|
||||
<ModalHeader>Create new project...</ModalHeader>
|
||||
<ModalBody className={style.scrollContainer}>
|
||||
<ModalCloseButton />
|
||||
<Panel.ListGroup>
|
||||
<Panel.ListItem>
|
||||
<Panel.Field title='Project title' description='Shown as the title in some views' />
|
||||
<Input
|
||||
variant='ontime-filled'
|
||||
size='sm'
|
||||
maxLength={150}
|
||||
placeholder='Project title'
|
||||
autoComplete='off'
|
||||
width='20rem'
|
||||
{...register('project.title')}
|
||||
/>
|
||||
</Panel.ListItem>
|
||||
<Panel.ListItem>
|
||||
<Panel.Field
|
||||
title='Time format'
|
||||
description='Default time format to show in views 12 /24 hours'
|
||||
error={errors.settings?.timeFormat?.message}
|
||||
/>
|
||||
<Select variant='ontime' size='sm' width='auto' isDisabled={false} {...register('settings.timeFormat')}>
|
||||
<option value='12'>12 hours 11:00:10 PM</option>
|
||||
<option value='24'>24 hours 23:00:10</option>
|
||||
</Select>
|
||||
</Panel.ListItem>
|
||||
<Panel.ListItem>
|
||||
<Panel.Field
|
||||
title='Views language'
|
||||
description='Language to be displayed in views'
|
||||
error={errors.settings?.language?.message}
|
||||
/>
|
||||
<Select variant='ontime' size='sm' width='auto' isDisabled={false} {...register('settings.language')}>
|
||||
<option value='en'>English</option>
|
||||
<option value='fr'>French</option>
|
||||
<option value='de'>German</option>
|
||||
<option value='hu'>Hungarian</option>
|
||||
<option value='it'>Italian</option>
|
||||
<option value='no'>Norwegian</option>
|
||||
<option value='pt'>Portuguese</option>
|
||||
<option value='es'>Spanish</option>
|
||||
<option value='sv'>Swedish</option>
|
||||
<option value='pl'>Polish</option>
|
||||
</Select>
|
||||
</Panel.ListItem>
|
||||
</Panel.ListGroup>
|
||||
|
||||
<Panel.ListGroup>
|
||||
<Panel.ListItem>
|
||||
<Panel.Field title='Warning time' description='Default threshold for warning time in an event' />
|
||||
<TimeInput<'warnTime'>
|
||||
name='warnTime'
|
||||
submitHandler={(_field, value) => setWarnTime(value)}
|
||||
time={warnTimeInMs}
|
||||
placeholder={editorSettingsDefaults.warnTime}
|
||||
/>
|
||||
</Panel.ListItem>
|
||||
<Panel.ListItem>
|
||||
<Panel.Field title='Danger time' description='Default threshold for danger time in an event' />
|
||||
<TimeInput<'dangerTime'>
|
||||
name='dangerTime'
|
||||
submitHandler={(_field, value) => setDangerTime(value)}
|
||||
time={dangerTimeInMs}
|
||||
placeholder={editorSettingsDefaults.dangerTime}
|
||||
/>
|
||||
</Panel.ListItem>
|
||||
</Panel.ListGroup>
|
||||
|
||||
<Panel.ListGroup>
|
||||
<Panel.ListItem>
|
||||
<Panel.Field
|
||||
title='Freeze timer on end'
|
||||
description='When a timer hits 00:00:00, it freezes instead of going negative. It invalidates the End Message.'
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='viewSettings.freezeEnd'
|
||||
render={({ field: { onChange, value, ref } }) => (
|
||||
<Switch variant='ontime' size='lg' isChecked={value} onChange={onChange} ref={ref} />
|
||||
)}
|
||||
/>
|
||||
</Panel.ListItem>
|
||||
<Panel.ListItem>
|
||||
<Panel.Field
|
||||
title='End message'
|
||||
description='Message for negative timers; applies only if the timer isn`t frozen on End. If no message is provided, it continues into negative time'
|
||||
/>
|
||||
<Input
|
||||
size='sm'
|
||||
autoComplete='off'
|
||||
variant='ontime-filled'
|
||||
maxLength={150}
|
||||
width='20rem'
|
||||
placeholder='Shown when timer reaches end'
|
||||
{...register('viewSettings.endMessage')}
|
||||
/>
|
||||
</Panel.ListItem>
|
||||
</Panel.ListGroup>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{errors?.root && <Panel.Error>{errors.root.message}</Panel.Error>}
|
||||
<Button variant='ontime-ghosted' size='md' onClick={onClose} isDisabled={false}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='ontime-filled' size='md' type='submit' isDisabled={!isValid} isLoading={isSubmitting}>
|
||||
Create project
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { QuickStartData } from 'ontime-types';
|
||||
|
||||
export const quickStartDefaults: QuickStartData = {
|
||||
project: {
|
||||
title: '',
|
||||
},
|
||||
settings: {
|
||||
timeFormat: '24',
|
||||
language: 'en',
|
||||
},
|
||||
viewSettings: {
|
||||
freezeEnd: false,
|
||||
endMessage: '',
|
||||
},
|
||||
};
|
||||
@@ -15,6 +15,7 @@ const staticOptions = [
|
||||
id: 'project',
|
||||
label: 'Project',
|
||||
secondary: [
|
||||
{ id: 'project__create', label: 'Create...' },
|
||||
{ id: 'project__data', label: 'Project data' },
|
||||
{ id: 'project__manage', label: 'Manage projects' },
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user