From 42147d4d5b19f0658816a8cb30c4b3115fb22162 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:24:47 +0200 Subject: [PATCH] allow editing cuesheet link permissions --- .../panel/feature-panel/URLPresets.tsx | 8 +- .../feature-panel/composite/URLPresetForm.tsx | 120 +++++++--- .../src/features/sharing/GenerateLinkForm.tsx | 63 ++++-- .../sharing/composite/CuesheetLinkOptions.tsx | 207 ++++++++++-------- 4 files changed, 258 insertions(+), 140 deletions(-) diff --git a/apps/client/src/features/app-settings/panel/feature-panel/URLPresets.tsx b/apps/client/src/features/app-settings/panel/feature-panel/URLPresets.tsx index 26e6f0d83..8cbb5a5a2 100644 --- a/apps/client/src/features/app-settings/panel/feature-panel/URLPresets.tsx +++ b/apps/client/src/features/app-settings/panel/feature-panel/URLPresets.tsx @@ -1,4 +1,4 @@ -import { URLPreset } from 'ontime-types'; +import { OntimeView, URLPreset } from 'ontime-types'; import { useState } from 'react'; import { IoAdd, IoOpenOutline, IoPencil, IoTrash } from 'react-icons/io5'; @@ -78,20 +78,22 @@ export default function URLPresets() { {data.length === 0 && } {data.map((preset, index) => { + const isCuesheet = preset.target === OntimeView.Cuesheet; return ( persistPreset({ ...preset, enabled: checked })} + onCheckedChange={(enabled) => persistPreset({ ...preset, enabled })} disabled={isMutating} + aria-label='Toggle preset enabled' /> persistPreset({ ...preset, displayInNav: checked })} - disabled={isMutating} + disabled={isMutating || isCuesheet} /> diff --git a/apps/client/src/features/app-settings/panel/feature-panel/composite/URLPresetForm.tsx b/apps/client/src/features/app-settings/panel/feature-panel/composite/URLPresetForm.tsx index faa264e31..2d87612fc 100644 --- a/apps/client/src/features/app-settings/panel/feature-panel/composite/URLPresetForm.tsx +++ b/apps/client/src/features/app-settings/panel/feature-panel/composite/URLPresetForm.tsx @@ -1,5 +1,5 @@ import { OntimeView, OntimeViewPresettable, URLPreset } from 'ontime-types'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { maybeAxiosError, unwrapError } from '../../../../../common/api/utils'; @@ -12,6 +12,7 @@ import { preventEscape } from '../../../../../common/utils/keyEvent'; import { isUrlSafe } from '../../../../../common/utils/regex'; import { enDash } from '../../../../../common/utils/styleUtils'; import { generateUrlPresetOptions } from '../../../../../common/utils/urlPresets'; +import CuesheetLinkOptions, { CuesheetPermissionValues } from '../../../../sharing/composite/CuesheetLinkOptions'; import * as Panel from '../../../panel-utils/PanelUtils'; import style from './URLPresetForm.module.scss'; @@ -62,12 +63,42 @@ export default function URLPresetForm({ urlPreset, onClose }: URLPresetFormProps }); const urlRef = useRef(null); + // Cuesheet read/write permissions live outside react-hook-form + const initialPermissions = useRef({ + read: urlPreset?.options?.read ?? 'full', + write: urlPreset?.options?.write ?? 'full', + }); + const [cuesheetPermissions, setCuesheetPermissions] = useState(initialPermissions.current); + + // update initial permissions on mount + useEffect(() => { + initialPermissions.current = { + read: urlPreset?.options?.read ?? 'full', + write: urlPreset?.options?.write ?? 'full', + }; + setCuesheetPermissions(initialPermissions.current); + // oxlint-disable-next-line eslint-plugin-react-hooks/exhaustive-deps -- run on mount + }, []); + + const isEditingCuesheet = urlPreset && urlPreset.target === OntimeView.Cuesheet; + const isCuesheet = watch('target') === OntimeView.Cuesheet; + const permissionsDirty = + isCuesheet && + (cuesheetPermissions.read !== initialPermissions.current.read || + cuesheetPermissions.write !== initialPermissions.current.write); + const noReadAccess = isCuesheet && cuesheetPermissions.read === '-'; + const setupSubmit = async (data: URLPreset) => { try { + // Preserve / apply cuesheet permissions, which are not part of the form fields + const payload: URLPreset = + data.target === OntimeView.Cuesheet + ? { ...data, target: OntimeView.Cuesheet, options: cuesheetPermissions } + : data; if (urlPreset) { - await updatePreset(urlPreset.alias, data); + await updatePreset(urlPreset.alias, payload); } else { - await addPreset(data); + await addPreset(payload); } onClose(); } catch (error) { @@ -75,6 +106,7 @@ export default function URLPresetForm({ urlPreset, onClose }: URLPresetFormProps } }; + // focus on alias when the form opens useEffect(() => { setFocus('alias'); }, [setFocus]); @@ -127,44 +159,66 @@ export default function URLPresetForm({ urlPreset, onClose }: URLPresetFormProps
Generate options (paste URL to generate options) - - + +
{errors.alias?.message && {errors.alias.message}} -
- {enDash} or {enDash} -
-
2. Choose a view and its parameters
-
- Target -