(null);
@@ -102,7 +100,6 @@ export default function ProjectListItem({
) : (
<>
| {filename} |
- {new Date(createdAt).toLocaleString()} |
{new Date(updatedAt).toLocaleString()} |
state.sheetId);
const setSheetId = useSheetStore((state) => state.setSheetId);
const setWorksheets = useSheetStore((state) => state.setWorksheets);
-
+ const patchStepData = useSheetStore((state) => state.patchStepData);
const authenticationStatus = useSheetStore((state) => state.authenticationStatus);
const setAuthenticationStatus = useSheetStore((state) => state.setAuthenticationStatus);
@@ -91,8 +92,13 @@ export default function GSheetSetup(props: GSheetSetupProps) {
setAuthenticationStatus(result.authenticated);
if (result.authenticated !== 'pending') {
if (result.authenticated == 'authenticated') {
- const names = await getWorksheetNames(result.sheetId);
- setWorksheets(names);
+ try {
+ const names = await getWorksheetNames(result.sheetId);
+ setWorksheets(names);
+ } catch (error) {
+ const message = maybeAxiosError(error);
+ patchStepData({ worksheet: { available: false, error: message } });
+ }
}
setLoading('');
return;
@@ -184,6 +190,7 @@ export default function GSheetSetup(props: GSheetSetupProps) {
) : (
+ {isAuthenticating && }
{authKey ? authKey : 'Upload files to generate Auth Key'}
@@ -192,8 +199,7 @@ export default function GSheetSetup(props: GSheetSetupProps) {
size='sm'
leftIcon={}
onClick={handleAuthenticate}
- isDisabled={!canAuthenticate || isLoading}
- isLoading={loading === 'authenticate' || isAuthenticating}
+ isDisabled={!canAuthenticate}
>
Authenticate
diff --git a/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.module.scss b/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.module.scss
index e6d01046c..004b06bcb 100644
--- a/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.module.scss
+++ b/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.module.scss
@@ -26,6 +26,7 @@
.buttonRow {
display: flex;
gap: 1rem;
+ align-items: center;
justify-content: end;
}
diff --git a/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx b/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx
index 7bb896df4..fb804052b 100644
--- a/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx
+++ b/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx
@@ -2,7 +2,7 @@ import { ChangeEvent, useRef, useState } from 'react';
import { Button, Input } from '@chakra-ui/react';
import { IoCloudOutline } from '@react-icons/all-files/io5/IoCloudOutline';
import { IoDownloadOutline } from '@react-icons/all-files/io5/IoDownloadOutline';
-import { ImportMap, unpackError } from 'ontime-utils';
+import { getErrorMessage, ImportMap } from 'ontime-utils';
import {
getWorksheetNames as getWorksheetNamesExcel,
@@ -59,7 +59,7 @@ export default function SourcesPanel() {
setImportFlow('excel');
setHasFile('done');
} catch (error) {
- const errorMessage = unpackError(error);
+ const errorMessage = getErrorMessage(error);
setError(`Error uploading file: ${errorMessage}`);
setWorksheets(null);
setHasFile('none');
diff --git a/apps/client/src/features/app-settings/settingsStore.ts b/apps/client/src/features/app-settings/settingsStore.ts
index 379f08407..3d4998a5a 100644
--- a/apps/client/src/features/app-settings/settingsStore.ts
+++ b/apps/client/src/features/app-settings/settingsStore.ts
@@ -15,7 +15,7 @@ export const settingPanels: Readonly = [
},
{
id: 'general',
- label: 'General',
+ label: 'App Settings',
secondary: [
{ id: 'general__manage', label: 'Manage Ontime settings' },
{ id: 'general__view', label: 'View settings' },
@@ -59,6 +59,7 @@ export const settingPanels: Readonly = [
] as const;
export type SettingsOptionId = (typeof settingPanels)[number]['id'];
+
export interface PanelBaseProps {
location?: string;
}
diff --git a/apps/client/src/features/control/message/MessageControl.tsx b/apps/client/src/features/control/message/MessageControl.tsx
index 3d33e2754..a3b9d23f5 100644
--- a/apps/client/src/features/control/message/MessageControl.tsx
+++ b/apps/client/src/features/control/message/MessageControl.tsx
@@ -52,7 +52,7 @@ export default function MessageControl() {
onClick={() => setMessage.timerBlink(!blink)}
data-testid='toggle timer blink'
>
- Blink message
+ Blink
makeCuesheetColumns(customFields), [customFields]);
const toggleSettings = useCuesheetSettings((state) => state.toggleSettings);
- // Set window title
- useEffect(() => {
- document.title = 'ontime - Cuesheet';
- }, []);
+ useWindowTitle('Cuesheet');
/**
* Handles updating a field
@@ -82,9 +84,24 @@ export default function CuesheetWrapper() {
return (
-
+
+
+ }
+ onClick={onOpen}
+ />
+ }
+ onClick={() => toggleSettings()}
+ />
+
- toggleSettings()} />
('table-order', initialColumnOrder);
+ useEffect(() => {
+ if (!localStorage.getItem('table-order')) {
+ saveColumnOrder(initialColumnOrder);
+ }
+ }, [saveColumnOrder]);
+
const handleOnDragEnd = (event: DragEndEvent) => {
const { delta, active, over } = event;
diff --git a/apps/client/src/features/editors/Editor.tsx b/apps/client/src/features/editors/Editor.tsx
index 9ae9e56a2..0bcf4be2b 100644
--- a/apps/client/src/features/editors/Editor.tsx
+++ b/apps/client/src/features/editors/Editor.tsx
@@ -1,7 +1,11 @@
import { lazy, useCallback, useEffect } from 'react';
+import { IconButton, useDisclosure } from '@chakra-ui/react';
+import { IoApps } from '@react-icons/all-files/io5/IoApps';
+import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu';
import useElectronEvent from '../../common/hooks/useElectronEvent';
+import { useWindowTitle } from '../../common/hooks/useWindowTitle';
import AppSettings from '../app-settings/AppSettings';
import useAppSettingsNavigation from '../app-settings/useAppSettingsNavigation';
import Overview from '../overview/Overview';
@@ -13,16 +17,17 @@ const TimerControl = lazy(() => import('../control/playback/TimerControlExport')
const MessageControl = lazy(() => import('../control/message/MessageControlExport'));
export default function Editor() {
- const { isOpen, setLocation, close } = useAppSettingsNavigation();
+ const { isOpen: isSettingsOpen, setLocation, close } = useAppSettingsNavigation();
const { isElectron } = useElectronEvent();
+ const { isOpen: isMenuOpen, onOpen, onClose } = useDisclosure();
- const handleSettings = useCallback(() => {
- if (isOpen) {
+ const toggleSettings = useCallback(() => {
+ if (isSettingsOpen) {
close();
} else {
setLocation('project');
}
- }, [close, isOpen, setLocation]);
+ }, [close, isSettingsOpen, setLocation]);
// Handle keyboard shortcuts
const handleKeyPress = useCallback(
@@ -34,13 +39,13 @@ export default function Editor() {
if (event.ctrlKey || event.metaKey) {
// ctrl + , (settings)
if (event.key === ',') {
- handleSettings();
+ toggleSettings();
event.preventDefault();
event.stopPropagation();
}
}
},
- [handleSettings],
+ [toggleSettings],
);
// register ctrl + , to open settings
@@ -55,15 +60,28 @@ export default function Editor() {
};
}, [handleKeyPress, isElectron]);
- // Set window title
- useEffect(() => {
- document.title = 'ontime - Editor';
- }, []);
+ useWindowTitle('Editor');
return (
-
- {isOpen ? (
+
+
+ }
+ onClick={onOpen}
+ />
+ }
+ onClick={toggleSettings}
+ />
+
+ {isSettingsOpen ? (
) : (
@@ -74,7 +92,6 @@ export default function Editor() {
)}
-
);
}
diff --git a/apps/client/src/features/operator/Operator.tsx b/apps/client/src/features/operator/Operator.tsx
index 7e8aa855d..10eb56f6c 100644
--- a/apps/client/src/features/operator/Operator.tsx
+++ b/apps/client/src/features/operator/Operator.tsx
@@ -3,12 +3,12 @@ import { useSearchParams } from 'react-router-dom';
import { CustomField, CustomFields, isOntimeEvent, OntimeEvent, SupportedEvent } from 'ontime-types';
import { getFirstEventNormal, getLastEventNormal } from 'ontime-utils';
-import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu';
import Empty from '../../common/components/state/Empty';
import { getOperatorOptions } from '../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
import useFollowComponent from '../../common/hooks/useFollowComponent';
import { useOperator } from '../../common/hooks/useSocket';
+import { useWindowTitle } from '../../common/hooks/useWindowTitle';
import useCustomFields from '../../common/hooks-query/useCustomFields';
import useProjectData from '../../common/hooks-query/useProjectData';
import useRundown from '../../common/hooks-query/useRundown';
@@ -41,7 +41,7 @@ export default function Operator() {
const timeoutId = useRef(null);
const featureData = useOperator();
- const [searchParams, setSearchParams] = useSearchParams();
+ const [searchParams] = useSearchParams();
const { data: settings } = useSettings();
const [showEditPrompt, setShowEditPrompt] = useState(false);
@@ -57,10 +57,7 @@ export default function Operator() {
topOffset: selectedOffset,
});
- // Set window title
- useEffect(() => {
- document.title = 'ontime - Operator';
- }, []);
+ useWindowTitle('Operator');
// reset scroll if nothing is selected
useEffect(() => {
@@ -116,11 +113,6 @@ export default function Operator() {
[searchParams],
);
- const showEditFormDrawer = useCallback(() => {
- searchParams.set('edit', 'true');
- setSearchParams(searchParams);
- }, [searchParams, setSearchParams]);
-
const missingData = !data || !customFields || !projectData;
const isLoading = status === 'pending' || customFieldStatus === 'pending' || projectDataStatus === 'pending';
@@ -146,7 +138,6 @@ export default function Operator() {
return (
-
{editEvent && setEditEvent(null)} />}
diff --git a/apps/client/src/features/operator/OperatorExport.tsx b/apps/client/src/features/operator/OperatorExport.tsx
new file mode 100644
index 000000000..ff3cf2747
--- /dev/null
+++ b/apps/client/src/features/operator/OperatorExport.tsx
@@ -0,0 +1,29 @@
+import { useCallback } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { useDisclosure } from '@chakra-ui/react';
+
+import FloatingNavigation from '../../common/components/navigation-menu/FloatingNavigation';
+import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu';
+import ProtectRoute from '../../common/components/protect-route/ProtectRoute';
+
+import Operator from './Operator';
+
+export default function OperatorExport() {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const { isOpen, onOpen, onClose } = useDisclosure();
+
+ const showEditFormDrawer = useCallback(() => {
+ searchParams.set('edit', 'true');
+ setSearchParams(searchParams);
+ }, [searchParams, setSearchParams]);
+
+ const toggleMenu = isOpen ? onClose : onOpen;
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/client/src/features/overview/Overview.module.scss b/apps/client/src/features/overview/Overview.module.scss
index 57cbd04c7..4aa87687e 100644
--- a/apps/client/src/features/overview/Overview.module.scss
+++ b/apps/client/src/features/overview/Overview.module.scss
@@ -1,10 +1,20 @@
.overview {
grid-area: overview;
+ font-size: $inner-section-text-size;
+ display: flex;
+}
+
+.nav {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.info {
+ flex: 1;
+ padding-inline: 1rem;
display: flex;
align-items: center;
justify-content: space-between;
- font-size: $inner-section-text-size;
- padding: 0 1rem;
}
.title {
diff --git a/apps/client/src/features/overview/Overview.tsx b/apps/client/src/features/overview/Overview.tsx
index c2f6ec320..d2b138ec0 100644
--- a/apps/client/src/features/overview/Overview.tsx
+++ b/apps/client/src/features/overview/Overview.tsx
@@ -12,7 +12,7 @@ import style from './Overview.module.scss';
export default memo(Overview);
-function Overview() {
+function Overview({ children }: { children: React.ReactNode }) {
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRuntimeOverview();
const [maybePlannedEnd, maybePlannedDaySpan] = useMemo(() => calculateEndAndDaySpan(plannedEnd), [plannedEnd]);
@@ -24,15 +24,23 @@ function Overview() {
return (
-
-
-
-
-
-
-
@@ -43,7 +51,7 @@ function TitlesOverview() {
const { data } = useProjectData();
return (
-
+
{data.title}
{data.description}
diff --git a/apps/client/src/features/rundown/Rundown.tsx b/apps/client/src/features/rundown/Rundown.tsx
index 4c7df353c..3d059d901 100644
--- a/apps/client/src/features/rundown/Rundown.tsx
+++ b/apps/client/src/features/rundown/Rundown.tsx
@@ -39,7 +39,6 @@ export default function Rundown({ data }: RundownProps) {
const eventSettings = useEditorSettings((state) => state.eventSettings);
const defaultPublic = eventSettings.defaultPublic;
const linkPrevious = eventSettings.linkPrevious;
- const showQuickEntry = eventSettings.showQuickEntry;
// cursor
const { cursor, mode: appMode, setCursor } = useAppMode();
@@ -219,6 +218,8 @@ export default function Rundown({ data }: RundownProps) {
// all events before the current selected are in the past
let isPast = Boolean(featureData?.selectedEventId);
+ const isEditMode = appMode === AppMode.Edit;
+
return (
@@ -248,6 +249,7 @@ export default function Rundown({ data }: RundownProps) {
thisId = eventId;
}
}
+ const isFirst = index === 0;
const isLast = index === order.length - 1;
const isLoaded = featureData?.selectedEventId === event.id;
const isNext = featureData?.nextEventId === event.id;
@@ -258,6 +260,14 @@ export default function Rundown({ data }: RundownProps) {
return (
+ {isEditMode && (hasCursor || isFirst) && (
+
+ )}
{isOntimeEvent(event) && {eventIndex} }
@@ -277,9 +287,9 @@ export default function Rundown({ data }: RundownProps) {
/>
- {((showQuickEntry && hasCursor) || isLast) && (
+ {isEditMode && (hasCursor || isLast) && (
);
} else if (data.type === SupportedEvent.Block) {
- return ;
+ return actionHandler('delete')} />;
} else if (data.type === SupportedEvent.Delay) {
- return ;
+ return ;
}
return null;
}
diff --git a/apps/client/src/features/rundown/block-block/BlockBlock.tsx b/apps/client/src/features/rundown/block-block/BlockBlock.tsx
index c1248664e..16dc3da4f 100644
--- a/apps/client/src/features/rundown/block-block/BlockBlock.tsx
+++ b/apps/client/src/features/rundown/block-block/BlockBlock.tsx
@@ -1,32 +1,24 @@
-import { useEffect, useRef } from 'react';
+import { useRef } from 'react';
+import { IconButton } from '@chakra-ui/react';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
-import { OntimeBlock, OntimeEvent } from 'ontime-types';
+import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
+import { OntimeBlock } from 'ontime-types';
import { cx } from '../../../common/utils/styleUtils';
import EditableBlockTitle from '../common/EditableBlockTitle';
-import BlockActionMenu from '../event-block/composite/BlockActionMenu';
-import type { EventItemActions } from '../RundownEntry';
import style from './BlockBlock.module.scss';
interface BlockBlockProps {
data: OntimeBlock;
hasCursor: boolean;
- actionHandler: (
- action: EventItemActions,
- payload?:
- | number
- | {
- field: keyof Omit | 'durationOverride';
- value: unknown;
- },
- ) => void;
+ onDelete: () => void;
}
export default function BlockBlock(props: BlockBlockProps) {
- const { data, hasCursor, actionHandler } = props;
+ const { data, hasCursor, onDelete } = props;
const handleRef = useRef(null);
const {
@@ -45,12 +37,6 @@ export default function BlockBlock(props: BlockBlockProps) {
transition,
};
- useEffect(() => {
- if (hasCursor) {
- handleRef?.current?.focus();
- }
- }, [hasCursor]);
-
const blockClasses = cx([style.block, hasCursor ? style.hasCursor : null]);
return (
@@ -59,7 +45,14 @@ export default function BlockBlock(props: BlockBlockProps) {
-
+ }
+ variant='ontime-subtle'
+ color='#FA5656'
+ onClick={onDelete}
+ />
);
}
diff --git a/apps/client/src/features/rundown/delay-block/DelayBlock.tsx b/apps/client/src/features/rundown/delay-block/DelayBlock.tsx
index 136035375..e99f2a491 100644
--- a/apps/client/src/features/rundown/delay-block/DelayBlock.tsx
+++ b/apps/client/src/features/rundown/delay-block/DelayBlock.tsx
@@ -5,32 +5,21 @@ import { CSS } from '@dnd-kit/utilities';
import { IoCheckmark } from '@react-icons/all-files/io5/IoCheckmark';
import { IoClose } from '@react-icons/all-files/io5/IoClose';
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
-import { OntimeDelay, OntimeEvent } from 'ontime-types';
+import { OntimeDelay } from 'ontime-types';
import DelayInput from '../../../common/components/input/delay-input/DelayInput';
import { useEventAction } from '../../../common/hooks/useEventAction';
import { cx } from '../../../common/utils/styleUtils';
-import BlockActionMenu from '../event-block/composite/BlockActionMenu';
-import type { EventItemActions } from '../RundownEntry';
import style from './DelayBlock.module.scss';
interface DelayBlockProps {
data: OntimeDelay;
hasCursor: boolean;
- actionHandler: (
- action: EventItemActions,
- payload?:
- | number
- | {
- field: keyof Omit | 'durationOverride';
- value: unknown;
- },
- ) => void;
}
export default function DelayBlock(props: DelayBlockProps) {
- const { data, hasCursor, actionHandler } = props;
+ const { data, hasCursor } = props;
const { applyDelay, deleteEvent } = useEventAction();
const handleRef = useRef(null);
@@ -79,7 +68,6 @@ export default function DelayBlock(props: DelayBlockProps) {
} variant='ontime-subtle-white'>
Cancel
-
);
diff --git a/apps/client/src/features/rundown/event-block/EventBlock.module.scss b/apps/client/src/features/rundown/event-block/EventBlock.module.scss
index 8f66bff92..2328b52e0 100644
--- a/apps/client/src/features/rundown/event-block/EventBlock.module.scss
+++ b/apps/client/src/features/rundown/event-block/EventBlock.module.scss
@@ -9,7 +9,7 @@ $skip-opacity: 0.2;
display: grid;
grid-template-areas:
'binder ... ... ...'
- 'binder pb-actions times actions'
+ 'binder pb-actions times ...'
'binder pb-actions title title'
'binder pb-actions estatus estatus'
'binder ... ... ...';
@@ -55,7 +55,6 @@ $skip-opacity: 0.2;
outline: 1px solid $block-cursor-color;
}
- /* we stop the eventActions from having opacity to fix issue with dropdown drawing order */
&.past:not(.skip) {
.timerNote,
.statusElements,
@@ -153,12 +152,6 @@ $skip-opacity: 0.2;
}
}
-.eventActions {
- grid-area: actions;
- height: 100%;
- text-align: end;
-}
-
.progressBg {
grid-area: progb;
border-radius: 1px;
diff --git a/apps/client/src/features/rundown/event-block/EventBlock.tsx b/apps/client/src/features/rundown/event-block/EventBlock.tsx
index 67a84ab3e..907399acc 100644
--- a/apps/client/src/features/rundown/event-block/EventBlock.tsx
+++ b/apps/client/src/features/rundown/event-block/EventBlock.tsx
@@ -2,16 +2,18 @@ import { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
-import { IoCopyOutline } from '@react-icons/all-files/io5/IoCopyOutline';
+import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline';
import { IoPeople } from '@react-icons/all-files/io5/IoPeople';
import { IoPeopleOutline } from '@react-icons/all-files/io5/IoPeopleOutline';
+import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
+import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline';
+import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
import { EndAction, MaybeNumber, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types';
import { useContextMenu } from '../../../common/hooks/useContextMenu';
import { useAppMode } from '../../../common/stores/appModeStore';
-import copyToClipboard from '../../../common/utils/copyToClipboard';
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
import type { EventItemActions } from '../RundownEntry';
import { useEventIdSwapping } from '../useEventIdSwapping';
@@ -96,31 +98,25 @@ export default function EventBlock(props: EventBlockProps) {
selectedEvents.size > 1
? [
{
- label: 'Visiblity',
- group: [
- {
- label: 'Make public',
- icon: IoPeople,
- onClick: () =>
- actionHandler('update', {
- field: 'isPublic',
- value: true,
- }),
- },
- {
- label: 'Make private',
- icon: IoPeopleOutline,
- onClick: () =>
- actionHandler('update', {
- field: 'isPublic',
- value: false,
- }),
- },
- ],
+ label: 'Make public',
+ icon: IoPeople,
+ onClick: () =>
+ actionHandler('update', {
+ field: 'isPublic',
+ value: true,
+ }),
+ },
+ {
+ label: 'Make private',
+ icon: IoPeopleOutline,
+ onClick: () =>
+ actionHandler('update', {
+ field: 'isPublic',
+ value: false,
+ }),
},
]
: [
- { label: `Copy ID: ${eventId}`, icon: IoCopyOutline, onClick: () => copyToClipboard(eventId) },
{
label: 'Toggle public',
icon: IoPeopleOutline,
@@ -145,6 +141,14 @@ export default function EventBlock(props: EventBlockProps) {
},
isDisabled: selectedEventId == null || selectedEventId === eventId,
},
+ { withDivider: true, label: 'Clone', icon: IoDuplicateOutline, onClick: () => actionHandler('clone') },
+ { withDivider: true, label: 'Event before', icon: IoAdd, onClick: () => actionHandler('event-before') },
+ { label: 'Event after', icon: IoAdd, onClick: () => actionHandler('event') },
+ { label: 'Block before', icon: IoRemoveCircleOutline, onClick: () => actionHandler('block-before') },
+ { label: 'Block after', icon: IoRemoveCircleOutline, onClick: () => actionHandler('block') },
+ { label: 'Delay before', icon: IoTimerOutline, onClick: () => actionHandler('delay-before') },
+ { label: 'Delay after', icon: IoTimerOutline, onClick: () => actionHandler('delay') },
+ { withDivider: true, label: 'Delete', icon: IoTrash, onClick: () => actionHandler('delete') },
],
);
@@ -281,7 +285,6 @@ export default function EventBlock(props: EventBlockProps) {
loaded={loaded}
playback={playback}
isRolling={isRolling}
- actionHandler={actionHandler}
/>
)}
diff --git a/apps/client/src/features/rundown/event-block/EventBlockInner.tsx b/apps/client/src/features/rundown/event-block/EventBlockInner.tsx
index e279ddba3..827fe3052 100644
--- a/apps/client/src/features/rundown/event-block/EventBlockInner.tsx
+++ b/apps/client/src/features/rundown/event-block/EventBlockInner.tsx
@@ -14,10 +14,8 @@ import { EndAction, MaybeString, Playback, TimerType, TimeStrategy } from 'ontim
import { cx } from '../../../common/utils/styleUtils';
import { tooltipDelayMid } from '../../../ontimeConfig';
import EditableBlockTitle from '../common/EditableBlockTitle';
-import { EventItemActions } from '../RundownEntry';
import TimeInputFlow from '../time-input-flow/TimeInputFlow';
-import BlockActionMenu from './composite/BlockActionMenu';
import EventBlockPlayback from './composite/EventBlockPlayback';
import EventBlockProgressBar from './composite/EventBlockProgressBar';
@@ -46,7 +44,6 @@ interface EventBlockInnerProps {
loaded: boolean;
playback?: Playback;
isRolling: boolean;
- actionHandler: (action: EventItemActions, payload?: any) => void;
}
const EventBlockInner = (props: EventBlockInnerProps) => {
@@ -68,7 +65,6 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
loaded,
playback,
isRolling,
- actionHandler,
} = props;
const [renderInner, setRenderInner] = useState(false);
@@ -139,9 +135,6 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
-
-
-
>
);
};
diff --git a/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx b/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx
deleted file mode 100644
index 04452cfa3..000000000
--- a/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useCallback } from 'react';
-import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Tooltip } from '@chakra-ui/react';
-import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
-import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline';
-import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal';
-import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
-import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline';
-import { IoTrashBinSharp } from '@react-icons/all-files/io5/IoTrashBinSharp';
-
-import { tooltipDelayMid } from '../../../../ontimeConfig';
-import { EventItemActions } from '../../RundownEntry';
-
-interface BlockActionMenuProps {
- enableDelete?: boolean;
- showClone?: boolean;
- actionHandler: (action: EventItemActions, payload?: any) => void;
- className?: string;
-}
-
-export default function BlockActionMenu(props: BlockActionMenuProps) {
- const { enableDelete, showClone, actionHandler, className } = props;
-
- const handleAddEvent = useCallback(() => actionHandler('event'), [actionHandler]);
- const handleAddDelay = useCallback(() => actionHandler('delay'), [actionHandler]);
- const handleAddBlock = useCallback(() => actionHandler('block'), [actionHandler]);
- const handleClone = useCallback(() => actionHandler('clone'), [actionHandler]);
- const handleDelete = useCallback(() => actionHandler('delete'), [actionHandler]);
-
- return (
-
- );
-}
diff --git a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss
index 4a95ad6f0..e5b146389 100644
--- a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss
+++ b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss
@@ -23,7 +23,7 @@
padding: 0 0.25rem;
color: $label-gray;
border-radius: 2px;
- background-color: $black-10
+ background-color: $black-10;
}
.options {
diff --git a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx
index f85cf6e91..90f58a0fa 100644
--- a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx
+++ b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx
@@ -12,14 +12,14 @@ import { tooltipDelayMid } from '../../../ontimeConfig';
import style from './QuickAddBlock.module.scss';
interface QuickAddBlockProps {
- showKbd: boolean;
- previousEventId: string;
+ showKbd: 'above' | 'below' | 'none';
+ previousEventId?: string;
disableAddDelay?: boolean;
disableAddBlock: boolean;
}
const QuickAddBlock = (props: QuickAddBlockProps) => {
- const { showKbd, previousEventId, disableAddDelay = true, disableAddBlock } = props;
+ const { showKbd = 'none', previousEventId, disableAddDelay = true, disableAddBlock } = props;
const { addEvent } = useEventAction();
const { emitError } = useEmitLog();
@@ -28,6 +28,8 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
const { defaultPublic, linkPrevious } = useEditorSettings((state) => state.eventSettings);
+ const shortcutBase = showKbd === 'none' ? '' : `${deviceAlt} ${showKbd === 'above' ? '⇧' : ''}`;
+
const handleCreateEvent = useCallback(
(eventType: SupportedEvent) => {
switch (eventType) {
@@ -70,6 +72,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
[previousEventId, addEvent, emitError],
);
+ const canLinkPrevious = Boolean(previousEventId);
+ const shouldLinkPrevious = Boolean(linkPrevious) && canLinkPrevious;
+
return (
@@ -79,10 +84,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
size='xs'
variant='ontime-subtle-white'
className={style.quickBtn}
- data-testid='quick-add-event'
leftIcon={}
>
- Event {showKbd && {`${deviceAlt} + E`}}
+ Event {shortcutBase && {`${shortcutBase} E`}}
@@ -92,10 +96,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
variant='ontime-subtle-white'
disabled={disableAddDelay}
className={style.quickBtn}
- data-testid='quick-add-delay'
leftIcon={}
>
- Delay {showKbd && {`${deviceAlt} + D`}}
+ Delay {shortcutBase && {`${shortcutBase} D`}}
@@ -105,15 +108,20 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
variant='ontime-subtle-white'
disabled={disableAddBlock}
className={style.quickBtn}
- data-testid='quick-add-block'
leftIcon={}
>
- Block {showKbd && {`${deviceAlt} + B`}}
+ Block {shortcutBase && {`${shortcutBase} B`}}
-
+
Link to previous
diff --git a/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx b/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx
index 6875c1fb0..f30635196 100644
--- a/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx
+++ b/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx
@@ -1,8 +1,6 @@
-import { Button, ButtonGroup, MenuButton } from '@chakra-ui/react';
-import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
+import { ButtonGroup } from '@chakra-ui/react';
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
-import { IoSnowOutline } from '@react-icons/all-files/io5/IoSnowOutline';
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
@@ -16,20 +14,10 @@ export default function RundownHeader() {
const setAppMode = useAppMode((state) => state.setMode);
const setRunMode = () => setAppMode(AppMode.Run);
const setEditMode = () => setAppMode(AppMode.Edit);
- const setFreezeMode = () => setAppMode(AppMode.Freeze);
return (
- }
- clickHandler={setFreezeMode}
- tooltip='Freeze rundown'
- aria-label='Freeze rundown'
- isDisabled
- />
-
- } aria-label='Rundown menu' variant='ontime-outlined'>
- Rundown
-
-
+
);
}
diff --git a/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx b/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx
index ef409e266..12493d8b2 100644
--- a/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx
+++ b/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx
@@ -1,31 +1,16 @@
-import { memo, ReactNode, useCallback } from 'react';
-import { Menu, MenuDivider, MenuItem, MenuList } from '@chakra-ui/react';
-import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
-import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
-import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline';
-import { IoTrashOutline } from '@react-icons/all-files/io5/IoTrashOutline';
-import { SupportedEvent } from 'ontime-types';
+import { useCallback } from 'react';
+import { Button } from '@chakra-ui/react';
+import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
import { useEventAction } from '../../../common/hooks/useEventAction';
import { useAppMode } from '../../../common/stores/appModeStore';
import { useEventSelection } from '../useEventSelection';
-const RundownMenu = ({ children }: { children: ReactNode }) => {
+export default function RundownMenu() {
const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents);
const setCursor = useAppMode((state) => state.setCursor);
- const { addEvent, deleteAllEvents } = useEventAction();
-
- const newEvent = useCallback(() => {
- addEvent({ type: SupportedEvent.Event });
- }, [addEvent]);
-
- const newBlock = useCallback(() => {
- addEvent({ type: SupportedEvent.Block });
- }, [addEvent]);
-
- const newDelay = useCallback(() => {
- addEvent({ type: SupportedEvent.Delay });
- }, [addEvent]);
+ const appMode = useAppMode((state) => state.mode);
+ const { deleteAllEvents } = useEventAction();
const deleteAll = useCallback(() => {
deleteAllEvents();
@@ -34,25 +19,15 @@ const RundownMenu = ({ children }: { children: ReactNode }) => {
}, [clearSelectedEvents, deleteAllEvents, setCursor]);
return (
-
+ }
+ onClick={deleteAll}
+ color='#FA5656'
+ isDisabled={appMode === 'run'}
+ >
+ Clear rundown
+
);
-};
-
-export default memo(RundownMenu);
+}
diff --git a/apps/client/src/features/viewers/backstage/Backstage.tsx b/apps/client/src/features/viewers/backstage/Backstage.tsx
index 3be11f2d8..3f0e7026d 100644
--- a/apps/client/src/features/viewers/backstage/Backstage.tsx
+++ b/apps/client/src/features/viewers/backstage/Backstage.tsx
@@ -14,6 +14,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard';
import { getBackstageOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
+import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
@@ -57,10 +58,7 @@ export default function Backstage(props: BackstageProps) {
const [blinkClass, setBlinkClass] = useState(false);
const [searchParams] = useSearchParams();
- // Set window title
- useEffect(() => {
- document.title = 'ontime - Backstage Screen';
- }, []);
+ useWindowTitle('Backstage');
// blink on change
useEffect(() => {
diff --git a/apps/client/src/features/viewers/clock/Clock.tsx b/apps/client/src/features/viewers/clock/Clock.tsx
index 35168f8e5..ec6c7ccbe 100644
--- a/apps/client/src/features/viewers/clock/Clock.tsx
+++ b/apps/client/src/features/viewers/clock/Clock.tsx
@@ -1,4 +1,3 @@
-import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Settings, ViewSettings } from 'ontime-types';
@@ -6,6 +5,7 @@ import { overrideStylesURL } from '../../../common/api/constants';
import { getClockOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
+import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { OverridableOptions } from '../../../common/models/View.types';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
@@ -25,9 +25,7 @@ export default function Clock(props: ClockProps) {
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const [searchParams] = useSearchParams();
- useEffect(() => {
- document.title = 'ontime - Clock';
- }, []);
+ useWindowTitle('Clock');
// defer rendering until we load stylesheets
if (!shouldRender) {
diff --git a/apps/client/src/features/viewers/countdown/Countdown.tsx b/apps/client/src/features/viewers/countdown/Countdown.tsx
index 0b7f42d29..3713b5903 100644
--- a/apps/client/src/features/viewers/countdown/Countdown.tsx
+++ b/apps/client/src/features/viewers/countdown/Countdown.tsx
@@ -7,6 +7,7 @@ import { overrideStylesURL } from '../../../common/api/constants';
import { getCountdownOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
+import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
@@ -37,9 +38,7 @@ export default function Countdown(props: CountdownProps) {
const [runningMessage, setRunningMessage] = useState(TimerMessage.unhandled);
const [delay, setDelay] = useState(0);
- useEffect(() => {
- document.title = 'ontime - Countdown';
- }, []);
+ useWindowTitle('Countdown');
// eg. http://localhost:4001/countdown?eventId=ei0us
// Check for user options
diff --git a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx
index 0c8f46f52..5c6b3c348 100644
--- a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx
+++ b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx
@@ -6,6 +6,7 @@ import { overrideStylesURL } from '../../../common/api/constants';
import { getLowerThirdOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
+import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
import { getPropertyValue } from '../common/viewUtils';
import './LowerThird.scss';
@@ -141,10 +142,7 @@ export default function LowerThird(props: LowerProps) {
const [playState, setPlayState] = useState<'pre' | 'in' | 'out'>('pre');
useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
- // set window title
- useEffect(() => {
- document.title = 'ontime - Lower Third';
- }, []);
+ useWindowTitle('Lower Third');
const trigger = useMemo(() => {
if (options.trigger === TriggerType.Event) {
diff --git a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx
index d0986e301..663bbeb05 100644
--- a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx
+++ b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx
@@ -1,4 +1,3 @@
-import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
import { MILLIS_PER_SECOND, millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils';
@@ -7,6 +6,7 @@ import { overrideStylesURL } from '../../../common/api/constants';
import { MINIMAL_TIMER_OPTIONS } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
+import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { OverridableOptions } from '../../../common/models/View.types';
import { timerPlaceholder } from '../../../common/utils/styleUtils';
@@ -28,9 +28,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
const { getLocalizedString } = useTranslation();
const [searchParams] = useSearchParams();
- useEffect(() => {
- document.title = 'ontime - Minimal Timer';
- }, []);
+ useWindowTitle('Minimal Timer');
// defer rendering until we load stylesheets
if (!shouldRender) {
diff --git a/apps/client/src/features/viewers/public/Public.tsx b/apps/client/src/features/viewers/public/Public.tsx
index 52895fd71..d217faac9 100644
--- a/apps/client/src/features/viewers/public/Public.tsx
+++ b/apps/client/src/features/viewers/public/Public.tsx
@@ -1,4 +1,3 @@
-import { useEffect } from 'react';
import QRCode from 'react-qr-code';
import { useSearchParams } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
@@ -12,6 +11,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard';
import { getPublicOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
+import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
@@ -54,10 +54,7 @@ export default function Public(props: BackstageProps) {
const { getLocalizedString } = useTranslation();
const [searchParams] = useSearchParams();
- // set window title
- useEffect(() => {
- document.title = 'ontime - Public Screen';
- }, []);
+ useWindowTitle('Public Schedule');
// defer rendering until we load stylesheets
if (!shouldRender) {
diff --git a/apps/client/src/features/viewers/studio/StudioClock.tsx b/apps/client/src/features/viewers/studio/StudioClock.tsx
index b74e28016..e48f985df 100644
--- a/apps/client/src/features/viewers/studio/StudioClock.tsx
+++ b/apps/client/src/features/viewers/studio/StudioClock.tsx
@@ -1,4 +1,3 @@
-import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import type { OntimeEvent, OntimeRundown, Settings, ViewSettings } from 'ontime-types';
import { isOntimeEvent, Playback } from 'ontime-types';
@@ -9,6 +8,7 @@ import { getStudioClockOptions } from '../../../common/components/view-params-ed
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import useFitText from '../../../common/hooks/useFitText';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
+import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
@@ -46,9 +46,7 @@ export default function StudioClock(props: StudioClockProps) {
const [searchParams] = useSearchParams();
- useEffect(() => {
- document.title = 'ontime - Studio Clock';
- }, []);
+ useWindowTitle('Studio Clock');
let clock = formatTime(time.clock);
let hasAmPm = '';
diff --git a/apps/client/src/features/viewers/timer/Timer.scss b/apps/client/src/features/viewers/timer/Timer.scss
index 7eedbe2f1..0155374ab 100644
--- a/apps/client/src/features/viewers/timer/Timer.scss
+++ b/apps/client/src/features/viewers/timer/Timer.scss
@@ -32,6 +32,8 @@
.blackout {
position: absolute;
+ top: 0;
+ left: 0;
width: 100vw;
height: 100vh;
background-color: #000;
diff --git a/apps/client/src/features/viewers/timer/Timer.tsx b/apps/client/src/features/viewers/timer/Timer.tsx
index 333606df6..2141877ad 100644
--- a/apps/client/src/features/viewers/timer/Timer.tsx
+++ b/apps/client/src/features/viewers/timer/Timer.tsx
@@ -1,4 +1,3 @@
-import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import {
@@ -19,6 +18,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard';
import { getTimerOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
+import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { timerPlaceholder } from '../../../common/utils/styleUtils';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
@@ -63,9 +63,7 @@ export default function Timer(props: TimerProps) {
const { getLocalizedString } = useTranslation();
const [searchParams] = useSearchParams();
- useEffect(() => {
- document.title = 'ontime - Timer';
- }, []);
+ useWindowTitle('Timer');
// defer rendering until we load stylesheets
if (!shouldRender) {
diff --git a/apps/client/src/theme/ontimeCheckbox.ts b/apps/client/src/theme/ontimeCheckbox.ts
index 003937d95..345c928fb 100644
--- a/apps/client/src/theme/ontimeCheckbox.ts
+++ b/apps/client/src/theme/ontimeCheckbox.ts
@@ -3,12 +3,24 @@ export const ontimeCheckboxOnDark = {
border: '1px',
borderColor: '#2d2d2d', // $gray-1100
backgroundColor: '#2d2d2d', // $gray-1100
+ _disabled: {
+ color: 'white',
+ borderColor: '#2d2d2d', // $gray-1100
+ backgroundColor: '#2d2d2d', // $gray-1100
+ opacity: 0.6,
+ },
_checked: {
borderColor: '#3182ce', // $action-blue
backgroundColor: '#3182ce', //$action-blue
+ _disabled: {
+ color: 'white',
+ borderColor: '#3182ce', // $action-blue
+ backgroundColor: '#3182ce', //$action-blue
+ opacity: 0.6,
+ },
},
_focus: {
- boxShadow: '0 0 0 1px #578AF4', // $blue-500
+ boxShadow: 'none',
},
},
label: {
diff --git a/apps/client/src/theme/ontimeMenu.ts b/apps/client/src/theme/ontimeMenu.ts
index b38ddebad..59f01daa4 100644
--- a/apps/client/src/theme/ontimeMenu.ts
+++ b/apps/client/src/theme/ontimeMenu.ts
@@ -1,19 +1,27 @@
export const ontimeMenuOnDark = {
list: {
+ fontSize: 'calc(1rem - 2px)',
borderRadius: '3px',
- border: 'none',
- bg: '#fff', // $gray-50
+ borderColor: 'rgba(255, 255, 255, 0.1)',
+ color: '#ececec', // $gray-1030
+ backgroundColor: '#202020', // $gray-1250
zIndex: 100,
},
item: {
- letterSpacing: '0.15px',
- color: '#101010', // $gray-1350
- bg: '#fff', //
+ backgroundColor: 'transparent',
+ paddingBlock: '0.5rem',
_hover: {
- backgroundColor: '#e2e2e2', // $gray-200
+ backgroundColor: 'rgba(0, 0, 0, 0.1)',
+ _disabled: {
+ backgroundColor: 'transparent',
+ },
+ },
+ _disabled: {
+ color: '#b1b1b1', // $gray-400
},
},
divider: {
- borderColor: '#cfcfcf', // $gray-200
+ borderColor: 'rgba(255, 255, 255, 0.07)',
+ opacity: 1,
},
};
diff --git a/apps/client/src/theme/theme.ts b/apps/client/src/theme/theme.ts
index a34c6cb8b..7681c3bdf 100644
--- a/apps/client/src/theme/theme.ts
+++ b/apps/client/src/theme/theme.ts
@@ -57,8 +57,8 @@ const theme = extendTheme({
},
Drawer: {
variants: {
- 'ontime': {...ontimeDrawer},
- }
+ ontime: { ...ontimeDrawer },
+ },
},
Editable: {
variants: {
diff --git a/apps/server/package.json b/apps/server/package.json
index 3dc0c112c..513a292b4 100644
--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -43,6 +43,7 @@
"nodemon": "^2.0.20",
"ontime-types": "workspace:*",
"prettier": "^3.0.3",
+ "server-timing": "^3.3.3",
"shx": "^0.3.4",
"ts-node": "^10.9.1",
"typescript": "^5.4.3",
diff --git a/apps/server/src/adapters/WebsocketAdapter.ts b/apps/server/src/adapters/WebsocketAdapter.ts
index 079f8aa54..f953af9a1 100644
--- a/apps/server/src/adapters/WebsocketAdapter.ts
+++ b/apps/server/src/adapters/WebsocketAdapter.ts
@@ -116,16 +116,14 @@ export class SocketServer implements IAdapter {
// Protocol specific stuff handled above
try {
- const reply = dispatchFromAdapter(
- type,
- {
- payload,
- },
- 'ws',
- );
+ const reply = dispatchFromAdapter(type, { payload }, 'ws');
if (reply) {
- const { payload } = reply;
- ws.send(type, payload);
+ ws.send(
+ JSON.stringify({
+ type: 'ontime-change',
+ payload: reply.payload,
+ }),
+ );
}
} catch (error) {
logger.error(LogOrigin.Rx, `WS IN: ${error}`);
diff --git a/apps/server/src/api-data/custom-fields/customFields.controller.ts b/apps/server/src/api-data/custom-fields/customFields.controller.ts
index 42c428c08..71c55f5f8 100644
--- a/apps/server/src/api-data/custom-fields/customFields.controller.ts
+++ b/apps/server/src/api-data/custom-fields/customFields.controller.ts
@@ -1,7 +1,8 @@
-import { CustomField, CustomFields } from 'ontime-types';
+import { CustomField, CustomFields, ErrorResponse } from 'ontime-types';
import type { Request, Response } from 'express';
+import { getErrorMessage } from 'ontime-utils';
import {
createCustomField,
editCustomField,
@@ -14,36 +15,37 @@ export async function getCustomFields(_req: Request, res: Response
res.json(customFields);
}
-// Expects { label: |