mirror of
https://github.com/cpvalente/ontime.git
synced 2026-07-26 10:38:55 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1aeaebe1dd | |||
| 2bcd816ff6 | |||
| f6e7f3d57a | |||
| ffd81d2c07 |
@@ -1,24 +1,18 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ProjectFile, ProjectFileList, ProjectFileListResponse } from 'ontime-types';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ProjectFile, ProjectFileList } from 'ontime-types';
|
||||
import { MILLIS_PER_HOUR } from 'ontime-utils';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
||||
import { PROJECT_LIST } from '../api/constants';
|
||||
import { getProjects } from '../api/db';
|
||||
|
||||
const placeholderProjectList: ProjectFileListResponse = {
|
||||
files: [],
|
||||
lastLoadedProject: '',
|
||||
};
|
||||
|
||||
function useProjectList() {
|
||||
const { data, status, refetch } = useQuery({
|
||||
export function useProjectList() {
|
||||
const { data, status, refetch } = useSuspenseQuery({
|
||||
queryKey: PROJECT_LIST,
|
||||
queryFn: ({ signal }) => getProjects({ signal }),
|
||||
placeholderData: (previousData, _previousQuery) => previousData,
|
||||
refetchInterval: queryRefetchIntervalSlow,
|
||||
staleTime: MILLIS_PER_HOUR,
|
||||
});
|
||||
return { data: data ?? placeholderProjectList, status, refetch };
|
||||
return { data, status, refetch };
|
||||
}
|
||||
|
||||
export type ProjectSortMode = 'alphabetical-asc' | 'alphabetical-desc' | 'modified-asc' | 'modified-desc';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ProjectRundownsList } from 'ontime-types';
|
||||
import { MILLIS_PER_HOUR } from 'ontime-utils';
|
||||
|
||||
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
||||
import { PROJECT_RUNDOWNS } from '../api/constants';
|
||||
import {
|
||||
createRundown,
|
||||
@@ -11,24 +11,21 @@ import {
|
||||
loadRundown,
|
||||
renameRundown,
|
||||
} from '../api/rundown';
|
||||
import { ontimeQueryClient } from '../queryClient';
|
||||
|
||||
//TODO: make suspends so we don't have to deal with no value all over
|
||||
/**
|
||||
* Project rundowns
|
||||
*/
|
||||
export function useProjectRundowns() {
|
||||
const { data, status, isError, refetch, isFetching } = useQuery<ProjectRundownsList>({
|
||||
const { data, status, isError, refetch, isFetching } = useSuspenseQuery<ProjectRundownsList>({
|
||||
queryKey: PROJECT_RUNDOWNS,
|
||||
queryFn: ({ signal }) => fetchProjectRundownList({ signal }),
|
||||
placeholderData: (previousData, _previousQuery) => previousData,
|
||||
refetchInterval: queryRefetchIntervalSlow,
|
||||
staleTime: MILLIS_PER_HOUR,
|
||||
});
|
||||
return { data: data ?? { loaded: '', rundowns: [] }, status, isError, refetch, isFetching };
|
||||
return { data, status, isError, refetch, isFetching };
|
||||
}
|
||||
|
||||
export function useMutateProjectRundowns() {
|
||||
const ontimeQueryClient = useQueryClient();
|
||||
|
||||
const { mutateAsync: create } = useMutation({
|
||||
mutationFn: createRundown,
|
||||
onMutate: () => {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
VIEW_SETTINGS,
|
||||
getRundownQueryKey,
|
||||
PROJECT_RUNDOWNS,
|
||||
PROJECT_LIST,
|
||||
} from '../api/constants';
|
||||
import { invalidateAllCaches } from '../api/utils';
|
||||
import { ontimeQueryClient } from '../queryClient';
|
||||
@@ -216,6 +217,9 @@ export const connectSocket = () => {
|
||||
case RefetchKey.ProjectRundowns:
|
||||
ontimeQueryClient.invalidateQueries({ queryKey: PROJECT_RUNDOWNS });
|
||||
break;
|
||||
case RefetchKey.ProjectFiles:
|
||||
ontimeQueryClient.invalidateQueries({ queryKey: PROJECT_LIST });
|
||||
break;
|
||||
default: {
|
||||
target satisfies never;
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { useState } from 'react';
|
||||
import { Suspense, useState } from 'react';
|
||||
import {
|
||||
IoAdd,
|
||||
IoDocumentOutline,
|
||||
@@ -28,6 +28,20 @@ import { ManageRundownForm } from './ManageRundownForm';
|
||||
import style from './ManagePanel.module.scss';
|
||||
|
||||
export default function ManageRundowns() {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className={style.empty}>
|
||||
<Panel.Loader isLoading />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ManageRundownsSuspense />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function ManageRundownsSuspense() {
|
||||
const { data } = useProjectRundowns();
|
||||
const { duplicate, remove, load, rename } = useMutateProjectRundowns();
|
||||
const [isOpenDelete, deleteHandlers] = useDisclosure();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { Suspense, useState } from 'react';
|
||||
import { IoArrowDown, IoArrowUp } from 'react-icons/io5';
|
||||
|
||||
import Info from '../../../../common/components/info/Info';
|
||||
@@ -11,11 +11,25 @@ import style from './ProjectPanel.module.scss';
|
||||
type SortParameter = 'alphabetical' | 'modified';
|
||||
|
||||
export default function ProjectList() {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className={style.empty}>
|
||||
<Panel.Loader isLoading />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ProjectListSuspend />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectListSuspend() {
|
||||
const [editingMode, setEditingMode] = useState<EditMode | null>(null);
|
||||
const [editingFilename, setEditingFilename] = useState<string | null>(null);
|
||||
const [sortMode, setSortMode] = useState<ProjectSortMode>('modified-desc');
|
||||
|
||||
const { data, refetch, status } = useOrderedProjectList(sortMode);
|
||||
const { data, refetch } = useOrderedProjectList(sortMode);
|
||||
|
||||
const handleToggleEditMode = (editMode: EditMode, filename: string | null) => {
|
||||
setEditingMode((prev) => (prev === editMode && filename === editingFilename ? null : editMode));
|
||||
@@ -38,14 +52,6 @@ export default function ProjectList() {
|
||||
});
|
||||
};
|
||||
|
||||
if (status === 'pending') {
|
||||
return (
|
||||
<div className={style.empty}>
|
||||
<Panel.Loader isLoading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const numProjects = data.reorderedProjectFiles.length;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ErrorBoundary } from '@sentry/react';
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
import { PropsWithChildren, ReactNode, Suspense } from 'react';
|
||||
|
||||
import ScrollArea from '../../common/components/scroll-area/ScrollArea';
|
||||
import { useIsOnline } from '../../common/hooks/useSocket';
|
||||
import { cx } from '../../common/utils/styleUtils';
|
||||
import Loader from '../../views/common/loader/Loader';
|
||||
|
||||
import style from './Overview.module.scss';
|
||||
|
||||
@@ -15,18 +16,37 @@ export function OverviewWrapper({ navElements, children }: PropsWithChildren<Ove
|
||||
const isOnline = useIsOnline();
|
||||
|
||||
return (
|
||||
<div className={cx([style.overview, !isOnline && style.isOffline])}>
|
||||
<ErrorBoundary>
|
||||
<div className={style.nav}>{navElements}</div>
|
||||
<ScrollArea
|
||||
className={style.infoScroll}
|
||||
contentClassName={style.info}
|
||||
contentStyle={{ minWidth: '100%' }}
|
||||
orientation='horizontal'
|
||||
>
|
||||
{children}
|
||||
</ScrollArea>
|
||||
</ErrorBoundary>
|
||||
<Suspense fallback={<OverviewFallback navElements={navElements} />}>
|
||||
<div className={cx([style.overview, !isOnline && style.isOffline])}>
|
||||
<ErrorBoundary>
|
||||
<div className={style.nav}>{navElements}</div>
|
||||
<ScrollArea
|
||||
className={style.infoScroll}
|
||||
contentClassName={style.info}
|
||||
contentStyle={{ minWidth: '100%' }}
|
||||
orientation='horizontal'
|
||||
>
|
||||
{children}
|
||||
</ScrollArea>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewFallback({ navElements }: OverviewWrapperProps) {
|
||||
return (
|
||||
<div className={style.overview}>
|
||||
<div className={style.nav}>{navElements}</div>
|
||||
<ScrollArea
|
||||
className={style.infoScroll}
|
||||
contentClassName={style.info}
|
||||
contentStyle={{ minWidth: '100%' }}
|
||||
orientation='horizontal'
|
||||
>
|
||||
{/* TODO: this could be alined in a nicer way */}
|
||||
<Loader />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { copyFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
import { DatabaseModel, LogOrigin, ProjectFileListResponse } from 'ontime-types';
|
||||
import { DatabaseModel, LogOrigin, ProjectFileListResponse, RefetchKey } from 'ontime-types';
|
||||
import { getErrorMessage, getFirstRundown } from 'ontime-utils';
|
||||
|
||||
import { sendRefetch } from '../../adapters/WebsocketAdapter.js';
|
||||
import { parseCustomFields } from '../../api-data/custom-fields/customFields.parser.js';
|
||||
import { parseDatabaseModel } from '../../api-data/db/db.parser.js';
|
||||
import { getCurrentRundown } from '../../api-data/rundown/rundown.dao.js';
|
||||
@@ -105,6 +106,9 @@ async function loadProject(projectData: DatabaseModel, fileName: string, rundown
|
||||
currentProjectName: fileName,
|
||||
};
|
||||
|
||||
setImmediate(() => {
|
||||
sendRefetch(RefetchKey.ProjectFiles);
|
||||
});
|
||||
return fileName;
|
||||
}
|
||||
|
||||
@@ -263,6 +267,10 @@ export async function duplicateProjectFile(originalFile: string, newFilename: st
|
||||
|
||||
const pathToDuplicate = getPathToProject(newFilename);
|
||||
await copyFile(projectFilePath, pathToDuplicate);
|
||||
|
||||
setImmediate(() => {
|
||||
sendRefetch(RefetchKey.ProjectFiles);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -293,6 +301,10 @@ export async function renameProjectFile(originalFile: string, newFilename: strin
|
||||
const newFileName = await loadProject(projectData.data, newFilename);
|
||||
return newFileName;
|
||||
}
|
||||
|
||||
setImmediate(() => {
|
||||
sendRefetch(RefetchKey.ProjectFiles);
|
||||
});
|
||||
return newFilename;
|
||||
}
|
||||
|
||||
@@ -332,6 +344,9 @@ export async function deleteProjectFile(filename: string) {
|
||||
}
|
||||
|
||||
await deleteFile(projectFilePath);
|
||||
setImmediate(() => {
|
||||
sendRefetch(RefetchKey.ProjectFiles);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,6 +389,7 @@ export async function patchCurrentProject(data: Partial<DatabaseModel>) {
|
||||
}
|
||||
}
|
||||
|
||||
const updatedData = await getDataProvider().getData();
|
||||
const updatedData = getDataProvider().getData();
|
||||
|
||||
return updatedData;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export enum RefetchKey {
|
||||
All = 'all',
|
||||
CustomFields = 'custom-fields',
|
||||
ProjectFiles = 'project-files',
|
||||
ProjectData = 'project-data',
|
||||
ProjectRundowns = 'project-rundowns',
|
||||
Report = 'report',
|
||||
|
||||
Reference in New Issue
Block a user