mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 06:29:07 +00:00
v2 (#386)
* Refactor/ts sockets (#210) * refactor(ts): create socket subscription service * refactor(ts-sockets): useSubscription hook * refactor(ts-sockets): extract colour selection to utility * Refactor/socket controller (#212) * refactor(socket): dictionary cleanup * refactor(socket): detangle socket from EventTimer * refactor(socket): poll object * refactor(socket): wip, extract data responsibilities to provider * fix: issue with onAir * refactor: create data provider and validation utils * refactor: remove deprecated endpoint * refactor: detangle and validate ontime controller * refactor: detangle and validate events controller * refactor: validate routers * refactor: handle post failure in modals * chore: upgrade electron (#215) * Fix: issue with text colour (#214) * fix: adjust text colour from context * fix: issue with wrong proptype * chore: upgrade electron * chore: declare node version * chore: upgrade node actions * Feat/55 v2 (#225) * chore: upgrade relevant libraries * feat(skip): add skip styling to paginated items * chore: upgrade relevant libraries * Fix: issue with text colour (#214) * fix: adjust text colour from context * fix: issue with wrong proptype * fix issue with vite migration (#217) * hotfix: 1.8.2 issues vite migration * fix: file import options * feat(55): styling * refactor: remove onhover option for entry block * refactor: relocate logging provider * refactor: convert to typescript * feat(55): add event editor * refactor: extract event actions * fix: bad import * feat(55): redesign components * fix: issues with not awaiting async * refactor: replace socket with subscription * refactor: remove unused * style: small tweaks * refactor: extract event actions * refactor: cleanup debug * refactor: chakra imports * fix: optimistic mutations * refactor: handle promise rejections * refactor: validation * fix: rq optimistic mutations * feat: add playback feedback to block * refactor: no optimistic adding of events * refactor: simplify cursor state * styles: cleanup editor style * refactor: cleanup duration update * fix: revert package upgrade (issues with vitest) * fix: prevent cyclic imports * refactor: extract data fetcher * refactor: typescript migration * chore: update tests * fix: remove problematic (#228) * styles: v2 (#229) * style: revise selected style * style: revise editor styles * style: revise viewer styles * chore: update tests * v2:feat (#231) * style: element relationship * feat: utility to add first element * refactor: organise dir * ux: deleting element closes drawer * fix: issue with escaped characters * fix: issue with overflow in events list * fix: prevent event timer from receiving non event * style: prevent small shift on class change * style: visually detach event editor * fix: entry block knows last event * fix: skip event timer updates on non events * fix: prevent electron OPENGL error * style: cleanup text styles * style: unify input styles * refactor: simplify style and composition * refactor: tweaks on style + ts * refactor: add validation to events post * fix: handle non events in event timer * refactor: cleanup unused * V2 styles part2 (#233) * style: create tag to display network messages * style: create button style for playback * style: review style for QuickEntry * style: review style for Info panel * style: review style for Event blocks * style: review style for Message control panel * style: review style for Playback control * style: review application styles * style: review block styles * refactor: TapButton has active state * style: small style tweaks * refactor: type checks * V2 message manager (#234) * refactor: server run script * refactor: extract message control feature * refactor: socket usage for message control feature * refactor: migrate e2e testing to playwright * chore: add feature tests * refactor: add validation on socket controller * chore: jest tests server logic * chore: update tests * chore: run playwright tests on pull_requests * V2 rundown manager (#237) * refactor: migrate eventlist > rundown * hotfix: fix object naming error on excel import (#252) * feat: V2 ontime menu (#259) * V2 socket store (#254) * refactor: create socket utility * refactor: ensure a single subscription to topics * refactor: replace usages of socket object with subscription store handler * refactor: v2 event loader (#260) * refactor: extract jest config * refactor: rename params in reoder endpoint * refactor: avoid app exports * refactor: small code smells * refactor: run development server * refactor: extract rundown service and event loader logic * refactor: rename events > rundown * refactor: code style * fix: handle external change of title * refactor: cleanup dictionary * refactor: rename MessageService * refactor: use eventID for operations * refactor: migrate OSC controller to service * refactor: migrate Socket controller to service * refactor: migrate HTTP controller to service * refactor: extract logic into discrete services * refactor: remove rundown from event timer * refactor: remove duplicate * refactor: remove unused * chore: update tests * refactor: run test CI on master or manual run * lint: reorder imports * refactor: ensure consistent routing * V2 event block (#261) * feat: implement progress bar on running event * refactor: use event action hook * refactor: align backend data * v2 styling (#263) * feat: implement progress bar on running event * refactor: use event action hook * refactor: align backend data * style: v2 style tweaks * style: v2 style tweaks + ts <Info> * fix: fix delay increment calls * style: v2 style tweaks + ts <Playback> * style: v2 style tweaks + ts <EventEditor> * style: v2 style tweaks + ts <MessageControl> * fix: naming issue with message control socket endpoint * chore: upgrade style dependencies * style: v2 style tweaks + ts <MenuBar> * style: v2 style tweaks + ts <RundownMenu> * style: v2 style tweaks + ts <Playback> * fix: add selected and next information to EventTimer * style: v2 style tweaks + ts <EventBlock> * style: v2 style tweaks + ts <DelayBlock> * style: v2 style tweaks + ts <BlockBlock> * style: v2 style tweaks + ts <QuickEntry> * refactor: extract <TextInput> logic * chore: upgrade build dependencies * chore: folder structure refactor * fix: issue with dependency path in electron * chore: update version in demo db * v2: refactor (#264) * chore: upgrade dependencies * chore: remove unused * fix: issue with missing index data * fix: discrepancy on counting events * fix: swapped classes * fix: prevent attempting load in empty list * chore: cleanup completed * chore: remove unused * refactor: convert to typescript * refactor: migrate to new endpoints * refactor: convert to typescript * fix: prevent issue with undefined process * fix: small code smells * fix: issue with missing version variable on build * refactor: configure retries on data fetching * style: design review * fix: prevent cursor out of range * lint: react query linting * style: checkbox styles * v2 event editor (#265) * style: add cursor indicator in all blocks * style: prevent reflows on width * style: harmonise white range * ux: focus elements on cursor nav * ux: prevent focus on indicators * refactor: convert to typescript * fix: missing dependency * chore: add linter rules for dependency arrays * style: style editable component * style: tweak input styles * style: tweak switch styles * style: style tweaks * V2 sentry (#267) * chore: add sentry to frontend * chore: add sentry to server * chore: report only in production * chore: add sentry source maps * v2 alpha 3 (#272) * style: correct background colours * chore: add folder resolutions to vite * refactor: add auto refetch to HTTP APIs * fix: view settings override endpoint * refactor: memoize callbacks * fix: reorder reaching wrong data adapter * refactor: memoize callbacks * style: presentation cleanup * fix(delete): prevent flow with deleted event in playback * refactor: convert to typescript * refactor: small fixes and typescript conversion * ux: improve feedback on local changes * refactor: cleanup props * v2 event timer (#274) * lint: cleanup unused * refactor: rename component to avoid conflict * style: gap in element row * refactor: rename playstate -> playback * refactor: cleanup usages of timer and prepare integration manager * style: re-arrange button order * refactor: improve event loading * feat(timer-service): hot reload * fix: issue with duration input * chore: cleanup debug * refactor: resolve poll from runtime store * refactor: cleanup merge * refactor: small improvements in timer hot-reload * v2: views (#278) * config(sentry): instrumentation only in production * chore: upgrade deps * refactor: simplify and convert to ts * refactor: composition and styles * chore: remove deprecated deps * ux: close menu on click outside * style: small tweaks and clarifications * refactor: retire PiP view * ux: small tweaks * style: cleanup deprecated styles * chore: remove unused * refactor: remove usages of deprecated fields and improve null timer presentation * refactor: improve composition of countdown * style: cleanup overridable styles * ux: show event overtime status * style: correct logic in paused timers * v2: roll (#280) * ux: delete flow * style: small tweaks in interface * refactor: gracefully quit on error * fix: logic around updating events * chore: cleanup dictionary * refactor: improve DX on creating aux files * fix: safe destructure function return * refactor: safe handling of falsy timer values * fix: improve ux on stopping roll mode * chore: upgrade deps * feat: roll mode * refactor: remove unused * V2 monorepo (#285) * refactor(project structure): UI * refactor(project structure): extract utilities * refactor(project structure): remove unused * refactor(project structure): electron * refactor(project structure): server refactor: migrate to vitest refactor: monorepo config * refactor: extract application menu * refactor: exit process * refactor: extract tray menu * chore: electron build * Added Seconds in studio clock #282 --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> --------- Co-authored-by: Fabian Posenau <fabian.p99@gmx.de> Co-authored-by: Fabian Posenau <fabian@fphome.de> * Fix: #279 End Message in Minimal Timer (#286) * fix: Minimal View #279 end message --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> * hotfix: resolve data directory (#288) * Feature/#276/qrcodes (#289) * bugfix: wrong format when on 12h format * update: website link in placeholder --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com> * V2 types (#291) * V2 integration (#293) * feat: integrations and lifecycles * refactor: prevent issues with start order * V2 fix offline (#294) * fix: resolve path to UI build in dev * fix: configure offline fetching * V2 fix socket (#296) * refactor: rename timer endpoint * refactor: typescript * refactor: rename eventData and viewSettings * refactor: message manager uses event store * style: alpha4 style tweaks (#299) * fix: backgroundcolor for .blockNotes (#300) Co-authored-by: Fabian Posenau <fabian@fphome.de> * Feat/countdown style (#290) * feat: Countdown Style --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com> Co-authored-by: Fabian Posenau <19673098+kellhogs@users.noreply.github.com> * fix: wrong parameter (#302) Co-authored-by: Fabian Posenau <fabian@fphome.de> * V2 fix timer (#304) * Feat/update check (#303) * feat: show latest version in settings modal --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> * improve time input (#307) * add the ability to parse time like 2h20m --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> * V2 ws store (#310) * Update TimerService.ts * refactor: message service publishes to store * refactor: several type improvements * V2 ws store wss (#309) * refactor: shared logging types * refactor: simplify message service consumption * refactor: create discrete logging system * refactor: move socket.io > websocket * Feat/end action (#308) * feat: end action --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com> Co-authored-by: Fabian Posenau <19673098+kellhogs@users.noreply.github.com> * V2 osc (#313) * chore: upgrade relevant deps * fix: electron app to tray * chore: cleanup dictionary * feat: handle several messages in a event * ux: disable irrelevant buttons in browser * feat: parse and validate subscriptions * feat: create UI for OSC Integration * fix: cleanup logger behaviour * feat: allow OSC settings to be changed at runtime * v2 alpha5 (#315) * fix: fetch in offline environments (#295) * add arm platforms to docker build --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> * fix: docker build (#298) Co-authored-by: Fabian Posenau <fabian@fphome.de> * Timer: fix too many renders error when using ?progress (#305) * ux: remove duplicate showing of selected event * several small fixes and UX improvements --------- Co-authored-by: Fabian Posenau <fabian.p99@gmx.de> Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: Marks Polakovs <github@markspolakovs.me> * V2 docker (#312) * Create docker-ci.yml * docker build * add build app * path changes * delete double workflow * update docker run template * cleanup * resolve wierd path issue * wrong path * again wrong path * rename docker releases * move styles population and change to function call * re-add old build pipeline * rename buld file * feractor init function * refactor: startDb to initAssets * refactor: startDB to initAssets * fix: docker.cjs building whole app * fix: docker environment * update docker paths * update: build command * cleanup * cleanup: commented code --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> * v2 lazy rundown (#317) * chore: update related deps * refactor: memoise entrypoints * refactor: store selectors * refactor: improve component performance * chore: remove unused dependencies * refactor: improve component dnd * V2 build (#319) * chore: build pipeline for v2 * chore: run unit tests * chore: configure sentry build * Update test_v2.yml * Update test_v2.yml * V2 e2e (#320) * chore: cleanup handling of a bad read * chore: configure e2e testing * chore: version bump * V2 align (#321) * fix: fetch in offline environments (#295) * Add arm platforms to docker build (#297) * add arm platforms to docker build --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> * fix: docker build (#298) Co-authored-by: Fabian Posenau <fabian@fphome.de> * Timer: fix too many renders error when using ?progress (#305) * Timer: fix too many renders error when using ?progress * version bump (#314) * style: small tweaks * fix: prevent actions in roll mode * chore: update external assets * style: show shortcuts in quick blocks * refactor: revert optimisations on callbacks * fun: industry dictionary * style: indicators for event features --------- Co-authored-by: Fabian Posenau <fabian.p99@gmx.de> Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: Marks Polakovs <github@markspolakovs.me> * refactor: ensure start order (#322) * Update build_docker_v2.yml * V2 fix roll (#323) * chore: profile node * fix: always roll * Add translations for views (#325) (#328) * Add translations for views (#325) --------- Co-authored-by: Kevin <60135953+kev-ac@users.noreply.github.com> * feat: notify ws clients on rundown changes (#327) * V2 beta1 (#326) * style: labels on added time * style: remove mentions of PiP * refactor: unify usage of ms for timers * refactor: create events with 0 duration * style: several small tweaks * refactor: keep block when applying delays * feat: blocks have titles * style: improvements in time entry warnings * style: override progress bar styles * style: prevent overflow * feat: show character count in editor * refactor: provide initial payload * refactor: lower test boundary * refactor: get colour from swatches * style: rename title block * chore: version bump * style: fix info scroll * refactor: performance tweaks to event editor * chore: upgrade deps * refactor: prevent iterating on undefined * style: small style tweaks * fix: close editor on event delete * refactor: improve performance on EventBlock * style: small tweaks on blocks * chore: upgrade deps * chore: upgrade deps * style: ensure events are always visible * Fix/events enpoint (#329) * fix: sentry hangs node process * add onClick handler (#335) Co-authored-by: Fabian Posenau <fabian@fphome.de> * fix docker on different port (#337) Co-authored-by: Fabian Posenau <fabian@fphome.de> * V2 beta3 (#338) * chore: upgrade local build documentation * style: keep scrolling event in screen * feat: delay is time entry * refactor: remove unused * refactor: remove unused * refactor: batch store updates * refactor: virtually remove cap on events * refactor: style and behaviour tweaks to event block * style: tweaks on schedules * chore: remove sentry from server * style: reorder menu * chore: update docs * fix: resolve to static port in dev mode * V2 fix delay (#340) * fix: avoid modifying original objects * refactor: serve react as compressed br (#339) * refactor: changes in time input * V2 e2e playwright (#343) * test: create utility for uploading test-db * chore: upgrade deps * refactor: remove unused functions * chore: create testing boilerplate * test: upload showfile * refactor: parse time input (#351) * chore: vite upgrade (#354) * chore: upgrade vite * feat: edit mode (#344) * style: add mode selection to menu * style: rename rundown functions * feat: edit mode * fix: osc modal (#352) * refactor: parse time input * fix: form dirty flag on dynamic elements * style: cuesheet block (#355) * style: cuesheet show block title * style: cuesheet show full delay string * v2 about (#362) * chore: remove unused assets * feat: about ontime * refactor: cleanup unused * v2 beta 4 (#361) * ux: rename end action * chore: update demo db * style: tweaks on extracted rundown * chore: prevent console logs in production code * feat: go mode * style: remove window size limits * style: rename delete action * style: small tweaks on icons * fix: style override on params * chore: update test db * refactor: small code quality improvements * refactor: prevent circular imports * fix v2 roll timer (#363) * fix: roll update skips elapsed time * refactor: remove unused data * style: fix style in roll state * ux: content aware playback (#364) * v2 beta 4 bump (#367) * chore: update links to documentation * chore: version bump * Feature: quick start (#369) * feat: quick start modal * refactor: end message is part of view settings * refactor: small tweaks and type improvements * Feat/timer/stylechange (#341) * feat: multi color progress bar * add color support to minimal timer Co-Authored-By: Fabian Posenau <19673098+kellhogs@users.noreply.github.com> --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Co-authored-by: Fabian Posenau <19673098+kellhogs@users.noreply.github.com> * v2 beta5 modals (#377) * refactor: folder structure * style: modal wrapper * feat: add nordic translations * v2 beta 5 (#375) * chore: update documentation links * fix: sentry has no access to error context * style: clarify event history * fix: issue with clipboard write in safari * style: clarify event history * refactor: improvements to follow logic in rundown * refactor: improvements in go mode * several style tweaks and small improvements * Ux language (#378) * ux: language from menu * feat: add extra support for languages german portuguese spanish * refactor: type improvements * v2 beta6 (#379) * feat: operator key * chore: update demo * chore: version bump * fix: prevent stale keys * refactor: add new fields to validation * style: tweaks to modal arrangement * refactor: prevent parsing http * ux: click anywhere in event to edit * refactor: performance improvements to time input * refactor: performance improvements menu * V2 beta 6 (#381) * fix: fetch in offline environments (#295) * Add arm platforms to docker build (#297) * add arm platforms to docker build --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> * fix: docker build (#298) Co-authored-by: Fabian Posenau <fabian@fphome.de> * Timer: fix too many renders error when using ?progress (#305) * Timer: fix too many renders error when using ?progress * version bump (#314) * chore: remove unused assets * refactor: improve rundown loading time * style: rename project file * chore: update demo files * refactor: improve loading time * chore: upgrade upload packages * refactor: modal redesign and ux improvements * refactor: type and ux improvements on translations * refactor: prevent flash on suspense * refactor: remove unnecessary import * chore: update tests * refactor: cleanup imports * fix: prevent flash on suspense --------- Co-authored-by: Fabian Posenau <fabian.p99@gmx.de> Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: Marks Polakovs <github@markspolakovs.me> * style views (#390) * style: animate transition from left side * refactor: improve schedule relationship * refactor: prevent passing unhandled value * style: remove inner radius in borders --------- Co-authored-by: Fabian Posenau <fabian.p99@gmx.de> Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: Fabian Posenau <19673098+kellhogs@users.noreply.github.com> Co-authored-by: Marks Polakovs <github@markspolakovs.me> Co-authored-by: Kevin <60135953+kev-ac@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { Alert, AlertDescription, AlertIcon, AlertTitle, Button, IconButton, Input, Switch } from '@chakra-ui/react';
|
||||
import { IoOpenOutline } from '@react-icons/all-files/io5/IoOpenOutline';
|
||||
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
|
||||
import { Alias } from 'ontime-types';
|
||||
|
||||
import { postAliases } from '../../../common/api/ontimeApi';
|
||||
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||
import useAliases from '../../../common/hooks-query/useAliases';
|
||||
import { useEmitLog } from '../../../common/stores/logger';
|
||||
import { handleLinks } from '../../../common/utils/linkUtils';
|
||||
import { inputProps } from '../modalHelper';
|
||||
import ModalLink from '../ModalLink';
|
||||
import OntimeModalFooter from '../OntimeModalFooter';
|
||||
|
||||
import style from './SettingsModal.module.scss';
|
||||
|
||||
const aliasesDocsUrl = 'https://ontime.gitbook.io/v2/features/url-aliases';
|
||||
|
||||
// we wrap the array in an object to be simplify react-hook-form
|
||||
type Aliases = { aliases: Alias[] };
|
||||
|
||||
export default function AliasesForm() {
|
||||
const { data, status, refetch } = useAliases();
|
||||
const { emitError } = useEmitLog();
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { isSubmitting, isDirty, isValid },
|
||||
} = useForm<Aliases>({
|
||||
defaultValues: { aliases: data },
|
||||
values: { aliases: data || [] },
|
||||
});
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
name: 'aliases',
|
||||
control,
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: Aliases) => {
|
||||
try {
|
||||
await postAliases(formData.aliases);
|
||||
} catch (error) {
|
||||
emitError(`Error saving aliases: ${error}`);
|
||||
} finally {
|
||||
await refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
reset({ aliases: data });
|
||||
};
|
||||
|
||||
const addNew = () => {
|
||||
if (fields.length > 20) {
|
||||
emitError('Maximum amount of aliases reacted (20)');
|
||||
return;
|
||||
}
|
||||
append({
|
||||
enabled: false,
|
||||
alias: '',
|
||||
pathAndParams: '',
|
||||
});
|
||||
};
|
||||
|
||||
const disableInputs = status === 'loading';
|
||||
const hasTooManyOptions = fields.length >= 20;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} id='aliases' className={style.sectionContainer}>
|
||||
<div style={{ height: '16px' }} />
|
||||
<Alert status='info' variant='ontime-on-light-info'>
|
||||
<AlertIcon />
|
||||
<div className={style.column}>
|
||||
<AlertTitle>URL Aliases</AlertTitle>
|
||||
<AlertDescription>
|
||||
Custom aliases allow providing a short name for any ontime URL. <br />
|
||||
It serves two primary purposes: <br />
|
||||
- Providing dynamic URLs for automation or unattended screens <br />
|
||||
- Simplifying complex URLs
|
||||
<ModalLink href={aliasesDocsUrl}>For more information, see the docs</ModalLink>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
</Alert>
|
||||
<div style={{ height: '16px' }} />
|
||||
<ul className={style.aliases}>
|
||||
{fields.map((alias, index) => {
|
||||
return (
|
||||
<li className={style.aliasRow} key={alias.id}>
|
||||
<IconButton
|
||||
onClick={() => remove(index)}
|
||||
aria-label='delete'
|
||||
size='xs'
|
||||
icon={<IoRemove />}
|
||||
colorScheme='red'
|
||||
isDisabled={disableInputs}
|
||||
/>
|
||||
<Input
|
||||
{...inputProps}
|
||||
{...register(`aliases.${index}.alias`)}
|
||||
width='12em'
|
||||
size='xs'
|
||||
variant='ontime-filled-on-light'
|
||||
placeholder='URL Alias'
|
||||
isDisabled={disableInputs}
|
||||
/>
|
||||
<Input
|
||||
{...inputProps}
|
||||
{...register(`aliases.${index}.pathAndParams`)}
|
||||
className={style.grow}
|
||||
size='xs'
|
||||
variant='ontime-filled-on-light'
|
||||
placeholder='URL (portion after ontime Port)'
|
||||
isDisabled={disableInputs}
|
||||
/>
|
||||
<TooltipActionBtn
|
||||
clickHandler={(event) => handleLinks(event, alias.pathAndParams)}
|
||||
tooltip='Test alias'
|
||||
aria-label='Test alias'
|
||||
size='xs'
|
||||
variant='ontime-ghost-on-light'
|
||||
icon={<IoOpenOutline />}
|
||||
colorScheme='red'
|
||||
isDisabled={disableInputs}
|
||||
/>
|
||||
<Switch {...register(`aliases.${index}.enabled`)} variant='ontime-on-light' isDisabled={disableInputs} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<Button
|
||||
onClick={addNew}
|
||||
className={style.shiftRight}
|
||||
isDisabled={hasTooManyOptions}
|
||||
size='xs'
|
||||
colorScheme='blue'
|
||||
variant='outline'
|
||||
padding='0 2em'
|
||||
>
|
||||
Add new
|
||||
</Button>
|
||||
<OntimeModalFooter
|
||||
formId='aliases'
|
||||
handleRevert={onReset}
|
||||
isDirty={isDirty}
|
||||
isValid={isValid}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Input, Select } from '@chakra-ui/react';
|
||||
import type { Settings } from 'ontime-types';
|
||||
|
||||
import { postSettings } from '../../../common/api/ontimeApi';
|
||||
import useSettings from '../../../common/hooks-query/useSettings';
|
||||
import { useEmitLog } from '../../../common/stores/logger';
|
||||
import { isOnlyNumbers } from '../../../common/utils/regex';
|
||||
import ModalSplitInput from '../ModalSplitInput';
|
||||
import OntimeModalFooter from '../OntimeModalFooter';
|
||||
|
||||
import ModalPinInput from './ModalPinInput';
|
||||
|
||||
import style from './SettingsModal.module.scss';
|
||||
|
||||
export default function AppSettingsModal() {
|
||||
const { data, status, refetch } = useSettings();
|
||||
const { emitError } = useEmitLog();
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isDirty, isValid },
|
||||
} = useForm<Settings>({
|
||||
defaultValues: data,
|
||||
values: data,
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: Settings) => {
|
||||
try {
|
||||
await postSettings(formData);
|
||||
} catch (error) {
|
||||
emitError(`Error saving settings: ${error}`);
|
||||
} finally {
|
||||
await refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
reset(data);
|
||||
};
|
||||
|
||||
const disableInputs = status === 'loading';
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} id='app-settings' className={style.sectionContainer}>
|
||||
<ModalSplitInput
|
||||
field='serverPort'
|
||||
title='Ontime is available on port'
|
||||
description='Default 4001'
|
||||
error={errors.serverPort?.message}
|
||||
>
|
||||
<Input
|
||||
width='75px'
|
||||
size='sm'
|
||||
textAlign='right'
|
||||
maxLength={5}
|
||||
disabled
|
||||
variant='ontime-filled-on-light'
|
||||
{...register('serverPort', {
|
||||
required: { value: true, message: 'Required field' },
|
||||
max: { value: 65535, message: 'Port in incorrect range (1024 - 65535)' },
|
||||
min: { value: 1024, message: 'Port in incorrect range (1024 - 65535)' },
|
||||
pattern: {
|
||||
value: isOnlyNumbers,
|
||||
message: 'Value should be numeric',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput
|
||||
field='editorKey'
|
||||
title='Editor pin code'
|
||||
description='Protect the editor with a pin code'
|
||||
error={errors.editorKey?.message}
|
||||
>
|
||||
<ModalPinInput register={register} formName='editorKey' isDisabled={disableInputs} />
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput
|
||||
field='operatorKey'
|
||||
title='Operator pin code'
|
||||
description='Protect the cuesheet with a pin code'
|
||||
error={errors.operatorKey?.message}
|
||||
>
|
||||
<ModalPinInput register={register} formName='operatorKey' isDisabled={disableInputs} />
|
||||
</ModalSplitInput>
|
||||
<div style={{ height: '16px' }} />
|
||||
<ModalSplitInput
|
||||
field='timeFormat'
|
||||
title='Time Format'
|
||||
description='Views 12 / 24 hours'
|
||||
error={errors.timeFormat?.message}
|
||||
>
|
||||
<Select backgroundColor='white' size='sm' width='auto' isDisabled={disableInputs} {...register('timeFormat')}>
|
||||
<option value='12'>12 hours eg. 11:00:10 PM</option>
|
||||
<option value='24'>24 hours eg. 23:00:10</option>
|
||||
</Select>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput
|
||||
field='language'
|
||||
title='Views Language'
|
||||
description='Language for static fields in views'
|
||||
error={errors.language?.message}
|
||||
>
|
||||
<Select backgroundColor='white' size='sm' width='auto' isDisabled={disableInputs} {...register('language')}>
|
||||
<option value='en'>English</option>
|
||||
<option value='de'>German</option>
|
||||
<option value='no'>Norwegian</option>
|
||||
<option value='pt'>Portuguese</option>
|
||||
<option value='es'>Spanish</option>
|
||||
<option value='sv'>Swedish</option>
|
||||
</Select>
|
||||
</ModalSplitInput>
|
||||
<OntimeModalFooter
|
||||
formId='app-settings'
|
||||
handleRevert={onReset}
|
||||
isDirty={isDirty}
|
||||
isValid={isValid}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Alert, AlertDescription, AlertIcon, AlertTitle, Input } from '@chakra-ui/react';
|
||||
import { UserFields } from 'ontime-types';
|
||||
|
||||
import { postUserFields } from '../../../common/api/ontimeApi';
|
||||
import useUserFields from '../../../common/hooks-query/useUserFields';
|
||||
import { useEmitLog } from '../../../common/stores/logger';
|
||||
import { inputProps } from '../modalHelper';
|
||||
import ModalLink from '../ModalLink';
|
||||
import ModalSplitInput from '../ModalSplitInput';
|
||||
import OntimeModalFooter from '../OntimeModalFooter';
|
||||
|
||||
import style from './SettingsModal.module.scss';
|
||||
|
||||
const userFieldsDocsUrl = 'https://ontime.gitbook.io/v2/features/user-fields';
|
||||
|
||||
export default function CuesheetSettings() {
|
||||
const { data, status, refetch } = useUserFields();
|
||||
const { emitError } = useEmitLog();
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isDirty, isValid },
|
||||
} = useForm<UserFields>({
|
||||
defaultValues: data,
|
||||
values: data,
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: UserFields) => {
|
||||
try {
|
||||
await postUserFields(formData);
|
||||
} catch (error) {
|
||||
emitError(`Error saving cuesheet settings: ${error}`);
|
||||
} finally {
|
||||
await refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
reset(data);
|
||||
};
|
||||
|
||||
const disableInputs = status === 'loading';
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} id='cuesheet-settings' className={style.sectionContainer}>
|
||||
<div style={{ height: '16px' }} />
|
||||
<Alert status='info' variant='ontime-on-light-info'>
|
||||
<AlertIcon />
|
||||
<div className={style.column}>
|
||||
<AlertTitle>User Fields</AlertTitle>
|
||||
<AlertDescription>
|
||||
Allow for custom naming of additional data fields on each event (eg. light, sound, camera). <br />
|
||||
<ModalLink href={userFieldsDocsUrl}>See the docs</ModalLink>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
</Alert>
|
||||
<div style={{ height: '16px' }} />
|
||||
<ModalSplitInput field='user0' title='User0' description='' error={errors.user0?.message}>
|
||||
<Input
|
||||
{...inputProps}
|
||||
width='300px'
|
||||
variant='ontime-filled-on-light'
|
||||
isDisabled={disableInputs}
|
||||
placeholder='Display name for user field'
|
||||
{...register('user0')}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='user1' title='User1' description='' error={errors.user1?.message}>
|
||||
<Input
|
||||
{...inputProps}
|
||||
width='300px'
|
||||
variant='ontime-filled-on-light'
|
||||
isDisabled={disableInputs}
|
||||
placeholder='Display name for user field'
|
||||
{...register('user1')}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='user2' title='User2' description='' error={errors.user2?.message}>
|
||||
<Input
|
||||
{...inputProps}
|
||||
width='300px'
|
||||
variant='ontime-filled-on-light'
|
||||
isDisabled={disableInputs}
|
||||
placeholder='Display name for user field'
|
||||
{...register('user2')}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='user3' title='User3' description='' error={errors.user3?.message}>
|
||||
<Input
|
||||
{...inputProps}
|
||||
width='300px'
|
||||
variant='ontime-filled-on-light'
|
||||
isDisabled={disableInputs}
|
||||
placeholder='Display name for user field'
|
||||
{...register('user3')}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='user4' title='User4' description='' error={errors.user4?.message}>
|
||||
<Input
|
||||
{...inputProps}
|
||||
width='300px'
|
||||
variant='ontime-filled-on-light'
|
||||
isDisabled={disableInputs}
|
||||
placeholder='Display name for user field'
|
||||
{...register('user4')}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='user5' title='User5' description='' error={errors.user5?.message}>
|
||||
<Input
|
||||
{...inputProps}
|
||||
width='300px'
|
||||
variant='ontime-filled-on-light'
|
||||
isDisabled={disableInputs}
|
||||
placeholder='Display name for user field'
|
||||
{...register('user5')}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='user6' title='User6' description='' error={errors.user6?.message}>
|
||||
<Input
|
||||
{...inputProps}
|
||||
width='300px'
|
||||
variant='ontime-filled-on-light'
|
||||
isDisabled={disableInputs}
|
||||
placeholder='Display name for user field'
|
||||
{...register('user6')}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='user7' title='User7' description='' error={errors.user7?.message}>
|
||||
<Input
|
||||
{...inputProps}
|
||||
width='300px'
|
||||
variant='ontime-filled-on-light'
|
||||
isDisabled={disableInputs}
|
||||
placeholder='Display name for user field'
|
||||
{...register('user7')}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='user8' title='User8' description='' error={errors.user8?.message}>
|
||||
<Input
|
||||
{...inputProps}
|
||||
width='300px'
|
||||
variant='ontime-filled-on-light'
|
||||
isDisabled={disableInputs}
|
||||
placeholder='Display name for user field'
|
||||
{...register('user8')}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='user9' title='User9' description='' error={errors.user9?.message}>
|
||||
<Input
|
||||
{...inputProps}
|
||||
width='300px'
|
||||
variant='ontime-filled-on-light'
|
||||
isDisabled={disableInputs}
|
||||
placeholder='Display name for user field'
|
||||
{...register('user9')}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<OntimeModalFooter
|
||||
formId='cuesheet-settings'
|
||||
handleRevert={onReset}
|
||||
isDirty={isDirty}
|
||||
isValid={isValid}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Switch } from '@chakra-ui/react';
|
||||
|
||||
import { useLocalEvent } from '../../../common/stores/localEvent';
|
||||
import ModalSplitInput from '../ModalSplitInput';
|
||||
|
||||
import style from './SettingsModal.module.scss';
|
||||
|
||||
export default function EditorSettings() {
|
||||
const eventSettings = useLocalEvent((state) => state.eventSettings);
|
||||
const setShowQuickEntry = useLocalEvent((state) => state.setShowQuickEntry);
|
||||
const setStartTimeIsLastEnd = useLocalEvent((state) => state.setStartTimeIsLastEnd);
|
||||
const setDefaultPublic = useLocalEvent((state) => state.setDefaultPublic);
|
||||
|
||||
return (
|
||||
<div className={style.sectionContainer}>
|
||||
<span className={style.title}>Rundown settings</span>
|
||||
<ModalSplitInput field='' title='Show quick entry' description='Whether quick entry shows under selected event'>
|
||||
<Switch
|
||||
variant='ontime-on-light'
|
||||
defaultChecked={eventSettings.showQuickEntry}
|
||||
onChange={(event) => setShowQuickEntry(event.target.checked)}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput
|
||||
field=''
|
||||
title='Start time is last end'
|
||||
description='New events start time will be previous event end'
|
||||
>
|
||||
<Switch
|
||||
variant='ontime-on-light'
|
||||
defaultChecked={eventSettings.startTimeIsLastEnd}
|
||||
onChange={(event) => setStartTimeIsLastEnd(event.target.checked)}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='' title='Default public' description='New events will be public'>
|
||||
<Switch
|
||||
variant='ontime-on-light'
|
||||
defaultChecked={eventSettings.defaultPublic}
|
||||
onChange={(event) => setDefaultPublic(event.target.checked)}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Input, Textarea } from '@chakra-ui/react';
|
||||
import { EventData } from 'ontime-types';
|
||||
|
||||
import { postEventData } from '../../../common/api/eventDataApi';
|
||||
import useEventData from '../../../common/hooks-query/useEventData';
|
||||
import { useEmitLog } from '../../../common/stores/logger';
|
||||
import { inputProps } from '../modalHelper';
|
||||
import ModalInput from '../ModalInput';
|
||||
import OntimeModalFooter from '../OntimeModalFooter';
|
||||
|
||||
import style from './SettingsModal.module.scss';
|
||||
|
||||
export default function EventDataForm() {
|
||||
const { data, status, refetch } = useEventData();
|
||||
const { emitError } = useEmitLog();
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isDirty, isValid },
|
||||
} = useForm<EventData>({
|
||||
defaultValues: data,
|
||||
values: data,
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: EventData) => {
|
||||
try {
|
||||
await postEventData(formData);
|
||||
} catch (error) {
|
||||
emitError(`Error saving event settings: ${error}`);
|
||||
} finally {
|
||||
await refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
reset(data);
|
||||
};
|
||||
|
||||
const disableInputs = status === 'loading';
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} id='event-data' className={style.sectionContainer}>
|
||||
<ModalInput
|
||||
field='title'
|
||||
title='Event title'
|
||||
description='Shown in overview screens'
|
||||
error={errors.title?.message}
|
||||
>
|
||||
<Input
|
||||
{...inputProps}
|
||||
variant='ontime-filled-on-light'
|
||||
maxLength={50}
|
||||
placeholder='Eurovision song contest'
|
||||
isDisabled={disableInputs}
|
||||
{...register('title')}
|
||||
/>
|
||||
</ModalInput>
|
||||
<div style={{ height: '16px' }} />
|
||||
<ModalInput field='publicInfo' title='Public Info' description='Information shown in public screens'>
|
||||
<Textarea
|
||||
{...inputProps}
|
||||
variant='ontime-filled-on-light'
|
||||
maxLength={150}
|
||||
placeholder='Shows always start ontime'
|
||||
isDisabled={disableInputs}
|
||||
{...register('publicInfo')}
|
||||
/>
|
||||
</ModalInput>
|
||||
<ModalInput field='publicUrl' title='Public URL' description='QR code to be shown on public screens'>
|
||||
<Input
|
||||
{...inputProps}
|
||||
variant='ontime-filled-on-light'
|
||||
placeholder='www.getontime.no'
|
||||
isDisabled={disableInputs}
|
||||
{...register('publicUrl')}
|
||||
/>
|
||||
</ModalInput>
|
||||
<div style={{ height: '16px' }} />
|
||||
<ModalInput field='backstageInfo' title='Backstage Info' description='Information shown in public screens'>
|
||||
<Textarea
|
||||
{...inputProps}
|
||||
variant='ontime-filled-on-light'
|
||||
maxLength={150}
|
||||
placeholder='Wi-Fi password: 1234'
|
||||
isDisabled={disableInputs}
|
||||
{...register('backstageInfo')}
|
||||
/>
|
||||
</ModalInput>
|
||||
<ModalInput field='backstageUrl' title='Backstage URL' description='QR code to be shown on public screens'>
|
||||
<Input
|
||||
{...inputProps}
|
||||
variant='ontime-filled-on-light'
|
||||
size='sm'
|
||||
placeholder='www.ontime.gitbook.io'
|
||||
isDisabled={disableInputs}
|
||||
{...register('backstageUrl')}
|
||||
/>
|
||||
</ModalInput>
|
||||
<OntimeModalFooter
|
||||
formId='event-data'
|
||||
handleRevert={onReset}
|
||||
isDirty={isDirty}
|
||||
isValid={isValid}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useController, UseControllerProps } from 'react-hook-form';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { ViewSettings } from 'ontime-types';
|
||||
|
||||
import { millisToMinutes } from '../../../common/utils/dateConfig';
|
||||
import { inputProps } from '../modalHelper';
|
||||
|
||||
export default function InputMillisWithString(props: UseControllerProps<ViewSettings>) {
|
||||
const { name, control } = props;
|
||||
const {
|
||||
field: { onChange, value },
|
||||
} = useController({
|
||||
control,
|
||||
name,
|
||||
rules: {
|
||||
pattern: {
|
||||
value: /^[0-9]+$/,
|
||||
message: 'Only numbers are valid',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Input
|
||||
{...inputProps}
|
||||
type='number'
|
||||
variant='ontime-filled-on-light'
|
||||
width='75px'
|
||||
size='sm'
|
||||
maxLength={3}
|
||||
defaultValue={millisToMinutes(value as number)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useState } from 'react';
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
import { IconButton, Input, InputGroup, InputRightElement } from '@chakra-ui/react';
|
||||
import { IoEyeOutline } from '@react-icons/all-files/io5/IoEyeOutline';
|
||||
|
||||
interface ModalPinInputProps {
|
||||
register: UseFormRegister<any>;
|
||||
formName: string;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
export default function ModalPinInput({ register, formName, isDisabled }: ModalPinInputProps) {
|
||||
const [isVisible, setVisible] = useState(false);
|
||||
return (
|
||||
<InputGroup size='sm' width='100px'>
|
||||
<Input
|
||||
type={isVisible ? 'text' : 'password'}
|
||||
maxLength={4}
|
||||
{...register(formName)}
|
||||
placeholder='-'
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
<InputRightElement>
|
||||
<IconButton
|
||||
onMouseDown={() => setVisible(true)}
|
||||
onMouseUp={() => setVisible(false)}
|
||||
size='sm'
|
||||
variant='ontime-ghost-on-light'
|
||||
icon={<IoEyeOutline />}
|
||||
aria-label='Show pin code'
|
||||
/>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
@import "../Modal.module.scss";
|
||||
|
||||
.aliases {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 8px 0;
|
||||
|
||||
.aliasRow {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { ModalBody, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
||||
|
||||
import ModalWrapper from '../ModalWrapper';
|
||||
|
||||
import AliasesForm from './AliasesForm';
|
||||
import AppSettingsModal from './AppSettings';
|
||||
import CuesheetSettings from './CuesheetSettings';
|
||||
import EditorSettings from './EditorSettings';
|
||||
import EventDataForm from './EventDataForm';
|
||||
import ViewSettingsForm from './ViewSettingsForm';
|
||||
|
||||
interface ModalManagerProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function SettingsModal(props: ModalManagerProps) {
|
||||
const { isOpen, onClose } = props;
|
||||
return (
|
||||
<ModalWrapper title='Ontime Settings' isOpen={isOpen} onClose={onClose}>
|
||||
<ModalBody>
|
||||
<Tabs variant='ontime' size='sm' isLazy>
|
||||
<TabList>
|
||||
<Tab>App Settings</Tab>
|
||||
<Tab>Event Data</Tab>
|
||||
<Tab>Editor</Tab>
|
||||
<Tab>Cuesheet</Tab>
|
||||
<Tab>Views</Tab>
|
||||
<Tab>URL Aliases</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<AppSettingsModal />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<EventDataForm />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<EditorSettings />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<CuesheetSettings />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<ViewSettingsForm />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<AliasesForm />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</ModalBody>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Input, Switch } from '@chakra-ui/react';
|
||||
import { ViewSettings } from 'ontime-types';
|
||||
|
||||
import { postViewSettings } from '../../../common/api/ontimeApi';
|
||||
import { PopoverPickerRHF } from '../../../common/components/input/popover-picker/PopoverPicker';
|
||||
import useViewSettings from '../../../common/hooks-query/useViewSettings';
|
||||
import { useEmitLog } from '../../../common/stores/logger';
|
||||
import { mtm } from '../../../common/utils/timeConstants';
|
||||
import { inputProps } from '../modalHelper';
|
||||
import ModalInput from '../ModalInput';
|
||||
import ModalSplitInput from '../ModalSplitInput';
|
||||
import OntimeModalFooter from '../OntimeModalFooter';
|
||||
|
||||
import InputMillisWithString from './InputMillisWithString';
|
||||
|
||||
import style from './SettingsModal.module.scss';
|
||||
|
||||
export default function ViewSettingsForm() {
|
||||
const { data, status, refetch } = useViewSettings();
|
||||
const { emitError } = useEmitLog();
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { isSubmitting, isDirty, isValid, dirtyFields },
|
||||
} = useForm<ViewSettings>({
|
||||
defaultValues: data,
|
||||
values: data,
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: ViewSettings) => {
|
||||
const parsedWarningThreshold = dirtyFields?.warningThreshold
|
||||
? // @ts-expect-error -- trust me
|
||||
Number.parseInt(formData.warningThreshold) * mtm
|
||||
: formData.warningThreshold;
|
||||
const parsedDangerThreshold = dirtyFields?.dangerThreshold
|
||||
? // @ts-expect-error -- trust me
|
||||
Number.parseInt(formData.dangerThreshold) * mtm
|
||||
: formData.dangerThreshold;
|
||||
|
||||
const newData = {
|
||||
...formData,
|
||||
warningThreshold: parsedWarningThreshold,
|
||||
dangerThreshold: parsedDangerThreshold,
|
||||
};
|
||||
|
||||
try {
|
||||
await postViewSettings(newData);
|
||||
} catch (error) {
|
||||
emitError(`Error saving view settings: ${error}`);
|
||||
} finally {
|
||||
await refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
reset(data);
|
||||
};
|
||||
|
||||
if (!control) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const disableInputs = status === 'loading';
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} id='view-settings' className={style.sectionContainer}>
|
||||
<span className={style.title}>General view settings</span>
|
||||
<ModalSplitInput
|
||||
field='overrideStyles'
|
||||
title='Override CSS Styles'
|
||||
description='Enables overriding view styles with custom stylesheet'
|
||||
>
|
||||
<Switch {...register('overrideStyles')} variant='ontime-on-light' />
|
||||
</ModalSplitInput>
|
||||
<span className={style.title}>Timer view settings</span>
|
||||
<ModalSplitInput field='normalColor' title='Timer colour' description='Normal colour of a running timer'>
|
||||
<PopoverPickerRHF name='normalColor' control={control} />
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput
|
||||
field='warningColor'
|
||||
title='Warning Color'
|
||||
description='Time (in minutes) when the timer moves to warning mode'
|
||||
>
|
||||
<InputMillisWithString name='warningThreshold' control={control} />
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='warningColor' title='Warning Color' description='Colour of timer in warning mode'>
|
||||
<PopoverPickerRHF name='warningColor' control={control} />
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput
|
||||
field='dangerThreshold'
|
||||
title='Danger colour'
|
||||
description='Time (in minutes) when the timer moves to danger mode'
|
||||
>
|
||||
<InputMillisWithString name='dangerThreshold' control={control} />
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput field='dangerColor' title='Timer colour' description='Colour of timer in danger mode'>
|
||||
<PopoverPickerRHF name='dangerColor' control={control} />
|
||||
</ModalSplitInput>
|
||||
<div style={{ height: '16px' }} />
|
||||
<ModalInput
|
||||
field='endMessage'
|
||||
title='End Message'
|
||||
description='If no end message is provided, timer will continue in overtime mode'
|
||||
>
|
||||
<Input
|
||||
{...inputProps}
|
||||
variant='ontime-filled-on-light'
|
||||
maxLength={150}
|
||||
placeholder='Message to be shown when timer reaches end'
|
||||
isDisabled={disableInputs}
|
||||
{...register('endMessage')}
|
||||
/>
|
||||
</ModalInput>
|
||||
<OntimeModalFooter
|
||||
formId='view-settings'
|
||||
handleRevert={onReset}
|
||||
isDirty={isDirty}
|
||||
isValid={isValid}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user