From bac2dc6b8798b3ee808d8788e491ed9fa2e8fe16 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Sat, 6 Jan 2024 21:38:16 +0100 Subject: [PATCH] feat: create project list (#671) --- apps/client/src/common/api/apiConstants.ts | 13 ++-- apps/client/src/common/api/ontimeApi.ts | 12 +++ .../navigation-menu/NavigationMenu.tsx | 4 +- .../src/common/hooks-query/useProjectList.ts | 24 ++++++ apps/client/src/common/utils/keyEvent.ts | 5 ++ .../src/features/app-settings/AppSettings.tsx | 6 +- .../app-settings/panel-list/PanelList.tsx | 9 +-- .../app-settings/panel/Panel.module.scss | 30 ++++++++ .../app-settings/panel/PanelUtils.tsx | 6 +- .../panel/project-panel/ProjectList.tsx | 76 +++++++++++++++++++ .../project-panel/ProjectPanel.module.scss | 10 +++ .../panel/project-panel/ProjectPanel.tsx | 22 ++++++ .../features/app-settings/settingsStore.ts | 16 ++-- .../preview/PreviewTable.module.scss | 19 +++-- .../ontime-controller/BackendResponse.type.ts | 2 +- 15 files changed, 220 insertions(+), 34 deletions(-) create mode 100644 apps/client/src/common/hooks-query/useProjectList.ts create mode 100644 apps/client/src/common/utils/keyEvent.ts create mode 100644 apps/client/src/features/app-settings/panel/project-panel/ProjectList.tsx create mode 100644 apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss create mode 100644 apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.tsx diff --git a/apps/client/src/common/api/apiConstants.ts b/apps/client/src/common/api/apiConstants.ts index e0364c012..f6014cade 100644 --- a/apps/client/src/common/api/apiConstants.ts +++ b/apps/client/src/common/api/apiConstants.ts @@ -1,14 +1,15 @@ // REST stuff -export const PROJECT_DATA = ['project']; export const ALIASES = ['aliases']; -export const USERFIELDS = ['userFields']; -export const RUNDOWN = ['rundown']; export const APP_INFO = ['appinfo']; -export const OSC_SETTINGS = ['oscSettings']; -export const HTTP_SETTINGS = ['httpSettings']; export const APP_SETTINGS = ['appSettings']; -export const VIEW_SETTINGS = ['viewSettings']; +export const HTTP_SETTINGS = ['httpSettings']; +export const OSC_SETTINGS = ['oscSettings']; +export const PROJECT_DATA = ['project']; +export const PROJECT_LIST = ['projectList']; +export const RUNDOWN = ['rundown']; export const RUNTIME = ['runtimeStore']; +export const USERFIELDS = ['userFields']; +export const VIEW_SETTINGS = ['viewSettings']; const location = window.location; const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws'; diff --git a/apps/client/src/common/api/ontimeApi.ts b/apps/client/src/common/api/ontimeApi.ts index 5c45a0b2e..b06aa6692 100644 --- a/apps/client/src/common/api/ontimeApi.ts +++ b/apps/client/src/common/api/ontimeApi.ts @@ -8,6 +8,7 @@ import { OSCSettings, OscSubscription, ProjectData, + ProjectFileListResponse, Settings, UserFields, ViewSettings, @@ -242,6 +243,17 @@ export async function getLatestVersion(): Promise { }; } +/** + * @description HTTP POST request to create a new project file with given project data + */ export async function postNew(initialData: Partial) { return axios.post(`${ontimeURL}/new`, initialData); } + +/** + * @description HTTP request to get the list of available project files + */ +export async function getProjects(): Promise { + const res = await axios.get(`${ontimeURL}/projects`); + return res.data; +} diff --git a/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx b/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx index 078aafb96..8fa4bbf1e 100644 --- a/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx +++ b/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx @@ -1,4 +1,4 @@ -import { KeyboardEvent, memo, useEffect, useRef, useState } from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { Link, useLocation, useSearchParams } from 'react-router-dom'; import { useDisclosure } from '@chakra-ui/react'; @@ -13,6 +13,7 @@ import { navigatorConstants } from '../../../viewerConfig'; import useClickOutside from '../../hooks/useClickOutside'; import useFullscreen from '../../hooks/useFullscreen'; import { useViewOptionsStore } from '../../stores/viewOptions'; +import { isKeyEnter } from '../../utils/keyEvent'; import RenameClientModal from './rename-client-modal/RenameClientModal'; @@ -53,7 +54,6 @@ function NavigationMenu() { }; }, []); - const isKeyEnter = (event: KeyboardEvent) => event.key === 'Enter'; const handleFullscreen = () => toggleFullScreen(); const handleMirror = () => toggleMirror(); diff --git a/apps/client/src/common/hooks-query/useProjectList.ts b/apps/client/src/common/hooks-query/useProjectList.ts new file mode 100644 index 000000000..9527f19dc --- /dev/null +++ b/apps/client/src/common/hooks-query/useProjectList.ts @@ -0,0 +1,24 @@ +import { useQuery } from '@tanstack/react-query'; +import { ProjectFileListResponse } from 'ontime-types'; + +import { queryRefetchIntervalSlow } from '../../ontimeConfig'; +import { PROJECT_LIST } from '../api/apiConstants'; +import { getProjects } from '../api/ontimeApi'; + +const placeholderProjectList: ProjectFileListResponse = { + files: [], + lastLoadedProject: '', +}; + +export function useProjectList() { + const { data, status } = useQuery({ + queryKey: PROJECT_LIST, + queryFn: getProjects, + placeholderData: placeholderProjectList, + retry: 5, + retryDelay: (attempt: number) => attempt * 2500, + refetchInterval: queryRefetchIntervalSlow, + networkMode: 'always', + }); + return { data: data ?? placeholderProjectList, status }; +} diff --git a/apps/client/src/common/utils/keyEvent.ts b/apps/client/src/common/utils/keyEvent.ts new file mode 100644 index 000000000..1a6cc361a --- /dev/null +++ b/apps/client/src/common/utils/keyEvent.ts @@ -0,0 +1,5 @@ +import { KeyboardEvent } from 'react'; + +export function isKeyEnter(event: KeyboardEvent): boolean { + return event.key === 'Enter'; +} diff --git a/apps/client/src/features/app-settings/AppSettings.tsx b/apps/client/src/features/app-settings/AppSettings.tsx index 07f7394cd..65d2b36bc 100644 --- a/apps/client/src/features/app-settings/AppSettings.tsx +++ b/apps/client/src/features/app-settings/AppSettings.tsx @@ -3,6 +3,7 @@ import { ErrorBoundary } from '@sentry/react'; import { useKeyDown } from '../../common/hooks/useKeyDown'; import AboutPanel from './panel/about-panel/AboutPanel'; +import ProjectPanel from './panel/project-panel/ProjectPanel'; import PanelContent from './panel-content/PanelContent'; import PanelList from './panel-list/PanelList'; import { useSettingsStore } from './settingsStore'; @@ -22,7 +23,10 @@ export default function AppSettings() {
- {selectedPanel === 'about' && } + + {selectedPanel === 'project' && } + {selectedPanel === 'about' && } +
); diff --git a/apps/client/src/features/app-settings/panel-list/PanelList.tsx b/apps/client/src/features/app-settings/panel-list/PanelList.tsx index 1a4282021..3a95b9bce 100644 --- a/apps/client/src/features/app-settings/panel-list/PanelList.tsx +++ b/apps/client/src/features/app-settings/panel-list/PanelList.tsx @@ -1,5 +1,6 @@ -import { KeyboardEvent } from 'react'; +import { Fragment } from 'react'; +import { isKeyEnter } from '../../../common/utils/keyEvent'; import { cx } from '../../../common/utils/styleUtils'; import { settingPanels, SettingsOption, useSettingsStore } from '../settingsStore'; @@ -12,8 +13,6 @@ export default function PanelList() { setShowSettings(panel.id); }; - const isKeyEnter = (event: KeyboardEvent) => event.key === 'Enter'; - return (
    {settingPanels.map((panel) => { @@ -27,7 +26,7 @@ export default function PanelList() { ]); return ( - <> +
  • handleSelect(panel)} @@ -47,7 +46,7 @@ export default function PanelList() {
  • ); })} - +
    ); })}
diff --git a/apps/client/src/features/app-settings/panel/Panel.module.scss b/apps/client/src/features/app-settings/panel/Panel.module.scss index d4fe23e75..049b5b146 100644 --- a/apps/client/src/features/app-settings/panel/Panel.module.scss +++ b/apps/client/src/features/app-settings/panel/Panel.module.scss @@ -10,7 +10,11 @@ .subheader { font-size: 1.25rem; + padding-bottom: 0.25rem; font-weight: 600; + display: flex; + align-items: center; + justify-content: space-between; } .section { @@ -35,3 +39,29 @@ display: block; color: $error-red; } + +.table { + width: 100%; + border-collapse: collapse; + font-size: calc(1rem - 2px); + text-align: left; + + tr { + padding: 1rem 0; + } + + th { + border-bottom: 1px solid $white-10; + font-weight: 400; + color: $gray-400; + } + + th, + td { + padding: 0.5rem; + } + + tr:nth-child(even) { + background-color: $white-1; + } +} diff --git a/apps/client/src/features/app-settings/panel/PanelUtils.tsx b/apps/client/src/features/app-settings/panel/PanelUtils.tsx index 57aa6367f..282ffb04e 100644 --- a/apps/client/src/features/app-settings/panel/PanelUtils.tsx +++ b/apps/client/src/features/app-settings/panel/PanelUtils.tsx @@ -11,7 +11,7 @@ export function SubHeader({ children }: { children: ReactNode }) { } export function Section({ children }: { children: ReactNode }) { - return

{children}

; + return
{children}
; } export function Paragraph({ children }: { children: ReactNode }) { @@ -21,3 +21,7 @@ export function Paragraph({ children }: { children: ReactNode }) { export function Card({ children }: { children: ReactNode }) { return
{children}
; } + +export function Table({ children }: { children: ReactNode }) { + return {children}
; +} diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectList.tsx b/apps/client/src/features/app-settings/panel/project-panel/ProjectList.tsx new file mode 100644 index 000000000..705cf31c0 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectList.tsx @@ -0,0 +1,76 @@ +import { IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; +import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal'; + +import { useProjectList } from '../../../../common/hooks-query/useProjectList'; +import * as Panel from '../PanelUtils'; + +import style from './ProjectPanel.module.scss'; + +export default function ProjectList() { + const { data } = useProjectList(); + const { files, lastLoadedProject } = data; + + // extract currently loaded from file list + const currentlyLoadedIndex = files.findIndex((project) => project.filename === lastLoadedProject); + const projectFiles = [...files]; + const current = projectFiles.splice(currentlyLoadedIndex, 1)[0]; + + return ( + + + + Project Name + Date Created + Date Modified + + + + + {current && ( + + {current.filename} + {new Date(current.createdAt).toLocaleString()} + {new Date(current.updatedAt).toLocaleString()} + + + + + )} + {projectFiles.map((project) => { + const createdAt = new Date(project.createdAt).toLocaleString(); + const updatedAt = new Date(project.updatedAt).toLocaleString(); + return ( + + {project.filename} + {createdAt} + {updatedAt} + + + + + ); + })} + + + ); +} + +function ActionMenu() { + return ( + + } + variant='ontime-ghosted' + size='sm' + /> + + Load + Rename + Duplicate + Delete + + + ); +} diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss b/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss new file mode 100644 index 000000000..f930e5301 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss @@ -0,0 +1,10 @@ +@use '../../../../theme/ontimeColours' as *; + +.current { + color: $blue-400; + background-color: $gray-1350; +} + +.actionButton { + text-align: right; +} diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.tsx b/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.tsx new file mode 100644 index 000000000..cbb072eaf --- /dev/null +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.tsx @@ -0,0 +1,22 @@ +import { Button } from '@chakra-ui/react'; + +import * as Panel from '../PanelUtils'; + +import ProjectList from './ProjectList'; + +export default function ProjectPanel() { + return ( + <> + Project + + + + Manage projects + + + + + + + ); +} diff --git a/apps/client/src/features/app-settings/settingsStore.ts b/apps/client/src/features/app-settings/settingsStore.ts index 4442562a8..a798d60a8 100644 --- a/apps/client/src/features/app-settings/settingsStore.ts +++ b/apps/client/src/features/app-settings/settingsStore.ts @@ -8,22 +8,26 @@ export type SettingsOption = { }; export const settingPanels: SettingsOption[] = [ - { id: 'project', label: 'Project' }, + { + id: 'project', + label: 'Project', + secondary: [{ id: 'project__manage', label: 'Manage project files' }], + }, { id: 'general', label: 'General' }, { id: 'interface', label: 'Interface' }, { id: 'views', label: 'Views' }, { id: 'sources', label: 'Data Sources', - secondary: [{ id: 'g-sheet', label: 'Sync with Google Sheet' }], + secondary: [{ id: 'sources__gsheet', label: 'Sync with Google Sheet' }], split: true, }, { id: 'integrations', label: 'Integrations', secondary: [ - { id: 'osc', label: 'OSC Integration' }, - { id: 'http', label: 'HTTP Integration' }, + { id: 'integrations__osc', label: 'OSC Integration' }, + { id: 'integrations__http', label: 'HTTP Integration' }, ], }, { id: 'log', label: 'Log', split: true }, @@ -31,10 +35,6 @@ export const settingPanels: SettingsOption[] = [ id: 'about', label: 'About', split: true, - secondary: [ - { id: 'links', label: 'Links' }, - { id: 'version', label: 'Version' }, - ], }, ] as const; diff --git a/apps/client/src/features/modals/upload-modal/preview/PreviewTable.module.scss b/apps/client/src/features/modals/upload-modal/preview/PreviewTable.module.scss index 008913718..41d950a7f 100644 --- a/apps/client/src/features/modals/upload-modal/preview/PreviewTable.module.scss +++ b/apps/client/src/features/modals/upload-modal/preview/PreviewTable.module.scss @@ -1,4 +1,4 @@ -@use "../../../../theme/_ontimeColours" as *; +@use '../../../../theme/_ontimeColours' as *; .container { max-width: 100%; @@ -9,6 +9,14 @@ .rundownPreview { font-size: calc(1rem - 2px); overflow-x: scroll; + tr td:first-child, + tr th:first-child { + position: sticky; + left: 0; + z-index: 2; + background-color: white; + box-shadow: 1px 0 $gray-50; + } } .header, @@ -60,12 +68,3 @@ background-color: $gray-50; } } - -table tr td:first-child, -table tr th:first-child { - position: sticky; - left: 0; - z-index: 2; - background-color: white; - box-shadow: 1px 0 $gray-50; -} \ No newline at end of file diff --git a/packages/types/src/api/ontime-controller/BackendResponse.type.ts b/packages/types/src/api/ontime-controller/BackendResponse.type.ts index 5380aa25d..f9bdb6150 100644 --- a/packages/types/src/api/ontime-controller/BackendResponse.type.ts +++ b/packages/types/src/api/ontime-controller/BackendResponse.type.ts @@ -19,7 +19,7 @@ export type ProjectFile = { updatedAt: string; }; -export type ProjectFileList = Array; +export type ProjectFileList = ProjectFile[]; export type ProjectFileListResponse = { files: ProjectFileList;