diff --git a/apps/client/src/AppRouter.tsx b/apps/client/src/AppRouter.tsx index 7e787756c..e43683833 100644 --- a/apps/client/src/AppRouter.tsx +++ b/apps/client/src/AppRouter.tsx @@ -49,12 +49,12 @@ export default function AppRouter() { } /> } /> + } /> - {/*/!* Lower cannot have fallback *!/*/} + } /> } /> - } /> {/*/!* Protected Routes *!/*/} } /> diff --git a/apps/client/src/common/api/db.ts b/apps/client/src/common/api/db.ts index 2ea617707..27a3b5e50 100644 --- a/apps/client/src/common/api/db.ts +++ b/apps/client/src/common/api/db.ts @@ -1,11 +1,5 @@ import axios, { AxiosResponse } from 'axios'; -import { - DatabaseModel, - GetInfo, - MessageResponse, - ProjectData, - ProjectFileListResponse, -} from 'ontime-types'; +import { DatabaseModel, GetInfo, MessageResponse, ProjectData, ProjectFileListResponse } from 'ontime-types'; import { makeCSV, makeTable } from '../../features/cuesheet/cuesheetUtils'; 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 264f5137d..2a54aeab4 100644 --- a/apps/client/src/common/components/navigation-menu/NavigationMenu.module.scss +++ b/apps/client/src/common/components/navigation-menu/NavigationMenu.module.scss @@ -1,12 +1,11 @@ @use '../../../theme/mixins' as *; -$menu-bg: $gray-1200; $menu-hover-bg: $gray-1350; $menu-focus-bg: $gray-1300; $icon-color: $ui-white; $button-bg: $gray-1050; -$button-size: 48px; +$button-size: 3rem; .buttonContainer { display: flex; @@ -21,7 +20,7 @@ $button-size: 48px; position: fixed; left: 0; top: 0; - z-index: 2; + z-index: 12; &.hidden { opacity: 0; @@ -29,7 +28,7 @@ $button-size: 48px; } .button { - font-size: 24px; + font-size: 1.5rem; color: $icon-color; background-color: $button-bg; width: $button-size; @@ -44,31 +43,10 @@ $button-size: 48px; z-index: 3; } -.menuContainer { - top: 0; - left: 0; - height: fit-content; - position: absolute; - background-color: $menu-bg; - min-width: 200px; - border-radius: 0 0 24px 0; - border-right: 1px solid $border-color-ondark; - - box-shadow: $box-shadow-l2; - padding-bottom: 1rem; - - max-height: 100vh; - overflow-y: auto; -} - -.buttonsContainer { - margin-top: calc(56px + 1rem); -} - .link { @include action-link; justify-content: space-between; - padding: 0.5rem 1rem; + padding: 0.75rem 1.5rem; &:hover { background-color: $menu-hover-bg; diff --git a/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx b/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx index bb28107bb..d4660ec58 100644 --- a/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx +++ b/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx @@ -1,39 +1,36 @@ -import { memo, useEffect, useRef, useState } from 'react'; +import { memo, PropsWithChildren, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import { Link, useLocation, useSearchParams } from 'react-router-dom'; -import { useDisclosure } from '@chakra-ui/react'; -import { useFullscreen } from '@mantine/hooks'; +import { + Drawer, + DrawerBody, + DrawerCloseButton, + DrawerContent, + DrawerHeader, + DrawerOverlay, + useDisclosure, +} from '@chakra-ui/react'; import { IoApps } from '@react-icons/all-files/io5/IoApps'; -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 { IoPencilSharp } from '@react-icons/all-files/io5/IoPencilSharp'; -import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical'; +import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline'; -import { navigatorConstants } from '../../../viewerConfig'; import useClickOutside from '../../hooks/useClickOutside'; -import { useViewOptionsStore } from '../../stores/viewOptions'; -import { isKeyEnter } from '../../utils/keyEvent'; - -import RenameClientModal from './rename-client-modal/RenameClientModal'; +import { debounce } from '../../utils/debounce'; import style from './NavigationMenu.module.scss'; -function NavigationMenu() { - const location = useLocation(); +interface NavigationMenuProps { + editCallback: () => void; +} + +function NavigationMenu(props: PropsWithChildren) { + const { children, editCallback } = props; - const { fullscreen, toggle } = useFullscreen(); - const { toggleMirror } = useViewOptionsStore(); const [showButton, setShowButton] = useState(false); - const [searchParams, setSearchParams] = useSearchParams(); - const [showMenu, setShowMenu] = useState(false); + const { isOpen, onOpen, onClose } = useDisclosure(); const menuRef = useRef(null); - useClickOutside(menuRef, () => setShowMenu(false)); + useClickOutside(menuRef, () => onClose); - const { isOpen, onOpen, onClose } = useDisclosure(); - - const toggleMenu = () => setShowMenu((prev) => !prev); + const toggleMenu = () => (isOpen ? onClose() : onOpen()); // show on mouse move useEffect(() => { @@ -45,98 +42,49 @@ function NavigationMenu() { } fadeOut = setTimeout(() => setShowButton(false), 3000); }; - document.addEventListener('mousemove', setShowMenuTrue); + + const debouncedShowMenu = debounce(setShowMenuTrue, 1000); + + document.addEventListener('mousemove', debouncedShowMenu); return () => { - document.removeEventListener('mousemove', setShowMenuTrue); + document.removeEventListener('mousemove', debouncedShowMenu); if (fadeOut) { clearTimeout(fadeOut); } }; }, []); - const handleFullscreen = () => toggle(); - const handleMirror = () => toggleMirror(); - - const showEditFormDrawer = () => { - searchParams.set('edit', 'true'); - setSearchParams(searchParams); - }; - 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 new file mode 100644 index 000000000..e80514578 --- /dev/null +++ b/apps/client/src/common/components/navigation-menu/ProductionNavigationMenu.tsx @@ -0,0 +1,91 @@ +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; +} + +function ProductionNavigationMenu({ handleSettings }: ProductionNavigationMenuProps) { + const location = useLocation(); + const { fullscreen, toggle } = useFullscreen(); + const { isOpen, onOpen, onClose } = useDisclosure(); + + return ( + + +
+
{ + isKeyEnter(event) && toggle(); + }} + > + Toggle Fullscreen + {fullscreen ? : } +
+
{ + isKeyEnter(event) && onOpen(); + }} + > + Rename Client +
+
+
+ + Editor + + + + Cuesheet + + + + Operator + + +
+ {navigatorConstants.map((route) => ( + + {route.label} + + + ))} +
+ ); +} + +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 new file mode 100644 index 000000000..4e14c9459 --- /dev/null +++ b/apps/client/src/common/components/navigation-menu/ViewNavigationMenu.tsx @@ -0,0 +1,87 @@ +import { memo, useCallback } from 'react'; +import { Link, useLocation, 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 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 showEditFormDrawer = useCallback(() => { + searchParams.set('edit', 'true'); + setSearchParams(searchParams); + }, [searchParams, setSearchParams]); + + 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} + + + ))} +
+ ); +} + +export default memo(ViewNavigationMenu); diff --git a/apps/client/src/common/components/navigation-menu/rename-client-modal/RenameClientModal.module.scss b/apps/client/src/common/components/navigation-menu/rename-client-modal/RenameClientModal.module.scss deleted file mode 100644 index 6d48a1ba4..000000000 --- a/apps/client/src/common/components/navigation-menu/rename-client-modal/RenameClientModal.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.modalBody { - display: flex; - flex-direction: column; - gap: 1rem; -} diff --git a/apps/client/src/common/components/navigation-menu/rename-client-modal/RenameClientModal.tsx b/apps/client/src/common/components/navigation-menu/rename-client-modal/RenameClientModal.tsx index 905d4d220..5dd2f0a4b 100644 --- a/apps/client/src/common/components/navigation-menu/rename-client-modal/RenameClientModal.tsx +++ b/apps/client/src/common/components/navigation-menu/rename-client-modal/RenameClientModal.tsx @@ -13,8 +13,6 @@ import { import { setClientName } from '../../../hooks/useSocket'; import { useSocketClientName } from '../../../stores/connectionName'; -import style from './RenameClientModal.module.scss'; - interface RenameClientModalProps { isOpen: boolean; onClose: () => void; @@ -45,18 +43,18 @@ export default function RenameClientModal({ isOpen, onClose }: RenameClientModal motionPreset='slideInBottom' scrollBehavior='inside' preserveScrollBarGap - variant='ontime-small' + variant='ontime' > Rename client - + setNewName(e.target.value)} - variant='ontime-filled-on-light' + variant='ontime-filled' /> + + + ); +} diff --git a/apps/client/src/features/app-settings/settingsStore.ts b/apps/client/src/features/app-settings/settingsStore.ts index af77dc138..45d3b47bf 100644 --- a/apps/client/src/features/app-settings/settingsStore.ts +++ b/apps/client/src/features/app-settings/settingsStore.ts @@ -51,6 +51,11 @@ export const settingPanels: Readonly = [ label: 'About', split: true, }, + { + id: 'shutdown', + label: 'Shutdown', + split: true, + }, ] as const; export type SettingsOptionId = (typeof settingPanels)[number]['id']; diff --git a/apps/client/src/features/cuesheet/CuesheetWrapper.module.scss b/apps/client/src/features/cuesheet/CuesheetWrapper.module.scss index e143c8ff8..eb82af77e 100644 --- a/apps/client/src/features/cuesheet/CuesheetWrapper.module.scss +++ b/apps/client/src/features/cuesheet/CuesheetWrapper.module.scss @@ -4,18 +4,11 @@ padding: 1rem; display: grid; - grid-template-rows: auto auto 1fr; + grid-template-rows: 3rem auto 1fr; grid-template-areas: - 'header' + 'overview' 'settings' 'table'; gap: 1rem; - - background-color: $gray-1300; - color: white; - - & > * { - border: 1px solid $white-10; - border-radius: 3px; - } + color: $ui-white; } diff --git a/apps/client/src/features/cuesheet/CuesheetWrapper.tsx b/apps/client/src/features/cuesheet/CuesheetWrapper.tsx index e7365425a..9ab91dd6d 100644 --- a/apps/client/src/features/cuesheet/CuesheetWrapper.tsx +++ b/apps/client/src/features/cuesheet/CuesheetWrapper.tsx @@ -1,17 +1,18 @@ import { useCallback, useEffect, useMemo } from 'react'; -import { CustomFieldLabel, isOntimeEvent, ProjectData } from 'ontime-types'; +import { CustomFieldLabel, isOntimeEvent } from 'ontime-types'; +import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu'; import Empty from '../../common/components/state/Empty'; import { useEventAction } from '../../common/hooks/useEventAction'; import { useCuesheet } from '../../common/hooks/useSocket'; import useCustomFields from '../../common/hooks-query/useCustomFields'; import { useFlatRundown } from '../../common/hooks-query/useRundown'; +import Overview from '../overview/Overview'; import CuesheetProgress from './cuesheet-progress/CuesheetProgress'; -import CuesheetTableHeader from './cuesheet-table-header/CuesheetTableHeader'; +import { useCuesheetSettings } from './store/CuesheetSettings'; import Cuesheet from './Cuesheet'; import { makeCuesheetColumns } from './cuesheetCols'; -import { makeCSV, makeTable } from './cuesheetUtils'; import styles from './CuesheetWrapper.module.scss'; @@ -23,6 +24,7 @@ export default function CuesheetWrapper() { const { updateCustomField } = useEventAction(); const featureData = useCuesheet(); const columns = useMemo(() => makeCuesheetColumns(customFields), [customFields]); + const toggleSettings = useCuesheetSettings((state) => state.toggleSettings); // Set window title useEffect(() => { @@ -74,39 +76,15 @@ export default function CuesheetWrapper() { [flatRundown, rundownStatus, updateCustomField], ); - const exportHandler = useCallback( - (headerData: ProjectData) => { - if (!flatRundown || rundownStatus !== 'success') { - return; - } - const sheetData = makeTable(headerData, flatRundown, customFields); - const csvContent = makeCSV(sheetData); - - const fileName = 'ontime rundown.csv'; - - const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); - const url = URL.createObjectURL(blob); - - const link = document.createElement('a'); - link.setAttribute('href', url); - link.setAttribute('download', fileName); - document.body.appendChild(link); - link.click(); - // Clean up the URL.createObjectURL to release resources - URL.revokeObjectURL(url); - return; - }, - [flatRundown, rundownStatus, customFields], - ); - if (!customFields || !flatRundown || rundownStatus !== 'success') { return ; } return (
- + + toggleSettings()} /> state.followSelected); - const showSettings = useCuesheetSettings((state) => state.showSettings); - const toggleSettings = useCuesheetSettings((state) => state.toggleSettings); const toggleFollow = useCuesheetSettings((state) => state.toggleFollow); - const { fullscreen, toggle } = useFullscreen(); const { data: project } = useProjectData(); const exportProject = () => { @@ -66,19 +59,6 @@ export default function CuesheetTableHeader({ handleExport, featureData }: Cuesh - - toggleSettings()} - className={cx([style.actionIcon, showSettings ? style.enabled : null])} - > - - - - - toggle()} className={style.actionIcon}> - {fullscreen ? : } - - Export CSV diff --git a/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx b/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx index 722d41a97..5b9a47a37 100644 --- a/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx +++ b/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx @@ -23,12 +23,14 @@ interface CuesheetTableSettingsProps { function CuesheetTableSettings(props: CuesheetTableSettingsProps) { const { columns, handleResetResizing, handleResetReordering, handleClearToggles } = props; const { + followSelected, + toggleFollow, showPrevious, - toggleDelayVisibility, + togglePreviousVisibility, showDelayBlock, showDelayedTimes, toggleDelayedTimes, - togglePreviousVisibility, + toggleDelayVisibility, } = useCuesheetSettings(); return ( @@ -53,6 +55,10 @@ function CuesheetTableSettings(props: CuesheetTableSettingsProps) {
Table Options
+