mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 06:04:05 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fbbbaa5922 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getontime/cli",
|
||||
"version": "4.4.0",
|
||||
"version": "4.3.1",
|
||||
"author": "Carlos Valente",
|
||||
"description": "Time keeping for live events",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime-ui",
|
||||
"version": "4.4.0",
|
||||
"version": "4.3.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -25,12 +25,15 @@ export default function useReactiveTextInput(
|
||||
const isKeyboardSubmitting = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const isFocused = document.activeElement === ref.current;
|
||||
if (isFocused) return;
|
||||
|
||||
if (typeof initialText === 'undefined') {
|
||||
setText('');
|
||||
} else {
|
||||
setText(initialText);
|
||||
}
|
||||
}, [initialText]);
|
||||
}, [initialText, ref]);
|
||||
|
||||
/**
|
||||
* @description Handles Input value change
|
||||
|
||||
@@ -11,7 +11,7 @@ import IconButton from '../buttons/IconButton';
|
||||
import Info from '../info/Info';
|
||||
|
||||
import { ViewOption } from './viewParams.types';
|
||||
import { getPreservedSearchParams, getURLSearchParamsFromObj } from './viewParams.utils';
|
||||
import { getURLSearchParamsFromObj } from './viewParams.utils';
|
||||
import { useViewParamsEditorStore } from './viewParamsEditor.store';
|
||||
import { ViewParamsPresets } from './ViewParamsPresets';
|
||||
import ViewParamsSection from './ViewParamsSection';
|
||||
@@ -25,19 +25,17 @@ interface EditFormDrawerProps {
|
||||
|
||||
export default memo(ViewParamsEditor);
|
||||
function ViewParamsEditor({ target, viewOptions }: EditFormDrawerProps) {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [_, setSearchParams] = useSearchParams();
|
||||
const { data: viewSettings } = useViewSettings();
|
||||
const { isOpen, close } = useViewParamsEditorStore();
|
||||
const isSmallScreen = useIsSmallScreen();
|
||||
|
||||
const getPreservedParams = () => getPreservedSearchParams(searchParams, viewOptions);
|
||||
|
||||
const handleClose = () => {
|
||||
close();
|
||||
};
|
||||
|
||||
const resetParams = () => {
|
||||
setSearchParams(getPreservedParams());
|
||||
setSearchParams();
|
||||
};
|
||||
|
||||
const onParamsFormSubmit = (formEvent: FormEvent<HTMLFormElement>) => {
|
||||
@@ -45,10 +43,6 @@ function ViewParamsEditor({ target, viewOptions }: EditFormDrawerProps) {
|
||||
|
||||
const newParamsObject = Object.fromEntries(new FormData(formEvent.currentTarget));
|
||||
const newSearchParams = getURLSearchParamsFromObj(newParamsObject, viewOptions);
|
||||
const preservedParams = getPreservedParams();
|
||||
preservedParams.forEach((value, key) => {
|
||||
newSearchParams.append(key, value);
|
||||
});
|
||||
setSearchParams(newSearchParams);
|
||||
|
||||
if (isSmallScreen) {
|
||||
|
||||
@@ -191,26 +191,3 @@ export function getURLSearchParamsFromObj(paramsObj: ViewParamsObj, paramFields:
|
||||
|
||||
return newSearchParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts search params that are not managed by the view params editor
|
||||
* @param currentParams - The current URL search params
|
||||
* @param paramFields - The view options that define the managed parameters
|
||||
* @returns A new URLSearchParams object with preserved params
|
||||
*/
|
||||
export function getPreservedSearchParams(currentParams: URLSearchParams, paramFields: ViewOption[]) {
|
||||
const managedParamIds = new Set<string>();
|
||||
paramFields.forEach((section) => {
|
||||
section.options.forEach((option) => {
|
||||
managedParamIds.add(option.id);
|
||||
});
|
||||
});
|
||||
|
||||
const preserved = new URLSearchParams();
|
||||
currentParams.forEach((value, key) => {
|
||||
if (!managedParamIds.has(key)) {
|
||||
preserved.append(key, value);
|
||||
}
|
||||
});
|
||||
return preserved;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { ComponentProps, memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import {
|
||||
ContextProp,
|
||||
ItemProps,
|
||||
TableComponents,
|
||||
TableProps,
|
||||
TableVirtuoso,
|
||||
TableVirtuosoHandle,
|
||||
} from 'react-virtuoso';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { TableVirtuoso, TableVirtuosoHandle } from 'react-virtuoso';
|
||||
import { useTableNav } from '@table-nav/react';
|
||||
import { ColumnDef, getCoreRowModel, Table, useReactTable } from '@tanstack/react-table';
|
||||
import { isOntimeDelay, isOntimeGroup, isOntimeMilestone, OntimeEntry, TimeField } from 'ontime-types';
|
||||
import { ColumnDef, getCoreRowModel, Row, Table, useReactTable } from '@tanstack/react-table';
|
||||
import { EntryId, isOntimeDelay, isOntimeGroup, isOntimeMilestone, OntimeEntry, TimeField } from 'ontime-types';
|
||||
|
||||
import EmptyPage from '../../../common/components/state/EmptyPage';
|
||||
import EmptyTableBody from '../../../common/components/state/EmptyTableBody';
|
||||
@@ -34,6 +27,115 @@ import { useColumnOrder, useColumnSizes, useColumnVisibility } from './useColumn
|
||||
|
||||
import style from './CuesheetTable.module.scss';
|
||||
|
||||
interface VirtuosoContext {
|
||||
table: Table<ExtendedEntry>;
|
||||
rows: Row<ExtendedEntry>[];
|
||||
cursor: EntryId | null;
|
||||
columnSizeVars: Record<string, number | string>;
|
||||
listeners: object;
|
||||
}
|
||||
|
||||
const VirtuosoTable = memo(({ style: injectedStyles, context, ...virtuosoProps }: any) => {
|
||||
const { columnSizeVars, listeners } = context as VirtuosoContext;
|
||||
return (
|
||||
<table
|
||||
className={style.cuesheet}
|
||||
id="cuesheet"
|
||||
style={{ ...injectedStyles, ...columnSizeVars }}
|
||||
{...listeners}
|
||||
{...virtuosoProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
VirtuosoTable.displayName = 'VirtuosoTable';
|
||||
|
||||
const VirtuosoTableRow = memo(({ item: _item, context, ...virtuosoProps }: any) => {
|
||||
const { table, rows, cursor } = context as VirtuosoContext;
|
||||
const rowIndex = virtuosoProps['data-index'];
|
||||
const row = rows[rowIndex];
|
||||
if (!row) return null;
|
||||
|
||||
const key = row.original.id;
|
||||
const entry = row.original;
|
||||
const hasCursor = entry.id === cursor;
|
||||
|
||||
if (isOntimeGroup(entry)) {
|
||||
return (
|
||||
<GroupRow
|
||||
key={key}
|
||||
groupId={entry.id}
|
||||
colour={entry.colour}
|
||||
rowId={row.id}
|
||||
rowIndex={row.index}
|
||||
table={table}
|
||||
injectedStyles={virtuosoProps.style}
|
||||
hasCursor={hasCursor}
|
||||
{...virtuosoProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isOntimeDelay(entry)) {
|
||||
return (
|
||||
<DelayRow
|
||||
key={key}
|
||||
duration={entry.duration}
|
||||
injectedStyles={virtuosoProps.style}
|
||||
hasCursor={hasCursor}
|
||||
{...virtuosoProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isOntimeMilestone(entry)) {
|
||||
return (
|
||||
<MilestoneRow
|
||||
key={key}
|
||||
entryId={entry.id}
|
||||
isPast={entry.isPast}
|
||||
parentBgColour={entry.groupColour}
|
||||
parentId={entry.parent}
|
||||
colour={entry.colour}
|
||||
rowId={row.id}
|
||||
rowIndex={rowIndex}
|
||||
table={table}
|
||||
injectedStyles={virtuosoProps.style}
|
||||
hasCursor={hasCursor}
|
||||
{...virtuosoProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EventRow
|
||||
key={row.id}
|
||||
id={entry.id}
|
||||
eventIndex={entry.eventIndex}
|
||||
colour={entry.colour}
|
||||
isFirstAfterGroup={entry.isFirstAfterGroup}
|
||||
isLoaded={entry.isLoaded}
|
||||
isPast={entry.isPast}
|
||||
groupColour={entry.groupColour}
|
||||
flag={entry.flag}
|
||||
skip={entry.skip}
|
||||
parent={entry.parent}
|
||||
rowId={row.id}
|
||||
rowIndex={rowIndex}
|
||||
table={table}
|
||||
injectedStyles={virtuosoProps.style}
|
||||
hasCursor={hasCursor}
|
||||
{...virtuosoProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
VirtuosoTableRow.displayName = 'VirtuosoTableRow';
|
||||
|
||||
const VirtuosoTableHead = memo((props: any) => <thead className={style.tableHeader} {...props} />);
|
||||
VirtuosoTableHead.displayName = 'VirtuosoTableHead';
|
||||
|
||||
const VirtuosoEmptyPlaceholder = memo(() => <EmptyTableBody text="No data in rundown" />);
|
||||
VirtuosoEmptyPlaceholder.displayName = 'VirtuosoEmptyPlaceholder';
|
||||
|
||||
interface CuesheetTableProps {
|
||||
columns: ColumnDef<ExtendedEntry>[];
|
||||
cuesheetMode: AppMode;
|
||||
@@ -56,11 +158,14 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
||||
const virtuosoRef = useRef<TableVirtuosoHandle | null>(null);
|
||||
const { listeners } = useTableNav();
|
||||
|
||||
const dataRef = useRef(data);
|
||||
dataRef.current = data;
|
||||
|
||||
const meta = useMemo(
|
||||
() => ({
|
||||
handleUpdate: (rowIndex: number, accessor: string, payload: string, isCustom = false) => {
|
||||
// check if value is the same
|
||||
const event = data[rowIndex];
|
||||
const event = dataRef.current[rowIndex];
|
||||
|
||||
if (!event) {
|
||||
return;
|
||||
@@ -91,7 +196,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
||||
hideIndexColumn,
|
||||
},
|
||||
}),
|
||||
[cuesheetMode, data, hideIndexColumn, hideTableSeconds, showDelayedTimes, updateEntry, updateTimer],
|
||||
[cuesheetMode, hideIndexColumn, hideTableSeconds, showDelayedTimes, updateEntry, updateTimer],
|
||||
);
|
||||
|
||||
const { columnOrder, resetColumnOrder } = useColumnOrder(columns, tableRoot);
|
||||
@@ -177,29 +282,18 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
||||
|
||||
const allLeafColumns = table.getAllLeafColumns();
|
||||
const { rows } = table.getRowModel();
|
||||
|
||||
const virtuosoContext = useMemo(
|
||||
() => ({
|
||||
columnSizeVars,
|
||||
cursor,
|
||||
listeners,
|
||||
rows,
|
||||
table,
|
||||
rows,
|
||||
cursor,
|
||||
columnSizeVars,
|
||||
listeners,
|
||||
}),
|
||||
[columnSizeVars, cursor, listeners, rows, table],
|
||||
);
|
||||
|
||||
const computeItemKey = useCallback((_: number, item: ExtendedEntry) => item.id, []);
|
||||
const fixedHeaderContent = useCallback(() => {
|
||||
return table.getHeaderGroups().map((headerGroup) => {
|
||||
const HeaderComponent = table.getState().columnSizingInfo.isResizingColumn
|
||||
? CuesheetHeader
|
||||
: SortableCuesheetHeader;
|
||||
|
||||
// if the table is being resized, we render non-sortable headers to avoid performance issues
|
||||
return <HeaderComponent key={headerGroup.id} cuesheetMode={cuesheetMode} headerGroup={headerGroup} />;
|
||||
});
|
||||
}, [cuesheetMode, table]);
|
||||
|
||||
const isLoading = !data || status === 'pending';
|
||||
|
||||
if (isLoading) {
|
||||
@@ -222,144 +316,26 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
||||
data={data}
|
||||
context={virtuosoContext}
|
||||
style={tableRoot === 'editor' ? { paddingLeft: '1rem' } : undefined}
|
||||
computeItemKey={computeItemKey}
|
||||
increaseViewportBy={{ top: 100, bottom: 200 }}
|
||||
components={virtuosoComponents}
|
||||
fixedHeaderContent={fixedHeaderContent}
|
||||
components={{
|
||||
EmptyPlaceholder: VirtuosoEmptyPlaceholder,
|
||||
Table: VirtuosoTable,
|
||||
TableRow: VirtuosoTableRow,
|
||||
TableHead: VirtuosoTableHead,
|
||||
}}
|
||||
fixedHeaderContent={() => {
|
||||
return table.getHeaderGroups().map((headerGroup) => {
|
||||
const HeaderComponent = table.getState().columnSizingInfo.isResizingColumn
|
||||
? CuesheetHeader
|
||||
: SortableCuesheetHeader;
|
||||
|
||||
// if the table is being resized, we render non-sortable headers to avoid performance issues
|
||||
return <HeaderComponent key={headerGroup.id} cuesheetMode={cuesheetMode} headerGroup={headerGroup} />;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<TableMenu />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
interface CuesheetVirtuosoContext {
|
||||
columnSizeVars: { [key: string]: number };
|
||||
cursor: string | null;
|
||||
listeners: ReturnType<typeof useTableNav>['listeners'];
|
||||
rows: ReturnType<Table<ExtendedEntry>['getRowModel']>['rows'];
|
||||
table: Table<ExtendedEntry>;
|
||||
}
|
||||
|
||||
const EmptyPlaceholder = memo(function EmptyPlaceholder() {
|
||||
return <EmptyTableBody text='No data in rundown' />;
|
||||
});
|
||||
|
||||
const CuesheetTableElement = memo(function CuesheetTableElement({
|
||||
style: injectedStyles,
|
||||
context,
|
||||
...virtuosoProps
|
||||
}: TableProps & ContextProp<CuesheetVirtuosoContext>) {
|
||||
return (
|
||||
<table
|
||||
className={style.cuesheet}
|
||||
id='cuesheet'
|
||||
style={{ ...injectedStyles, ...context.columnSizeVars }}
|
||||
{...context.listeners}
|
||||
{...virtuosoProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const CuesheetTableHead = memo(function CuesheetTableHead({
|
||||
context: _context,
|
||||
className: _className,
|
||||
...virtuosoProps
|
||||
}: ComponentProps<'thead'> & ContextProp<CuesheetVirtuosoContext>) {
|
||||
return <thead className={style.tableHeader} {...virtuosoProps} />;
|
||||
});
|
||||
|
||||
const CuesheetTableRow = memo(function CuesheetTableRow({
|
||||
item: _item,
|
||||
style: injectedStyles,
|
||||
context,
|
||||
...virtuosoProps
|
||||
}: ItemProps<ExtendedEntry> & ContextProp<CuesheetVirtuosoContext>) {
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
const rowIndex = virtuosoProps['data-index'];
|
||||
const row = context.rows[rowIndex];
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = row.original.id;
|
||||
const entry = row.original;
|
||||
const hasCursor = entry.id === context.cursor;
|
||||
|
||||
if (isOntimeGroup(entry)) {
|
||||
return (
|
||||
<GroupRow
|
||||
key={key}
|
||||
groupId={entry.id}
|
||||
colour={entry.colour}
|
||||
rowId={row.id}
|
||||
rowIndex={row.index}
|
||||
table={context.table}
|
||||
injectedStyles={injectedStyles}
|
||||
hasCursor={hasCursor}
|
||||
{...virtuosoProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isOntimeDelay(entry)) {
|
||||
return (
|
||||
<DelayRow
|
||||
key={key}
|
||||
duration={entry.duration}
|
||||
injectedStyles={injectedStyles}
|
||||
hasCursor={hasCursor}
|
||||
{...virtuosoProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isOntimeMilestone(entry)) {
|
||||
return (
|
||||
<MilestoneRow
|
||||
key={key}
|
||||
entryId={entry.id}
|
||||
isPast={entry.isPast}
|
||||
parentBgColour={entry.groupColour}
|
||||
parentId={entry.parent}
|
||||
colour={entry.colour}
|
||||
rowId={row.id}
|
||||
rowIndex={rowIndex}
|
||||
table={context.table}
|
||||
injectedStyles={injectedStyles}
|
||||
hasCursor={hasCursor}
|
||||
{...virtuosoProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EventRow
|
||||
key={row.id}
|
||||
id={entry.id}
|
||||
eventIndex={entry.eventIndex}
|
||||
colour={entry.colour}
|
||||
isFirstAfterGroup={entry.isFirstAfterGroup}
|
||||
isLoaded={entry.isLoaded}
|
||||
isPast={entry.isPast}
|
||||
groupColour={entry.groupColour}
|
||||
flag={entry.flag}
|
||||
skip={entry.skip}
|
||||
parent={entry.parent}
|
||||
rowId={row.id}
|
||||
rowIndex={rowIndex}
|
||||
table={context.table}
|
||||
injectedStyles={injectedStyles}
|
||||
hasCursor={hasCursor}
|
||||
{...virtuosoProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const virtuosoComponents: TableComponents<ExtendedEntry, CuesheetVirtuosoContext> = {
|
||||
EmptyPlaceholder,
|
||||
Table: CuesheetTableElement,
|
||||
TableHead: CuesheetTableHead,
|
||||
TableRow: CuesheetTableRow,
|
||||
};
|
||||
|
||||
@@ -91,7 +91,6 @@ export default function EventRow({
|
||||
}}
|
||||
data-cursor={hasCursor}
|
||||
data-testid='cuesheet-event'
|
||||
data-entry-id={id}
|
||||
{...virtuosoProps}
|
||||
>
|
||||
{cuesheetMode === AppMode.Edit && (
|
||||
@@ -128,8 +127,6 @@ export default function EventRow({
|
||||
}}
|
||||
tabIndex={-1}
|
||||
role='cell'
|
||||
data-testid={`cuesheet-cell-${cell.column.id}`}
|
||||
data-column-id={cell.column.id}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
|
||||
+1
-5
@@ -5,14 +5,12 @@ import useReactiveTextInput from '../../../../common/components/input/text-input
|
||||
|
||||
interface MultiLineCellProps {
|
||||
initialValue: string;
|
||||
fieldId?: string;
|
||||
fieldLabel?: string;
|
||||
handleUpdate: (newValue: string) => void;
|
||||
}
|
||||
|
||||
export default memo(MultiLineCell);
|
||||
|
||||
function MultiLineCell({ initialValue, fieldId, fieldLabel, handleUpdate }: MultiLineCellProps) {
|
||||
function MultiLineCell({ initialValue, handleUpdate }: MultiLineCellProps) {
|
||||
const ref = useRef<HTMLTextAreaElement | null>(null);
|
||||
const submitCallback = useCallback((newValue: string) => handleUpdate(newValue), [handleUpdate]);
|
||||
|
||||
@@ -32,8 +30,6 @@ function MultiLineCell({ initialValue, fieldId, fieldLabel, handleUpdate }: Mult
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
spellCheck={false}
|
||||
data-testid={fieldId ? `cuesheet-editor-${fieldId}` : undefined}
|
||||
aria-label={fieldLabel ? `${fieldLabel} editor` : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+1
-8
@@ -5,18 +5,13 @@ import useReactiveTextInput from '../../../../common/components/input/text-input
|
||||
|
||||
interface SingleLineCellProps {
|
||||
initialValue: string;
|
||||
fieldId?: string;
|
||||
fieldLabel?: string;
|
||||
allowSubmitSameValue?: boolean;
|
||||
handleUpdate: (newValue: string) => void;
|
||||
handleCancelUpdate?: () => void;
|
||||
}
|
||||
|
||||
const SingleLineCell = forwardRef(
|
||||
(
|
||||
{ initialValue, fieldId, fieldLabel, allowSubmitSameValue, handleUpdate, handleCancelUpdate }: SingleLineCellProps,
|
||||
inputRef,
|
||||
) => {
|
||||
({ initialValue, allowSubmitSameValue, handleUpdate, handleCancelUpdate }: SingleLineCellProps, inputRef) => {
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
const submitCallback = useCallback((newValue: string) => handleUpdate(newValue), [handleUpdate]);
|
||||
|
||||
@@ -52,8 +47,6 @@ const SingleLineCell = forwardRef(
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
data-testid={fieldId ? `cuesheet-editor-${fieldId}` : undefined}
|
||||
aria-label={fieldLabel ? `${fieldLabel} editor` : undefined}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
+3
-28
@@ -17,10 +17,6 @@ import MutedText from './MutedText';
|
||||
import SingleLineCell from './SingleLineCell';
|
||||
import TimeInput from './TimeInput';
|
||||
|
||||
function getColumnLabel(column: CellContext<ExtendedEntry, unknown>['column']): string {
|
||||
return typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id;
|
||||
}
|
||||
|
||||
function MakeStart({ getValue, row, table, column }: CellContext<ExtendedEntry, unknown>) {
|
||||
if (!table.options.meta) {
|
||||
return null;
|
||||
@@ -150,14 +146,7 @@ function MakeMultiLineField({ row, column, table }: CellContext<ExtendedEntry, u
|
||||
return <GhostedText multiline>{initialValue}</GhostedText>;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultiLineCell
|
||||
initialValue={initialValue as string}
|
||||
fieldId={column.id}
|
||||
fieldLabel={getColumnLabel(column)}
|
||||
handleUpdate={update}
|
||||
/>
|
||||
);
|
||||
return <MultiLineCell initialValue={initialValue as string} handleUpdate={update} />;
|
||||
}
|
||||
|
||||
function LazyImage({ row, column, table }: CellContext<ExtendedEntry, unknown>) {
|
||||
@@ -197,14 +186,7 @@ function MakeSingleLineField({ row, column, table }: CellContext<ExtendedEntry,
|
||||
return <GhostedText>{initialValue}</GhostedText>;
|
||||
}
|
||||
|
||||
return (
|
||||
<SingleLineCell
|
||||
initialValue={initialValue as string}
|
||||
fieldId={column.id}
|
||||
fieldLabel={getColumnLabel(column)}
|
||||
handleUpdate={update}
|
||||
/>
|
||||
);
|
||||
return <SingleLineCell initialValue={initialValue as string} handleUpdate={update} />;
|
||||
}
|
||||
|
||||
function MakeFlagField({ row }: CellContext<ExtendedEntry, unknown>) {
|
||||
@@ -237,14 +219,7 @@ function MakeCustomField({ row, column, table }: CellContext<ExtendedEntry, unkn
|
||||
return <GhostedText multiline>{initialValue}</GhostedText>;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultiLineCell
|
||||
initialValue={initialValue}
|
||||
fieldId={column.id}
|
||||
fieldLabel={getColumnLabel(column)}
|
||||
handleUpdate={update}
|
||||
/>
|
||||
);
|
||||
return <MultiLineCell initialValue={initialValue} handleUpdate={update} />;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime-electron",
|
||||
"version": "4.4.0",
|
||||
"version": "4.3.1",
|
||||
"author": "Carlos Valente",
|
||||
"description": "Time keeping for live events",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getontime/resolver",
|
||||
"version": "4.4.0",
|
||||
"version": "4.3.1",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
"types": "./dist/main.d.ts",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "ontime-server",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"version": "4.4.0",
|
||||
"version": "4.3.1",
|
||||
"exports": "./src/index.js",
|
||||
"dependencies": {
|
||||
"@googleapis/sheets": "^5.0.5",
|
||||
|
||||
@@ -499,7 +499,7 @@ export async function editCustomField(
|
||||
if (rundownId !== rundown.id) {
|
||||
const backgroundRundown = structuredClone(projectRundowns[rundownId]);
|
||||
customFieldMutation.renameUsages(backgroundRundown, oldKey, newKey);
|
||||
updateBackgroundRundown(rundownId, backgroundRundown);
|
||||
updateBackgroundRundown(rundown.id, backgroundRundown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,7 +539,7 @@ export async function deleteCustomField(key: CustomFieldKey, projectRundowns: Pr
|
||||
if (rundownId !== rundown.id) {
|
||||
const backgroundRundown = structuredClone(projectRundowns[rundownId]);
|
||||
customFieldMutation.removeUsages(backgroundRundown, key);
|
||||
updateBackgroundRundown(rundownId, backgroundRundown);
|
||||
updateBackgroundRundown(rundown.id, backgroundRundown);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
> ontime@4.3.1 dev /app
|
||||
> turbo run dev
|
||||
|
||||
|
||||
Attention:
|
||||
Turborepo now collects completely anonymous telemetry regarding usage.
|
||||
This information is used to shape the Turborepo roadmap and prioritize features.
|
||||
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
|
||||
https://turborepo.com/docs/telemetry
|
||||
|
||||
turbo 2.5.8
|
||||
|
||||
• Packages in scope: @getontime/cli, @getontime/resolver, ontime-electron, ontime-server, ontime-types, ontime-ui, ontime-utils
|
||||
• Running dev in 7 packages
|
||||
• Remote caching disabled
|
||||
ontime-server:dev: cache bypass, force executing 736e67d68a6b5681
|
||||
ontime-ui:dev: cache bypass, force executing 9eacdb30152cd80e
|
||||
ontime-ui:dev:
|
||||
ontime-ui:dev: > ontime-ui@4.3.1 dev /app/apps/client
|
||||
ontime-ui:dev: > cross-env BROWSER=none vite
|
||||
ontime-ui:dev:
|
||||
ontime-server:dev:
|
||||
ontime-server:dev: > ontime-server@4.3.1 dev /app/apps/server
|
||||
ontime-server:dev: > cross-env NODE_ENV=development tsx watch ./src/index.ts
|
||||
ontime-server:dev:
|
||||
ontime-ui:dev:
|
||||
ontime-ui:dev: VITE v6.3.1 ready in 748 ms
|
||||
ontime-ui:dev:
|
||||
ontime-ui:dev: ➜ Local: http://localhost:3000/
|
||||
ontime-ui:dev: ➜ Network: use --host to expose
|
||||
ontime-server:dev:
|
||||
ontime-server:dev:
|
||||
ontime-server:dev: [96mStarting Ontime version 4.3.1[0m
|
||||
ontime-server:dev: Ontime running in development environment
|
||||
ontime-server:dev: Ontime source directory at /app/apps/server/src/
|
||||
ontime-server:dev: Ontime public directory at /home/jules/.Ontime
|
||||
ontime-server:dev:
|
||||
ontime-server:dev:
|
||||
ontime-server:dev: [96mRequest: Initialise assets...[0m
|
||||
ontime-server:dev: [2m[INFO] SERVER Loaded project demo project.json[0m
|
||||
ontime-server:dev: [2m[INFO] SERVER Switch to rundown: default[0m
|
||||
ontime-server:dev: [2m[INFO] SERVER Initialised Ontime with demo project.json[0m
|
||||
ontime-server:dev:
|
||||
ontime-server:dev:
|
||||
ontime-server:dev: [96mRequest: Start server...[0m
|
||||
ontime-server:dev: [2m[INFO] SERVER Runtime service started[0m
|
||||
ontime-server:dev: [32mLocal: http://localhost:4001/editor[0m
|
||||
ontime-server:dev: [32mNetwork: http://192.168.0.2:4001/editor[0m
|
||||
ontime-server:dev: [2m[INFO] SERVER Ontime is listening on port 4001[0m
|
||||
ontime-server:dev:
|
||||
ontime-server:dev:
|
||||
ontime-server:dev: [96mRequest: Start integrations...[0m
|
||||
ontime-server:dev: [2m[INFO] SERVER Skipping OSC integration[0m
|
||||
ontime-ui:dev: Browserslist: browsers data (caniuse-lite) is 9 months old. Please run:
|
||||
ontime-ui:dev: npx update-browserslist-db@latest
|
||||
ontime-ui:dev: Why you should do it regularly: https://github.com/browserslist/update-db#readme
|
||||
ontime-ui:dev: Browserslist: caniuse-lite is outdated. Please run:
|
||||
ontime-ui:dev: npx update-browserslist-db@latest
|
||||
ontime-ui:dev: Why you should do it regularly: https://github.com/browserslist/update-db#readme
|
||||
ontime-server:dev: [2m[INFO] CLIENT 1 Connections with new: low-fi instrument[0m
|
||||
ontime-ui:dev: Proxy: GET /data/settings
|
||||
ontime-ui:dev: Proxy: GET /user/translations/translations.json
|
||||
ontime-ui:dev: Proxy: GET /data/report
|
||||
ontime-ui:dev: Proxy: GET /data/view-settings
|
||||
ontime-ui:dev: Proxy: GET /data/rundowns/current
|
||||
ontime-ui:dev: Proxy: GET /data/project
|
||||
ontime-ui:dev: Proxy: GET /data/db/all
|
||||
ontime-ui:dev: Proxy: GET /data/rundowns/current
|
||||
ontime-ui:dev: Proxy: GET /data/project
|
||||
ontime-ui:dev: Proxy: GET /data/db/all
|
||||
ontime-server:dev: [2m[INFO] CLIENT 0 Connections with disconnected: low-fi instrument[0m
|
||||
ontime-server:dev: [2m[INFO] CLIENT 1 Connections with new: mellow uplight[0m
|
||||
ontime-ui:dev: Proxy: GET /data/settings
|
||||
ontime-ui:dev: Proxy: GET /user/translations/translations.json
|
||||
ontime-ui:dev: Proxy: GET /data/report
|
||||
ontime-ui:dev: Proxy: GET /data/view-settings
|
||||
ontime-ui:dev: Proxy: GET /data/rundowns/current
|
||||
ontime-ui:dev: Proxy: GET /data/project
|
||||
ontime-ui:dev: Proxy: GET /data/rundowns/current
|
||||
ontime-ui:dev: Proxy: GET /data/project
|
||||
ontime-server:dev: [2m[INFO] CLIENT 0 Connections with disconnected: mellow uplight[0m
|
||||
ontime-server:dev: [2m[INFO] CLIENT 1 Connections with new: nostalgic mix[0m
|
||||
ontime-ui:dev: Proxy: GET /data/settings
|
||||
ontime-ui:dev: Proxy: GET /user/translations/translations.json
|
||||
ontime-ui:dev: Proxy: GET /data/report
|
||||
ontime-ui:dev: Proxy: GET /data/view-settings
|
||||
ontime-ui:dev: Proxy: GET /data/rundowns/current
|
||||
ontime-ui:dev: Proxy: GET /data/project
|
||||
ontime-ui:dev: Proxy: GET /data/rundowns/current
|
||||
ontime-ui:dev: Proxy: GET /data/project
|
||||
ontime-ui:dev: Proxy: GET /data/custom-fields
|
||||
ontime-ui:dev: Proxy: GET /data/rundowns/current
|
||||
ontime-server:dev: [2m[INFO] CLIENT 0 Connections with disconnected: nostalgic mix[0m
|
||||
@@ -1,57 +1,10 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test('cuesheet displays events', async ({ page }) => {
|
||||
await page.goto('/cuesheet');
|
||||
await expect(page.getByTestId('cuesheet')).toBeVisible();
|
||||
await expect(page.getByTestId('cuesheet-event').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('cuesheet datagrid keeps keyboard focus flow while editing text cells', async ({ page }) => {
|
||||
await page.goto('/cuesheet');
|
||||
|
||||
const firstEvent = page.getByTestId('cuesheet-event').first();
|
||||
await expect(firstEvent).toBeVisible();
|
||||
|
||||
const cueEditor = firstEvent.getByTestId('cuesheet-editor-cue');
|
||||
const titleEditor = firstEvent.getByTestId('cuesheet-editor-title');
|
||||
const noteEditor = firstEvent.getByTestId('cuesheet-editor-note');
|
||||
|
||||
/**
|
||||
* 1. focus a cell in the datagrid single line text
|
||||
* submitting the data returns the focus to the parent
|
||||
*/
|
||||
await titleEditor.click();
|
||||
await expect(titleEditor).toBeFocused();
|
||||
const updatedTitle = `focus-title-${Date.now()}`;
|
||||
await titleEditor.fill(updatedTitle);
|
||||
await titleEditor.press('Enter');
|
||||
await expect(titleEditor).not.toBeFocused();
|
||||
await expect(titleEditor).toHaveValue(updatedTitle);
|
||||
|
||||
/**
|
||||
* 2. navigate and modify multiline text cell
|
||||
* submitting works with ctrl/cmd + enter and the focus returns to the parent
|
||||
*/
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(noteEditor).toBeFocused();
|
||||
const updatedNote = `focus-note-${Date.now()}`;
|
||||
await noteEditor.fill(updatedNote);
|
||||
await noteEditor.press('ControlOrMeta+Enter');
|
||||
await expect(noteEditor).not.toBeFocused();
|
||||
await expect(noteEditor).toHaveValue(updatedNote);
|
||||
|
||||
/**
|
||||
* 2. navigate and modify single line text cell again
|
||||
* pressing escape cancels the edit and the focus returns to the parent
|
||||
*/
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(titleEditor).toBeFocused();
|
||||
const cueBeforeCancel = await cueEditor.inputValue();
|
||||
await cueEditor.click();
|
||||
await cueEditor.fill(`${cueBeforeCancel} temporary`);
|
||||
await cueEditor.press('Escape');
|
||||
await expect(cueEditor).not.toBeFocused();
|
||||
await expect(cueEditor).toHaveValue(cueBeforeCancel);
|
||||
// same elements in cuesheet
|
||||
await page.goto('http://localhost:4001/cuesheet');
|
||||
await expect(page.getByRole('row', { name: 'Lunch break' })).toBeVisible();
|
||||
await expect(page.getByRole('row', { name: 'Afternoon break' })).toBeVisible();
|
||||
|
||||
await expect(page.locator('#cuesheet')).toBeVisible();
|
||||
});
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime",
|
||||
"version": "4.4.0",
|
||||
"version": "4.3.1",
|
||||
"description": "Time keeping for live events",
|
||||
"keywords": [
|
||||
"ontime",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "4.4.0",
|
||||
"version": "4.3.1",
|
||||
"name": "ontime-types",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
|
||||
Reference in New Issue
Block a user