From ce14aabcd7ba1e7e73d5b6acaa32512d4a9e4665 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Wed, 27 Mar 2024 21:02:40 +0100 Subject: [PATCH] Navigation (#850) --- apps/client/src/AppRouter.tsx | 4 +- apps/client/src/common/api/db.ts | 8 +- .../NavigationMenu.module.scss | 30 +--- .../navigation-menu/NavigationMenu.tsx | 152 ++++++------------ .../ProductionNavigationMenu.tsx | 91 +++++++++++ .../navigation-menu/ViewNavigationMenu.tsx | 87 ++++++++++ .../RenameClientModal.module.scss | 5 - .../rename-client-modal/RenameClientModal.tsx | 8 +- .../ViewParamsEditor.module.scss | 10 -- .../view-params-editor/ViewParamsEditor.tsx | 8 +- .../src/features/app-settings/AppSettings.tsx | 2 + .../panel/shutdown-panel/ShutdownPanel.tsx | 27 ++++ .../features/app-settings/settingsStore.ts | 5 + .../cuesheet/CuesheetWrapper.module.scss | 13 +- .../src/features/cuesheet/CuesheetWrapper.tsx | 36 +---- .../CuesheetTableHeader.tsx | 20 --- .../CuesheetTableSettings.tsx | 10 +- .../src/features/editors/Editor.module.scss | 7 +- apps/client/src/features/editors/Editor.tsx | 49 ++++-- .../src/features/menu/MenuBar.module.scss | 20 --- apps/client/src/features/menu/MenuBar.tsx | 83 ---------- .../client/src/features/operator/Operator.tsx | 11 +- .../client/src/features/overview/Overview.tsx | 42 +---- .../src/features/overview/overviewUtils.ts | 47 ++++++ .../src/features/viewers/ViewWrapper.tsx | 50 +++--- .../features/viewers/backstage/Backstage.tsx | 2 - .../src/features/viewers/clock/Clock.tsx | 2 - .../features/viewers/countdown/Countdown.tsx | 2 - .../viewers/lower-thirds/LowerThird.tsx | 2 - .../viewers/minimal-timer/MinimalTimer.tsx | 2 - .../src/features/viewers/public/Public.tsx | 2 - .../features/viewers/studio/StudioClock.tsx | 2 - .../src/features/viewers/timer/Timer.tsx | 2 - apps/client/src/theme/OntimeAlert.ts | 12 -- apps/client/src/theme/OntimeProgress.ts | 8 - apps/client/src/theme/ontimeButton.ts | 32 ---- apps/client/src/theme/ontimeDrawer.ts | 20 +++ apps/client/src/theme/ontimeModal.ts | 35 +--- apps/client/src/theme/ontimeSwitch.ts | 13 -- apps/client/src/theme/ontimeTextInputs.ts | 40 +---- apps/client/src/theme/theme.ts | 33 ++-- e2e/tests/000-upload-showfile.spec.ts | 2 +- e2e/tests/002-view-navigation.spec.ts | 16 +- e2e/tests/features/202-cuesheet.spec.ts | 34 +--- e2e/tests/features/206-url-preset.spec.ts | 2 +- ...spec.ts => 301-spreadsheet-import.spec.ts} | 2 +- 46 files changed, 476 insertions(+), 614 deletions(-) create mode 100644 apps/client/src/common/components/navigation-menu/ProductionNavigationMenu.tsx create mode 100644 apps/client/src/common/components/navigation-menu/ViewNavigationMenu.tsx delete mode 100644 apps/client/src/common/components/navigation-menu/rename-client-modal/RenameClientModal.module.scss create mode 100644 apps/client/src/features/app-settings/panel/shutdown-panel/ShutdownPanel.tsx delete mode 100644 apps/client/src/features/menu/MenuBar.module.scss delete mode 100644 apps/client/src/features/menu/MenuBar.tsx create mode 100644 apps/client/src/features/overview/overviewUtils.ts delete mode 100644 apps/client/src/theme/OntimeProgress.ts create mode 100644 apps/client/src/theme/ontimeDrawer.ts rename e2e/tests/features/{207-spreadsheet-import.spec.ts => 301-spreadsheet-import.spec.ts} (95%) 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
+