diff --git a/.eslintrc b/.eslintrc index 94aadac2d..7e5b262e8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -19,6 +19,7 @@ "rules": { "no-useless-concat": "warn", "prefer-template": "warn", + "no-throw-literal": "error", "no-console": [ "warn", { diff --git a/apps/client/src/AppRouter.tsx b/apps/client/src/AppRouter.tsx index e43683833..2f34d2acf 100644 --- a/apps/client/src/AppRouter.tsx +++ b/apps/client/src/AppRouter.tsx @@ -7,7 +7,7 @@ import withData from './features/viewers/ViewWrapper'; const Editor = lazy(() => import('./features/editors/ProtectedEditor')); const Cuesheet = lazy(() => import('./features/cuesheet/ProtectedCuesheet')); -const Operator = lazy(() => import('./features/operator/Operator')); +const Operator = lazy(() => import('./features/operator/OperatorExport')); const TimerView = lazy(() => import('./features/viewers/timer/Timer')); const MinimalTimerView = lazy(() => import('./features/viewers/minimal-timer/MinimalTimer')); diff --git a/apps/client/src/common/components/navigation-menu/FloatingNavigation.tsx b/apps/client/src/common/components/navigation-menu/FloatingNavigation.tsx new file mode 100644 index 000000000..a4b692a9c --- /dev/null +++ b/apps/client/src/common/components/navigation-menu/FloatingNavigation.tsx @@ -0,0 +1,60 @@ +import { useEffect, useState } from 'react'; +import { IoApps } from '@react-icons/all-files/io5/IoApps'; +import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline'; + +import { debounce } from '../../utils/debounce'; + +import style from './NavigationMenu.module.scss'; + +interface FloatingNavigationProps { + toggleMenu: () => void; + toggleSettings: () => void; +} + +export default function FloatingNavigation(props: FloatingNavigationProps) { + const { toggleMenu, toggleSettings } = props; + const [showButton, setShowButton] = useState(false); + + // show on mouse move + useEffect(() => { + let fadeOut: NodeJS.Timeout | null = null; + const setShowMenuTrue = () => { + setShowButton(true); + if (fadeOut) { + clearTimeout(fadeOut); + } + fadeOut = setTimeout(() => setShowButton(false), 3000); + }; + + const debouncedShowMenu = debounce(setShowMenuTrue, 1000); + + document.addEventListener('mousemove', debouncedShowMenu); + return () => { + document.removeEventListener('mousemove', debouncedShowMenu); + if (fadeOut) { + clearTimeout(fadeOut); + } + }; + }, []); + + return ( +
+ + +
+ ); +} diff --git a/apps/client/src/common/components/navigation-menu/NavigationMenu.module.scss b/apps/client/src/common/components/navigation-menu/NavigationMenu.module.scss index 2a54aeab4..acb7e09b7 100644 --- a/apps/client/src/common/components/navigation-menu/NavigationMenu.module.scss +++ b/apps/client/src/common/components/navigation-menu/NavigationMenu.module.scss @@ -45,8 +45,8 @@ $button-size: 3rem; .link { @include action-link; - justify-content: space-between; padding: 0.75rem 1.5rem; + gap: 0.5rem; &:hover { background-color: $menu-hover-bg; @@ -69,7 +69,7 @@ $button-size: 3rem; } .linkIcon { - display: inline-block; + margin-left: auto; transform: rotate(45deg); } diff --git a/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx b/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx index d4660ec58..2e2a4ee19 100644 --- a/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx +++ b/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx @@ -1,5 +1,6 @@ -import { memo, PropsWithChildren, useEffect, useRef, useState } from 'react'; +import { memo, useRef } from 'react'; import { createPortal } from 'react-dom'; +import { Link } from 'react-router-dom'; import { Drawer, DrawerBody, @@ -9,81 +10,126 @@ import { DrawerOverlay, useDisclosure, } from '@chakra-ui/react'; -import { IoApps } from '@react-icons/all-files/io5/IoApps'; -import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline'; +import { useFullscreen } from '@mantine/hooks'; +import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp'; +import { IoContract } from '@react-icons/all-files/io5/IoContract'; +import { IoExpand } from '@react-icons/all-files/io5/IoExpand'; +import { IoLockClosedOutline } from '@react-icons/all-files/io5/IoLockClosedOutline'; +import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical'; +import { navigatorConstants } from '../../../viewerConfig'; import useClickOutside from '../../hooks/useClickOutside'; -import { debounce } from '../../utils/debounce'; +import { useViewOptionsStore } from '../../stores/viewOptions'; +import { isKeyEnter } from '../../utils/keyEvent'; + +import RenameClientModal from './rename-client-modal/RenameClientModal'; import style from './NavigationMenu.module.scss'; interface NavigationMenuProps { - editCallback: () => void; + isOpen: boolean; + onClose: () => void; } -function NavigationMenu(props: PropsWithChildren) { - const { children, editCallback } = props; +function NavigationMenu(props: NavigationMenuProps) { + const { isOpen, onClose } = props; + + const { isOpen: isRenameOpen, onOpen: onRenameOpen, onClose: onRenameClose } = useDisclosure(); + + const { fullscreen, toggle } = useFullscreen(); + const { toggleMirror } = useViewOptionsStore(); - const [showButton, setShowButton] = useState(false); - const { isOpen, onOpen, onClose } = useDisclosure(); const menuRef = useRef(null); useClickOutside(menuRef, () => onClose); - const toggleMenu = () => (isOpen ? onClose() : onOpen()); - - // show on mouse move - useEffect(() => { - let fadeOut: NodeJS.Timeout | null = null; - const setShowMenuTrue = () => { - setShowButton(true); - if (fadeOut) { - clearTimeout(fadeOut); - } - fadeOut = setTimeout(() => setShowButton(false), 3000); - }; - - const debouncedShowMenu = debounce(setShowMenuTrue, 1000); - - document.addEventListener('mousemove', debouncedShowMenu); - return () => { - document.removeEventListener('mousemove', debouncedShowMenu); - if (fadeOut) { - clearTimeout(fadeOut); - } - }; - }, []); - return createPortal( , document.body, ); diff --git a/apps/client/src/common/components/navigation-menu/ProductionNavigationMenu.tsx b/apps/client/src/common/components/navigation-menu/ProductionNavigationMenu.tsx index 12ee12216..2841ba069 100644 --- a/apps/client/src/common/components/navigation-menu/ProductionNavigationMenu.tsx +++ b/apps/client/src/common/components/navigation-menu/ProductionNavigationMenu.tsx @@ -1,91 +1,16 @@ import { memo } from 'react'; -import { Link, useLocation } from 'react-router-dom'; -import { useDisclosure } from '@chakra-ui/react'; -import { useFullscreen } from '@mantine/hooks'; -import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp'; -import { IoContract } from '@react-icons/all-files/io5/IoContract'; -import { IoExpand } from '@react-icons/all-files/io5/IoExpand'; -import { navigatorConstants } from '../../../viewerConfig'; -import { isKeyEnter } from '../../utils/keyEvent'; - -import RenameClientModal from './rename-client-modal/RenameClientModal'; import NavigationMenu from './NavigationMenu'; -import style from './NavigationMenu.module.scss'; - interface ProductionNavigationMenuProps { - handleSettings: () => void; + isMenuOpen: boolean; + onMenuClose: () => void; } -function ProductionNavigationMenu({ handleSettings }: ProductionNavigationMenuProps) { - const location = useLocation(); - const { fullscreen, toggle } = useFullscreen(); - const { isOpen, onOpen, onClose } = useDisclosure(); +function ProductionNavigationMenu(props: ProductionNavigationMenuProps) { + const { isMenuOpen, onMenuClose } = props; - return ( - - -
-
{ - isKeyEnter(event) && toggle(); - }} - > - Toggle Fullscreen - {fullscreen ? : } -
-
{ - isKeyEnter(event) && onOpen(); - }} - > - Rename Client -
-
-
- - Editor - - - - Cuesheet - - - - Operator - - -
- {navigatorConstants.map((route) => ( - - {route.label} - - - ))} -
- ); + return ; } export default memo(ProductionNavigationMenu); diff --git a/apps/client/src/common/components/navigation-menu/ViewNavigationMenu.tsx b/apps/client/src/common/components/navigation-menu/ViewNavigationMenu.tsx index 4e14c9459..3cb203ac4 100644 --- a/apps/client/src/common/components/navigation-menu/ViewNavigationMenu.tsx +++ b/apps/client/src/common/components/navigation-menu/ViewNavigationMenu.tsx @@ -1,86 +1,26 @@ import { memo, useCallback } from 'react'; -import { Link, useLocation, useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; import { useDisclosure } from '@chakra-ui/react'; -import { useFullscreen } from '@mantine/hooks'; -import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp'; -import { IoContract } from '@react-icons/all-files/io5/IoContract'; -import { IoExpand } from '@react-icons/all-files/io5/IoExpand'; -import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical'; -import { navigatorConstants } from '../../../viewerConfig'; -import { useViewOptionsStore } from '../../stores/viewOptions'; -import { isKeyEnter } from '../../utils/keyEvent'; - -import RenameClientModal from './rename-client-modal/RenameClientModal'; +import FloatingNavigation from './FloatingNavigation'; import NavigationMenu from './NavigationMenu'; -import style from './NavigationMenu.module.scss'; - function ViewNavigationMenu() { - const location = useLocation(); - const { fullscreen, toggle } = useFullscreen(); - const { toggleMirror } = useViewOptionsStore(); const [searchParams, setSearchParams] = useSearchParams(); - const { isOpen, onOpen, onClose } = useDisclosure(); + const { isOpen: isMenuOpen, onOpen: onMenuOpen, onClose: onMenuClose } = useDisclosure(); const showEditFormDrawer = useCallback(() => { searchParams.set('edit', 'true'); setSearchParams(searchParams); }, [searchParams, setSearchParams]); + const toggleMenu = () => (isMenuOpen ? onMenuClose() : onMenuOpen()); + return ( - - -
-
{ - isKeyEnter(event) && toggle(); - }} - > - Toggle Fullscreen - {fullscreen ? : } -
-
toggleMirror()} - onKeyDown={(event) => { - isKeyEnter(event) && toggleMirror(); - }} - > - Flip Screen - -
-
{ - isKeyEnter(event) && onOpen(); - }} - > - Rename Client -
-
-
- {navigatorConstants.map((route) => ( - - {route.label} - - - ))} -
+ <> + + + ); } diff --git a/apps/client/src/common/components/protect-route/PinPage.tsx b/apps/client/src/common/components/protect-route/PinPage.tsx index dfbd461e7..1648f23e4 100644 --- a/apps/client/src/common/components/protect-route/PinPage.tsx +++ b/apps/client/src/common/components/protect-route/PinPage.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useState } from 'react'; -import { HStack, IconButton, PinInput, PinInputField } from '@chakra-ui/react'; +import { IconButton, PinInput, PinInputField } from '@chakra-ui/react'; import { IoCheckmark } from '@react-icons/all-files/io5/IoCheckmark'; import style from './ProtectRoute.module.scss'; @@ -39,8 +39,8 @@ export default function PinPage(props: PinPageProps) { return (
- {`Ontime ${permission || ''}`} - + {`Ontime ${permission}`} +
- } onClick={validate} /> - + } + onClick={validate} + /> +
); } diff --git a/apps/client/src/common/components/protect-route/ProtectRoute.module.scss b/apps/client/src/common/components/protect-route/ProtectRoute.module.scss index f5aca2c70..7bceda037 100644 --- a/apps/client/src/common/components/protect-route/ProtectRoute.module.scss +++ b/apps/client/src/common/components/protect-route/ProtectRoute.module.scss @@ -1,27 +1,31 @@ .container { - display: grid; - place-content: center; - height: 100vh; - padding-bottom: 30vh; + display: flex; + flex-direction: column; + align-items: center; + padding-top: 25vh; - background: $bg-container-l1; color: $ontime-color; - font-family: $ontime-font-family; font-weight: 200; - text-align: center; - font-size: 3vw; + font-size: 3rem; } .pin, .pin__failed { - padding: 20px; + display: flex; + gap: 0.125em; + padding-block: 0.5em; input { - border-radius: 50%; + border-radius: 99px; + border-color: $gray-500; + + &:hover { + border-color: $blue-500; + } } button { - margin-left: 20px; + margin-left: 1em; } } @@ -33,9 +37,9 @@ @keyframes colourFade { from { - background: $action-blue; + background: $red-500; } to { - background: rgba($action-blue, 0); + background: rgba($red-500, 0); } -} +} \ No newline at end of file diff --git a/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss b/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss index 79ef8387f..53de3badc 100644 --- a/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss +++ b/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss @@ -1,14 +1,9 @@ .drawerFooter { display: flex; - justify-content: start; + justify-content: end; gap: $section-spacing; - button[type='reset'] { - padding: 0 2em; - margin-right: auto; - } - - button[type='submit'] { + button { padding: 0 2em; } } @@ -16,6 +11,9 @@ .label { font-size: $inner-section-text-size; color: $label-gray; + display: flex; + flex-direction: column; + gap: 0.25rem } .columnSection { diff --git a/apps/client/src/common/components/view-params-editor/ViewParamsEditor.tsx b/apps/client/src/common/components/view-params-editor/ViewParamsEditor.tsx index 055257030..b2bc55f26 100644 --- a/apps/client/src/common/components/view-params-editor/ViewParamsEditor.tsx +++ b/apps/client/src/common/components/view-params-editor/ViewParamsEditor.tsx @@ -52,15 +52,16 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) { } }, [searchParams, onOpen]); - const onCloseWithoutSaving = () => { - onClose(); - + const handleClose = () => { searchParams.delete('edit'); setSearchParams(searchParams); + + onClose(); }; const resetParams = () => { setSearchParams(); + onClose(); }; const onParamsFormSubmit = (formEvent: FormEvent) => { @@ -69,10 +70,12 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) { const newParamsObject = Object.fromEntries(new FormData(formEvent.currentTarget)); const newSearchParams = getURLSearchParamsFromObj(newParamsObject, paramFields); setSearchParams(newSearchParams); + + handleClose(); }; return ( - + @@ -96,10 +99,7 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) { - diff --git a/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.module.scss b/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.module.scss index e6d01046c..004b06bcb 100644 --- a/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.module.scss +++ b/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.module.scss @@ -26,6 +26,7 @@ .buttonRow { display: flex; gap: 1rem; + align-items: center; justify-content: end; } diff --git a/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx b/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx index 7bb896df4..fb804052b 100644 --- a/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx +++ b/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx @@ -2,7 +2,7 @@ import { ChangeEvent, useRef, useState } from 'react'; import { Button, Input } from '@chakra-ui/react'; import { IoCloudOutline } from '@react-icons/all-files/io5/IoCloudOutline'; import { IoDownloadOutline } from '@react-icons/all-files/io5/IoDownloadOutline'; -import { ImportMap, unpackError } from 'ontime-utils'; +import { getErrorMessage, ImportMap } from 'ontime-utils'; import { getWorksheetNames as getWorksheetNamesExcel, @@ -59,7 +59,7 @@ export default function SourcesPanel() { setImportFlow('excel'); setHasFile('done'); } catch (error) { - const errorMessage = unpackError(error); + const errorMessage = getErrorMessage(error); setError(`Error uploading file: ${errorMessage}`); setWorksheets(null); setHasFile('none'); diff --git a/apps/client/src/features/app-settings/settingsStore.ts b/apps/client/src/features/app-settings/settingsStore.ts index 379f08407..3d4998a5a 100644 --- a/apps/client/src/features/app-settings/settingsStore.ts +++ b/apps/client/src/features/app-settings/settingsStore.ts @@ -15,7 +15,7 @@ export const settingPanels: Readonly = [ }, { id: 'general', - label: 'General', + label: 'App Settings', secondary: [ { id: 'general__manage', label: 'Manage Ontime settings' }, { id: 'general__view', label: 'View settings' }, @@ -59,6 +59,7 @@ export const settingPanels: Readonly = [ ] as const; export type SettingsOptionId = (typeof settingPanels)[number]['id']; + export interface PanelBaseProps { location?: string; } diff --git a/apps/client/src/features/control/message/MessageControl.tsx b/apps/client/src/features/control/message/MessageControl.tsx index 3d33e2754..a3b9d23f5 100644 --- a/apps/client/src/features/control/message/MessageControl.tsx +++ b/apps/client/src/features/control/message/MessageControl.tsx @@ -52,7 +52,7 @@ export default function MessageControl() { onClick={() => setMessage.timerBlink(!blink)} data-testid='toggle timer blink' > - Blink message + Blink - ); diff --git a/apps/client/src/features/rundown/event-block/EventBlock.module.scss b/apps/client/src/features/rundown/event-block/EventBlock.module.scss index 8f66bff92..2328b52e0 100644 --- a/apps/client/src/features/rundown/event-block/EventBlock.module.scss +++ b/apps/client/src/features/rundown/event-block/EventBlock.module.scss @@ -9,7 +9,7 @@ $skip-opacity: 0.2; display: grid; grid-template-areas: 'binder ... ... ...' - 'binder pb-actions times actions' + 'binder pb-actions times ...' 'binder pb-actions title title' 'binder pb-actions estatus estatus' 'binder ... ... ...'; @@ -55,7 +55,6 @@ $skip-opacity: 0.2; outline: 1px solid $block-cursor-color; } - /* we stop the eventActions from having opacity to fix issue with dropdown drawing order */ &.past:not(.skip) { .timerNote, .statusElements, @@ -153,12 +152,6 @@ $skip-opacity: 0.2; } } -.eventActions { - grid-area: actions; - height: 100%; - text-align: end; -} - .progressBg { grid-area: progb; border-radius: 1px; diff --git a/apps/client/src/features/rundown/event-block/EventBlock.tsx b/apps/client/src/features/rundown/event-block/EventBlock.tsx index 67a84ab3e..907399acc 100644 --- a/apps/client/src/features/rundown/event-block/EventBlock.tsx +++ b/apps/client/src/features/rundown/event-block/EventBlock.tsx @@ -2,16 +2,18 @@ import { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react' import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; -import { IoCopyOutline } from '@react-icons/all-files/io5/IoCopyOutline'; +import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline'; import { IoPeople } from '@react-icons/all-files/io5/IoPeople'; import { IoPeopleOutline } from '@react-icons/all-files/io5/IoPeopleOutline'; +import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline'; import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo'; import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical'; +import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline'; +import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; import { EndAction, MaybeNumber, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types'; import { useContextMenu } from '../../../common/hooks/useContextMenu'; import { useAppMode } from '../../../common/stores/appModeStore'; -import copyToClipboard from '../../../common/utils/copyToClipboard'; import { cx, getAccessibleColour } from '../../../common/utils/styleUtils'; import type { EventItemActions } from '../RundownEntry'; import { useEventIdSwapping } from '../useEventIdSwapping'; @@ -96,31 +98,25 @@ export default function EventBlock(props: EventBlockProps) { selectedEvents.size > 1 ? [ { - label: 'Visiblity', - group: [ - { - label: 'Make public', - icon: IoPeople, - onClick: () => - actionHandler('update', { - field: 'isPublic', - value: true, - }), - }, - { - label: 'Make private', - icon: IoPeopleOutline, - onClick: () => - actionHandler('update', { - field: 'isPublic', - value: false, - }), - }, - ], + label: 'Make public', + icon: IoPeople, + onClick: () => + actionHandler('update', { + field: 'isPublic', + value: true, + }), + }, + { + label: 'Make private', + icon: IoPeopleOutline, + onClick: () => + actionHandler('update', { + field: 'isPublic', + value: false, + }), }, ] : [ - { label: `Copy ID: ${eventId}`, icon: IoCopyOutline, onClick: () => copyToClipboard(eventId) }, { label: 'Toggle public', icon: IoPeopleOutline, @@ -145,6 +141,14 @@ export default function EventBlock(props: EventBlockProps) { }, isDisabled: selectedEventId == null || selectedEventId === eventId, }, + { withDivider: true, label: 'Clone', icon: IoDuplicateOutline, onClick: () => actionHandler('clone') }, + { withDivider: true, label: 'Event before', icon: IoAdd, onClick: () => actionHandler('event-before') }, + { label: 'Event after', icon: IoAdd, onClick: () => actionHandler('event') }, + { label: 'Block before', icon: IoRemoveCircleOutline, onClick: () => actionHandler('block-before') }, + { label: 'Block after', icon: IoRemoveCircleOutline, onClick: () => actionHandler('block') }, + { label: 'Delay before', icon: IoTimerOutline, onClick: () => actionHandler('delay-before') }, + { label: 'Delay after', icon: IoTimerOutline, onClick: () => actionHandler('delay') }, + { withDivider: true, label: 'Delete', icon: IoTrash, onClick: () => actionHandler('delete') }, ], ); @@ -281,7 +285,6 @@ export default function EventBlock(props: EventBlockProps) { loaded={loaded} playback={playback} isRolling={isRolling} - actionHandler={actionHandler} /> )} diff --git a/apps/client/src/features/rundown/event-block/EventBlockInner.tsx b/apps/client/src/features/rundown/event-block/EventBlockInner.tsx index e279ddba3..827fe3052 100644 --- a/apps/client/src/features/rundown/event-block/EventBlockInner.tsx +++ b/apps/client/src/features/rundown/event-block/EventBlockInner.tsx @@ -14,10 +14,8 @@ import { EndAction, MaybeString, Playback, TimerType, TimeStrategy } from 'ontim import { cx } from '../../../common/utils/styleUtils'; import { tooltipDelayMid } from '../../../ontimeConfig'; import EditableBlockTitle from '../common/EditableBlockTitle'; -import { EventItemActions } from '../RundownEntry'; import TimeInputFlow from '../time-input-flow/TimeInputFlow'; -import BlockActionMenu from './composite/BlockActionMenu'; import EventBlockPlayback from './composite/EventBlockPlayback'; import EventBlockProgressBar from './composite/EventBlockProgressBar'; @@ -46,7 +44,6 @@ interface EventBlockInnerProps { loaded: boolean; playback?: Playback; isRolling: boolean; - actionHandler: (action: EventItemActions, payload?: any) => void; } const EventBlockInner = (props: EventBlockInnerProps) => { @@ -68,7 +65,6 @@ const EventBlockInner = (props: EventBlockInnerProps) => { loaded, playback, isRolling, - actionHandler, } = props; const [renderInner, setRenderInner] = useState(false); @@ -139,9 +135,6 @@ const EventBlockInner = (props: EventBlockInnerProps) => { -
- -
); }; diff --git a/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx b/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx deleted file mode 100644 index 04452cfa3..000000000 --- a/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useCallback } from 'react'; -import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Tooltip } from '@chakra-ui/react'; -import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; -import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline'; -import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal'; -import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline'; -import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline'; -import { IoTrashBinSharp } from '@react-icons/all-files/io5/IoTrashBinSharp'; - -import { tooltipDelayMid } from '../../../../ontimeConfig'; -import { EventItemActions } from '../../RundownEntry'; - -interface BlockActionMenuProps { - enableDelete?: boolean; - showClone?: boolean; - actionHandler: (action: EventItemActions, payload?: any) => void; - className?: string; -} - -export default function BlockActionMenu(props: BlockActionMenuProps) { - const { enableDelete, showClone, actionHandler, className } = props; - - const handleAddEvent = useCallback(() => actionHandler('event'), [actionHandler]); - const handleAddDelay = useCallback(() => actionHandler('delay'), [actionHandler]); - const handleAddBlock = useCallback(() => actionHandler('block'), [actionHandler]); - const handleClone = useCallback(() => actionHandler('clone'), [actionHandler]); - const handleDelete = useCallback(() => actionHandler('delete'), [actionHandler]); - - return ( - - - } - tabIndex={-1} - variant='ontime-ghosted-white' - size='sm' - className={className} - /> - - - } onClick={handleAddEvent}> - Add Event after - - } onClick={handleAddDelay}> - Add Delay after - - } onClick={handleAddBlock}> - Add Block after - - {showClone && ( - } onClick={handleClone}> - Clone event - - )} - - } onClick={handleDelete} isDisabled={!enableDelete} color='#D20300'> - Delete - - - - ); -} diff --git a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss index 4a95ad6f0..e5b146389 100644 --- a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss +++ b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss @@ -23,7 +23,7 @@ padding: 0 0.25rem; color: $label-gray; border-radius: 2px; - background-color: $black-10 + background-color: $black-10; } .options { diff --git a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx index f85cf6e91..90f58a0fa 100644 --- a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx +++ b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx @@ -12,14 +12,14 @@ import { tooltipDelayMid } from '../../../ontimeConfig'; import style from './QuickAddBlock.module.scss'; interface QuickAddBlockProps { - showKbd: boolean; - previousEventId: string; + showKbd: 'above' | 'below' | 'none'; + previousEventId?: string; disableAddDelay?: boolean; disableAddBlock: boolean; } const QuickAddBlock = (props: QuickAddBlockProps) => { - const { showKbd, previousEventId, disableAddDelay = true, disableAddBlock } = props; + const { showKbd = 'none', previousEventId, disableAddDelay = true, disableAddBlock } = props; const { addEvent } = useEventAction(); const { emitError } = useEmitLog(); @@ -28,6 +28,8 @@ const QuickAddBlock = (props: QuickAddBlockProps) => { const { defaultPublic, linkPrevious } = useEditorSettings((state) => state.eventSettings); + const shortcutBase = showKbd === 'none' ? '' : `${deviceAlt} ${showKbd === 'above' ? '⇧' : ''}`; + const handleCreateEvent = useCallback( (eventType: SupportedEvent) => { switch (eventType) { @@ -70,6 +72,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => { [previousEventId, addEvent, emitError], ); + const canLinkPrevious = Boolean(previousEventId); + const shouldLinkPrevious = Boolean(linkPrevious) && canLinkPrevious; + return (
@@ -79,10 +84,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => { size='xs' variant='ontime-subtle-white' className={style.quickBtn} - data-testid='quick-add-event' leftIcon={} > - Event {showKbd && {`${deviceAlt} + E`}} + Event {shortcutBase && {`${shortcutBase} E`}} @@ -92,10 +96,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => { variant='ontime-subtle-white' disabled={disableAddDelay} className={style.quickBtn} - data-testid='quick-add-delay' leftIcon={} > - Delay {showKbd && {`${deviceAlt} + D`}} + Delay {shortcutBase && {`${shortcutBase} D`}} @@ -105,15 +108,20 @@ const QuickAddBlock = (props: QuickAddBlockProps) => { variant='ontime-subtle-white' disabled={disableAddBlock} className={style.quickBtn} - data-testid='quick-add-block' leftIcon={} > - Block {showKbd && {`${deviceAlt} + B`}} + Block {shortcutBase && {`${shortcutBase} B`}}
- + Link to previous diff --git a/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx b/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx index 6875c1fb0..f30635196 100644 --- a/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx +++ b/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx @@ -1,8 +1,6 @@ -import { Button, ButtonGroup, MenuButton } from '@chakra-ui/react'; -import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; +import { ButtonGroup } from '@chakra-ui/react'; import { IoOptions } from '@react-icons/all-files/io5/IoOptions'; import { IoPlay } from '@react-icons/all-files/io5/IoPlay'; -import { IoSnowOutline } from '@react-icons/all-files/io5/IoSnowOutline'; import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn'; import { AppMode, useAppMode } from '../../../common/stores/appModeStore'; @@ -16,20 +14,10 @@ export default function RundownHeader() { const setAppMode = useAppMode((state) => state.setMode); const setRunMode = () => setAppMode(AppMode.Run); const setEditMode = () => setAppMode(AppMode.Edit); - const setFreezeMode = () => setAppMode(AppMode.Freeze); return (
- } - clickHandler={setFreezeMode} - tooltip='Freeze rundown' - aria-label='Freeze rundown' - isDisabled - /> - - } aria-label='Rundown menu' variant='ontime-outlined'> - Rundown - - +
); } diff --git a/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx b/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx index ef409e266..12493d8b2 100644 --- a/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx +++ b/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx @@ -1,31 +1,16 @@ -import { memo, ReactNode, useCallback } from 'react'; -import { Menu, MenuDivider, MenuItem, MenuList } from '@chakra-ui/react'; -import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; -import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline'; -import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline'; -import { IoTrashOutline } from '@react-icons/all-files/io5/IoTrashOutline'; -import { SupportedEvent } from 'ontime-types'; +import { useCallback } from 'react'; +import { Button } from '@chakra-ui/react'; +import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; import { useEventAction } from '../../../common/hooks/useEventAction'; import { useAppMode } from '../../../common/stores/appModeStore'; import { useEventSelection } from '../useEventSelection'; -const RundownMenu = ({ children }: { children: ReactNode }) => { +export default function RundownMenu() { const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents); const setCursor = useAppMode((state) => state.setCursor); - const { addEvent, deleteAllEvents } = useEventAction(); - - const newEvent = useCallback(() => { - addEvent({ type: SupportedEvent.Event }); - }, [addEvent]); - - const newBlock = useCallback(() => { - addEvent({ type: SupportedEvent.Block }); - }, [addEvent]); - - const newDelay = useCallback(() => { - addEvent({ type: SupportedEvent.Delay }); - }, [addEvent]); + const appMode = useAppMode((state) => state.mode); + const { deleteAllEvents } = useEventAction(); const deleteAll = useCallback(() => { deleteAllEvents(); @@ -34,25 +19,15 @@ const RundownMenu = ({ children }: { children: ReactNode }) => { }, [clearSelectedEvents, deleteAllEvents, setCursor]); return ( - - {children} - - } onClick={newEvent}> - Add event at start - - } onClick={newDelay}> - Add delay at start - - } onClick={newBlock}> - Add block at start - - - } onClick={deleteAll} color='#D20300'> - Delete all events - - - + ); -}; - -export default memo(RundownMenu); +} diff --git a/apps/client/src/features/viewers/backstage/Backstage.tsx b/apps/client/src/features/viewers/backstage/Backstage.tsx index 3be11f2d8..3f0e7026d 100644 --- a/apps/client/src/features/viewers/backstage/Backstage.tsx +++ b/apps/client/src/features/viewers/backstage/Backstage.tsx @@ -14,6 +14,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard'; import { getBackstageOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import { useTranslation } from '../../../translation/TranslationProvider'; @@ -57,10 +58,7 @@ export default function Backstage(props: BackstageProps) { const [blinkClass, setBlinkClass] = useState(false); const [searchParams] = useSearchParams(); - // Set window title - useEffect(() => { - document.title = 'ontime - Backstage Screen'; - }, []); + useWindowTitle('Backstage'); // blink on change useEffect(() => { diff --git a/apps/client/src/features/viewers/clock/Clock.tsx b/apps/client/src/features/viewers/clock/Clock.tsx index 35168f8e5..ec6c7ccbe 100644 --- a/apps/client/src/features/viewers/clock/Clock.tsx +++ b/apps/client/src/features/viewers/clock/Clock.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Settings, ViewSettings } from 'ontime-types'; @@ -6,6 +5,7 @@ import { overrideStylesURL } from '../../../common/api/constants'; import { getClockOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { OverridableOptions } from '../../../common/models/View.types'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; @@ -25,9 +25,7 @@ export default function Clock(props: ClockProps) { const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); const [searchParams] = useSearchParams(); - useEffect(() => { - document.title = 'ontime - Clock'; - }, []); + useWindowTitle('Clock'); // defer rendering until we load stylesheets if (!shouldRender) { diff --git a/apps/client/src/features/viewers/countdown/Countdown.tsx b/apps/client/src/features/viewers/countdown/Countdown.tsx index 0b7f42d29..3713b5903 100644 --- a/apps/client/src/features/viewers/countdown/Countdown.tsx +++ b/apps/client/src/features/viewers/countdown/Countdown.tsx @@ -7,6 +7,7 @@ import { overrideStylesURL } from '../../../common/api/constants'; import { getCountdownOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import { useTranslation } from '../../../translation/TranslationProvider'; @@ -37,9 +38,7 @@ export default function Countdown(props: CountdownProps) { const [runningMessage, setRunningMessage] = useState(TimerMessage.unhandled); const [delay, setDelay] = useState(0); - useEffect(() => { - document.title = 'ontime - Countdown'; - }, []); + useWindowTitle('Countdown'); // eg. http://localhost:4001/countdown?eventId=ei0us // Check for user options diff --git a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx index 0c8f46f52..5c6b3c348 100644 --- a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx +++ b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx @@ -6,6 +6,7 @@ import { overrideStylesURL } from '../../../common/api/constants'; import { getLowerThirdOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { getPropertyValue } from '../common/viewUtils'; import './LowerThird.scss'; @@ -141,10 +142,7 @@ export default function LowerThird(props: LowerProps) { const [playState, setPlayState] = useState<'pre' | 'in' | 'out'>('pre'); useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); - // set window title - useEffect(() => { - document.title = 'ontime - Lower Third'; - }, []); + useWindowTitle('Lower Third'); const trigger = useMemo(() => { if (options.trigger === TriggerType.Event) { diff --git a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx index d0986e301..663bbeb05 100644 --- a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx +++ b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types'; import { MILLIS_PER_SECOND, millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils'; @@ -7,6 +6,7 @@ import { overrideStylesURL } from '../../../common/api/constants'; import { MINIMAL_TIMER_OPTIONS } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { OverridableOptions } from '../../../common/models/View.types'; import { timerPlaceholder } from '../../../common/utils/styleUtils'; @@ -28,9 +28,7 @@ export default function MinimalTimer(props: MinimalTimerProps) { const { getLocalizedString } = useTranslation(); const [searchParams] = useSearchParams(); - useEffect(() => { - document.title = 'ontime - Minimal Timer'; - }, []); + useWindowTitle('Minimal Timer'); // defer rendering until we load stylesheets if (!shouldRender) { diff --git a/apps/client/src/features/viewers/public/Public.tsx b/apps/client/src/features/viewers/public/Public.tsx index 52895fd71..d217faac9 100644 --- a/apps/client/src/features/viewers/public/Public.tsx +++ b/apps/client/src/features/viewers/public/Public.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import QRCode from 'react-qr-code'; import { useSearchParams } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; @@ -12,6 +11,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard'; import { getPublicOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import { useTranslation } from '../../../translation/TranslationProvider'; @@ -54,10 +54,7 @@ export default function Public(props: BackstageProps) { const { getLocalizedString } = useTranslation(); const [searchParams] = useSearchParams(); - // set window title - useEffect(() => { - document.title = 'ontime - Public Screen'; - }, []); + useWindowTitle('Public Schedule'); // defer rendering until we load stylesheets if (!shouldRender) { diff --git a/apps/client/src/features/viewers/studio/StudioClock.tsx b/apps/client/src/features/viewers/studio/StudioClock.tsx index b74e28016..e48f985df 100644 --- a/apps/client/src/features/viewers/studio/StudioClock.tsx +++ b/apps/client/src/features/viewers/studio/StudioClock.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import type { OntimeEvent, OntimeRundown, Settings, ViewSettings } from 'ontime-types'; import { isOntimeEvent, Playback } from 'ontime-types'; @@ -9,6 +8,7 @@ import { getStudioClockOptions } from '../../../common/components/view-params-ed import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import useFitText from '../../../common/hooks/useFitText'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; @@ -46,9 +46,7 @@ export default function StudioClock(props: StudioClockProps) { const [searchParams] = useSearchParams(); - useEffect(() => { - document.title = 'ontime - Studio Clock'; - }, []); + useWindowTitle('Studio Clock'); let clock = formatTime(time.clock); let hasAmPm = ''; diff --git a/apps/client/src/features/viewers/timer/Timer.scss b/apps/client/src/features/viewers/timer/Timer.scss index 7eedbe2f1..0155374ab 100644 --- a/apps/client/src/features/viewers/timer/Timer.scss +++ b/apps/client/src/features/viewers/timer/Timer.scss @@ -32,6 +32,8 @@ .blackout { position: absolute; + top: 0; + left: 0; width: 100vw; height: 100vh; background-color: #000; diff --git a/apps/client/src/features/viewers/timer/Timer.tsx b/apps/client/src/features/viewers/timer/Timer.tsx index 333606df6..2141877ad 100644 --- a/apps/client/src/features/viewers/timer/Timer.tsx +++ b/apps/client/src/features/viewers/timer/Timer.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; import { @@ -19,6 +18,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard'; import { getTimerOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { timerPlaceholder } from '../../../common/utils/styleUtils'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; @@ -63,9 +63,7 @@ export default function Timer(props: TimerProps) { const { getLocalizedString } = useTranslation(); const [searchParams] = useSearchParams(); - useEffect(() => { - document.title = 'ontime - Timer'; - }, []); + useWindowTitle('Timer'); // defer rendering until we load stylesheets if (!shouldRender) { diff --git a/apps/client/src/theme/ontimeCheckbox.ts b/apps/client/src/theme/ontimeCheckbox.ts index 003937d95..345c928fb 100644 --- a/apps/client/src/theme/ontimeCheckbox.ts +++ b/apps/client/src/theme/ontimeCheckbox.ts @@ -3,12 +3,24 @@ export const ontimeCheckboxOnDark = { border: '1px', borderColor: '#2d2d2d', // $gray-1100 backgroundColor: '#2d2d2d', // $gray-1100 + _disabled: { + color: 'white', + borderColor: '#2d2d2d', // $gray-1100 + backgroundColor: '#2d2d2d', // $gray-1100 + opacity: 0.6, + }, _checked: { borderColor: '#3182ce', // $action-blue backgroundColor: '#3182ce', //$action-blue + _disabled: { + color: 'white', + borderColor: '#3182ce', // $action-blue + backgroundColor: '#3182ce', //$action-blue + opacity: 0.6, + }, }, _focus: { - boxShadow: '0 0 0 1px #578AF4', // $blue-500 + boxShadow: 'none', }, }, label: { diff --git a/apps/client/src/theme/ontimeMenu.ts b/apps/client/src/theme/ontimeMenu.ts index b38ddebad..59f01daa4 100644 --- a/apps/client/src/theme/ontimeMenu.ts +++ b/apps/client/src/theme/ontimeMenu.ts @@ -1,19 +1,27 @@ export const ontimeMenuOnDark = { list: { + fontSize: 'calc(1rem - 2px)', borderRadius: '3px', - border: 'none', - bg: '#fff', // $gray-50 + borderColor: 'rgba(255, 255, 255, 0.1)', + color: '#ececec', // $gray-1030 + backgroundColor: '#202020', // $gray-1250 zIndex: 100, }, item: { - letterSpacing: '0.15px', - color: '#101010', // $gray-1350 - bg: '#fff', // + backgroundColor: 'transparent', + paddingBlock: '0.5rem', _hover: { - backgroundColor: '#e2e2e2', // $gray-200 + backgroundColor: 'rgba(0, 0, 0, 0.1)', + _disabled: { + backgroundColor: 'transparent', + }, + }, + _disabled: { + color: '#b1b1b1', // $gray-400 }, }, divider: { - borderColor: '#cfcfcf', // $gray-200 + borderColor: 'rgba(255, 255, 255, 0.07)', + opacity: 1, }, }; diff --git a/apps/client/src/theme/theme.ts b/apps/client/src/theme/theme.ts index a34c6cb8b..7681c3bdf 100644 --- a/apps/client/src/theme/theme.ts +++ b/apps/client/src/theme/theme.ts @@ -57,8 +57,8 @@ const theme = extendTheme({ }, Drawer: { variants: { - 'ontime': {...ontimeDrawer}, - } + ontime: { ...ontimeDrawer }, + }, }, Editable: { variants: { diff --git a/apps/server/package.json b/apps/server/package.json index 3dc0c112c..513a292b4 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -43,6 +43,7 @@ "nodemon": "^2.0.20", "ontime-types": "workspace:*", "prettier": "^3.0.3", + "server-timing": "^3.3.3", "shx": "^0.3.4", "ts-node": "^10.9.1", "typescript": "^5.4.3", diff --git a/apps/server/src/adapters/WebsocketAdapter.ts b/apps/server/src/adapters/WebsocketAdapter.ts index 079f8aa54..f953af9a1 100644 --- a/apps/server/src/adapters/WebsocketAdapter.ts +++ b/apps/server/src/adapters/WebsocketAdapter.ts @@ -116,16 +116,14 @@ export class SocketServer implements IAdapter { // Protocol specific stuff handled above try { - const reply = dispatchFromAdapter( - type, - { - payload, - }, - 'ws', - ); + const reply = dispatchFromAdapter(type, { payload }, 'ws'); if (reply) { - const { payload } = reply; - ws.send(type, payload); + ws.send( + JSON.stringify({ + type: 'ontime-change', + payload: reply.payload, + }), + ); } } catch (error) { logger.error(LogOrigin.Rx, `WS IN: ${error}`); diff --git a/apps/server/src/api-data/custom-fields/customFields.controller.ts b/apps/server/src/api-data/custom-fields/customFields.controller.ts index 42c428c08..71c55f5f8 100644 --- a/apps/server/src/api-data/custom-fields/customFields.controller.ts +++ b/apps/server/src/api-data/custom-fields/customFields.controller.ts @@ -1,7 +1,8 @@ -import { CustomField, CustomFields } from 'ontime-types'; +import { CustomField, CustomFields, ErrorResponse } from 'ontime-types'; import type { Request, Response } from 'express'; +import { getErrorMessage } from 'ontime-utils'; import { createCustomField, editCustomField, @@ -14,36 +15,37 @@ export async function getCustomFields(_req: Request, res: Response res.json(customFields); } -// Expects { label: