import { OntimeView, URLPreset } from 'ontime-types'; import { generateId } from 'ontime-utils'; import { useRef, useState } from 'react'; import { FieldErrors, useForm } from 'react-hook-form'; import { generateUrl } from '../../common/api/session'; import { maybeAxiosError } from '../../common/api/utils'; import Button from '../../common/components/buttons/Button'; import CopyTag from '../../common/components/copy-tag/CopyTag'; import Info from '../../common/components/info/Info'; import Input from '../../common/components/input/input/Input'; import QRCode from '../../common/components/qr-code/QrCode'; import Select from '../../common/components/select/Select'; import Switch from '../../common/components/switch/Switch'; import { useUpdateUrlPreset } from '../../common/hooks-query/useUrlPresets'; import { safeCopyToClipboard } from '../../common/utils/copyToClipboard'; import { preventEscape } from '../../common/utils/keyEvent'; import { isUrlSafe } from '../../common/utils/regex'; import { isOntimeCloud, serverURL } from '../../externals'; import * as Panel from '../app-settings/panel-utils/PanelUtils'; import CuesheetLinkOptions from './composite/CuesheetLinkOptions'; import style from './GenerateLinkForm.module.scss'; interface GenerateLinkFormProps { hostOptions: { value: string; label: string }[]; pathOptions: { value: OntimeView | string; label: string }[]; presets: URLPreset[]; isLockedToView?: boolean; } type GenericLinkOptions = { baseUrl: string; path: OntimeView | string; // we use empty string for Companion view authenticate: boolean; lockConfig: boolean; lockNav: boolean; }; type CuesheetLinkOptions = GenericLinkOptions & { path: OntimeView.Cuesheet; alias: string; options: { read?: string; write?: string; }; }; type GenerateLinkFormOptions = GenericLinkOptions | CuesheetLinkOptions; type GenerateLinkState = 'pending' | 'loading' | 'success' | 'error'; export default function GenerateLinkForm({ hostOptions, pathOptions, presets, isLockedToView }: GenerateLinkFormProps) { const [formState, setFormState] = useState('pending'); const [url, setUrl] = useState(serverURL); const cuesheetReadRef = useRef(null); const cuesheetWriteRef = useRef(null); const generatedAlias = useRef(`cuesheet-${generateId()}`); const { addPreset } = useUpdateUrlPreset(); const { handleSubmit, setError, watch, setValue, reset, register, formState: { errors, isDirty }, } = useForm({ mode: 'onChange', defaultValues: { baseUrl: serverURL, path: isLockedToView ? pathOptions[0].value : OntimeView.Timer, authenticate: false, lockConfig: false, lockNav: false, }, }); /** * If the user is generating a link to the cuesheet we gather extra options * The extra options are saved into a URL preset which we then request a share link for */ const createPresetFromOptions = async ( alias: string, options: Required, ): Promise => { if (options.read === '-') { throw new Error('Cannot create a share with no read permissions'); } const presets = await addPreset({ target: OntimeView.Cuesheet, enabled: true, alias, search: '', options: { read: options.read, write: options.write, }, }); return presets.find((preset) => preset.alias === alias); }; const onSubmit = async (options: GenerateLinkFormOptions) => { try { setFormState('loading'); if (options.path === OntimeView.Cuesheet) { const urlPreset = await createPresetFromOptions((options as CuesheetLinkOptions).alias, { read: cuesheetReadRef.current?.value ?? 'full', write: cuesheetWriteRef.current?.value ?? 'full', }); if (!urlPreset) { throw new Error('Failed to create URL preset for Cuesheet'); } const url = await generateUrl({ baseUrl: options.baseUrl, path: options.path, authenticate: options.authenticate, lockConfig: options.lockConfig, lockNav: options.lockNav, preset: urlPreset.alias, }); await safeCopyToClipboard(url); setUrl(url); } else { const presetPath = options.path.startsWith('preset-') ? options.path.replace('preset-', '') : undefined; const path = presetPath ? presets.find((preset) => preset.alias === presetPath)?.target : options.path; if (!path) { throw new Error(`Could not resolve preset: ${path}`); } const url = await generateUrl({ baseUrl: options.baseUrl, path, authenticate: options.authenticate, lockConfig: options.lockConfig, lockNav: options.lockNav, preset: presetPath, }); await safeCopyToClipboard(url); setUrl(url); } reset(options, { keepValues: true, keepDirty: false, }); setFormState('success'); } catch (error) { const message = maybeAxiosError(error); setError('root', { message }); setFormState('error'); } }; const canSubmit = isDirty || formState !== 'success'; return (
preventEscape(event)}> {!isLockedToView && ( You can generate a link to share with your team or to use in automation (such as companion). )}
{isOntimeCloud ? ( ) : ( ) : ( )} setValue('lockNav', checked, { shouldDirty: true })} disabled={watch('lockConfig')} /> {watch('path') !== OntimeView.Cuesheet && ( { if (checked) { setValue('lockNav', checked, { shouldDirty: true }); } setValue('lockConfig', checked, { shouldDirty: true }); }} /> )} setValue('authenticate', checked, { shouldDirty: true })} /> {errors.root?.message}
Share this link
{url}
Copy link
); }