mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 22:49:18 +00:00
feat: edit mode (#344)
* style: add mode selection to menu * style: rename rundown functions * feat: edit mode
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
"prettier"
|
"prettier"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||||
"prettier/prettier": [
|
"prettier/prettier": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
export enum AppMode {
|
||||||
|
Run = 'run',
|
||||||
|
Edit = 'edit',
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppModeStore = {
|
||||||
|
mode: AppMode;
|
||||||
|
cursor: string | null;
|
||||||
|
editId: string | null;
|
||||||
|
setMode: (mode: AppMode) => void;
|
||||||
|
setCursor: (id: string | null, isEditable?: boolean) => void;
|
||||||
|
setEditId: (id: string | null) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAppMode = create<AppModeStore>()((set) => ({
|
||||||
|
mode: AppMode.Edit,
|
||||||
|
cursor: null,
|
||||||
|
editId: null,
|
||||||
|
setMode: (mode: AppMode) =>
|
||||||
|
set((state) => {
|
||||||
|
return mode === AppMode.Edit
|
||||||
|
? {
|
||||||
|
editId: state.cursor,
|
||||||
|
mode: mode,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
editId: null,
|
||||||
|
mode: mode,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
setCursor: (id: string | null, isEditable?: boolean) =>
|
||||||
|
set((state) => {
|
||||||
|
if (isEditable) {
|
||||||
|
return state.mode === AppMode.Edit
|
||||||
|
? {
|
||||||
|
cursor: id,
|
||||||
|
editId: id,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
cursor: id,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { cursor: id, editId: null };
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
setEditId: (id: string | null) =>
|
||||||
|
set((state) => {
|
||||||
|
return state.mode === AppMode.Edit
|
||||||
|
? {
|
||||||
|
cursor: id,
|
||||||
|
editId: id,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
editId: id,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}));
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { create } from 'zustand';
|
|
||||||
|
|
||||||
import { booleanFromLocalStorage } from '../utils/localStorage';
|
|
||||||
|
|
||||||
type CursorStore = {
|
|
||||||
cursor: number;
|
|
||||||
isCursorLocked: boolean;
|
|
||||||
toggleCursorLocked: (newValue?: boolean) => void;
|
|
||||||
moveCursorTo: (index: number) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cursorLockedKey = 'ontime-cursor-islocked';
|
|
||||||
|
|
||||||
export const useCursor = create<CursorStore>()((set) => ({
|
|
||||||
cursor: 0,
|
|
||||||
isCursorLocked: booleanFromLocalStorage(cursorLockedKey, false),
|
|
||||||
toggleCursorLocked: (newValue?: boolean) =>
|
|
||||||
set((state) => {
|
|
||||||
const val = typeof newValue === 'undefined' ? !state.isCursorLocked : newValue;
|
|
||||||
localStorage.setItem(cursorLockedKey, String(val));
|
|
||||||
return { isCursorLocked: val };
|
|
||||||
}),
|
|
||||||
moveCursorTo: (index: number) => set(() => ({ cursor: index })),
|
|
||||||
}));
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { create } from 'zustand';
|
|
||||||
|
|
||||||
type EventEditorStore = {
|
|
||||||
openId: string | null;
|
|
||||||
setOpenEvent: (eventId: string) => void;
|
|
||||||
removeOpenEvent: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useEventEditorStore = create<EventEditorStore>()((set) => ({
|
|
||||||
openId: null,
|
|
||||||
setOpenEvent: (eventId: string | null) => set({ openId: eventId }),
|
|
||||||
removeOpenEvent: () => set({ openId: null }),
|
|
||||||
}));
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { formatEventList, getEventsWithDelay, trimEventlist } from '../eventsManager';
|
import { formatEventList, getEventsWithDelay, trimRundown } from '../eventsManager';
|
||||||
|
|
||||||
describe('getEventsWithDelay function', () => {
|
describe('getEventsWithDelay function', () => {
|
||||||
test('with positive delays', () => {
|
test('with positive delays', () => {
|
||||||
@@ -365,7 +365,7 @@ describe('test trimEventlist function', () => {
|
|||||||
{ id: '8' },
|
{ id: '8' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const l = trimEventlist(testData, selectedId, limit);
|
const l = trimRundown(testData, selectedId, limit);
|
||||||
expect(l.length).toBe(limit);
|
expect(l.length).toBe(limit);
|
||||||
expect(l).toStrictEqual(expected);
|
expect(l).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
@@ -383,7 +383,7 @@ describe('test trimEventlist function', () => {
|
|||||||
{ id: '8' },
|
{ id: '8' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const l = trimEventlist(testData, selectedId, limit);
|
const l = trimRundown(testData, selectedId, limit);
|
||||||
expect(l.length).toBe(limit);
|
expect(l.length).toBe(limit);
|
||||||
expect(l).toStrictEqual(expected);
|
expect(l).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
@@ -401,7 +401,7 @@ describe('test trimEventlist function', () => {
|
|||||||
{ id: '9' },
|
{ id: '9' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const l = trimEventlist(testData, selectedId, limit);
|
const l = trimRundown(testData, selectedId, limit);
|
||||||
expect(l.length).toBe(limit);
|
expect(l.length).toBe(limit);
|
||||||
expect(l).toStrictEqual(expected);
|
expect(l).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
@@ -419,7 +419,7 @@ describe('test trimEventlist function', () => {
|
|||||||
{ id: '8' },
|
{ id: '8' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const l = trimEventlist(testData, selectedId, limit);
|
const l = trimRundown(testData, selectedId, limit);
|
||||||
expect(l.length).toBe(limit);
|
expect(l.length).toBe(limit);
|
||||||
expect(l).toStrictEqual(expected);
|
expect(l).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ import { formatTime } from './time';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @description From a list of events, returns only events of type event with calculated delays
|
* @description From a list of events, returns only events of type event with calculated delays
|
||||||
* @param {Object[]} events - given events
|
* @param {Object[]} rundown - given rundown
|
||||||
* @returns {Object[]} Filtered events with calculated delays
|
* @returns {Object[]} Filtered events with calculated delays
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const getEventsWithDelay = (events: OntimeRundownEntry[]): OntimeEvent[] => {
|
export const getEventsWithDelay = (rundown: OntimeRundownEntry[]): OntimeEvent[] => {
|
||||||
if (events == null) return [];
|
if (rundown == null) return [];
|
||||||
|
|
||||||
const delayedEvents: OntimeEvent[] = [];
|
const delayedEvents: OntimeEvent[] = [];
|
||||||
|
|
||||||
// Add running delay
|
// Add running delay
|
||||||
let delay = 0;
|
let delay = 0;
|
||||||
for (const event of events) {
|
for (const event of rundown) {
|
||||||
if (event.type === SupportedEvent.Block) delay = 0;
|
if (event.type === SupportedEvent.Block) delay = 0;
|
||||||
else if (event.type === SupportedEvent.Delay) {
|
else if (event.type === SupportedEvent.Delay) {
|
||||||
if (typeof event.duration === 'number') {
|
if (typeof event.duration === 'number') {
|
||||||
@@ -36,29 +36,29 @@ export const getEventsWithDelay = (events: OntimeRundownEntry[]): OntimeEvent[]
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Returns trimmed event list array
|
* @description Returns trimmed event list array
|
||||||
* @param {Object[]} events - given events
|
* @param {Object[]} rundown - given rundown
|
||||||
* @param {string} selectedId - id of currently selected event
|
* @param {string} selectedId - id of currently selected event
|
||||||
* @param {number} limit - max number of events to return
|
* @param {number} limit - max number of events to return
|
||||||
* @returns {Object[]} Event list with maximum <limit> objects
|
* @returns {Object[]} Event list with maximum <limit> objects
|
||||||
*/
|
*/
|
||||||
export const trimEventlist = (events: OntimeRundownEntry[], selectedId: string, limit: number) => {
|
export const trimRundown = (rundown: OntimeRundownEntry[], selectedId: string, limit: number) => {
|
||||||
if (events == null) return [];
|
if (rundown == null) return [];
|
||||||
|
|
||||||
const BEFORE = 2;
|
const BEFORE = 2;
|
||||||
const trimmedEvents = [...events];
|
const trimmedRundown = [...rundown];
|
||||||
|
|
||||||
// limit events length if necessary
|
// limit events length if necessary
|
||||||
if (limit != null) {
|
if (limit != null) {
|
||||||
while (trimmedEvents.length > limit) {
|
while (trimmedRundown.length > limit) {
|
||||||
const idx = trimmedEvents.findIndex((e) => e.id === selectedId);
|
const idx = trimmedRundown.findIndex((e) => e.id === selectedId);
|
||||||
if (idx <= BEFORE) {
|
if (idx <= BEFORE) {
|
||||||
trimmedEvents.pop();
|
trimmedRundown.pop();
|
||||||
} else {
|
} else {
|
||||||
trimmedEvents.shift();
|
trimmedRundown.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return trimmedEvents;
|
return trimmedRundown;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormatEventListOptionsProp = {
|
type FormatEventListOptionsProp = {
|
||||||
@@ -66,7 +66,7 @@ type FormatEventListOptionsProp = {
|
|||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* @description Returns list of events formatted to be displayed
|
* @description Returns list of events formatted to be displayed
|
||||||
* @param {Object[]} events - given events
|
* @param {Object[]} rundown - given rundown
|
||||||
* @param {string} selectedId - id of currently selected event
|
* @param {string} selectedId - id of currently selected event
|
||||||
* @param {string} nextId - id of next event
|
* @param {string} nextId - id of next event
|
||||||
* @param {object} [options]
|
* @param {object} [options]
|
||||||
@@ -74,15 +74,15 @@ type FormatEventListOptionsProp = {
|
|||||||
* @returns {Object[]} Formatted list of events [{time: -, title: -, isNow, isNext}]
|
* @returns {Object[]} Formatted list of events [{time: -, title: -, isNow, isNext}]
|
||||||
*/
|
*/
|
||||||
export const formatEventList = (
|
export const formatEventList = (
|
||||||
events: OntimeEvent[],
|
rundown: OntimeEvent[],
|
||||||
selectedId: string,
|
selectedId: string,
|
||||||
nextId: string,
|
nextId: string,
|
||||||
options: FormatEventListOptionsProp,
|
options: FormatEventListOptionsProp,
|
||||||
) => {
|
) => {
|
||||||
if (events == null) return [];
|
if (rundown == null) return [];
|
||||||
const { showEnd = false } = options;
|
const { showEnd = false } = options;
|
||||||
|
|
||||||
const givenEvents = [...events];
|
const givenEvents = [...rundown];
|
||||||
|
|
||||||
// format list
|
// format list
|
||||||
const formattedEvents = [];
|
const formattedEvents = [];
|
||||||
@@ -124,3 +124,42 @@ export const cloneEvent = (event: OntimeEvent, after?: string): ClonedEvent => {
|
|||||||
after: after,
|
after: after,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets first event in rundown, if it exists
|
||||||
|
* @param {OntimeRundownEntry[]} rundown
|
||||||
|
* @return {OntimeEvent | null}
|
||||||
|
*/
|
||||||
|
export function getFirstEvent(rundown: OntimeRundownEntry[]) {
|
||||||
|
return rundown.length ? rundown[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets next event in rundown, if it exists
|
||||||
|
* @param {OntimeRundownEntry[]} rundown
|
||||||
|
* @param {string} currentId
|
||||||
|
* @return {OntimeEvent | null}
|
||||||
|
*/
|
||||||
|
export function getNextEvent(rundown: OntimeRundownEntry[], currentId: string) {
|
||||||
|
const index = rundown.findIndex((event) => event.id === currentId);
|
||||||
|
if (index !== -1 && index + 1 < rundown.length) {
|
||||||
|
return rundown[index + 1];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets previous event in rundown, if it exists
|
||||||
|
* @param {OntimeRundownEntry[]} rundown
|
||||||
|
* @param {string} currentId
|
||||||
|
* @return {OntimeEvent | null}
|
||||||
|
*/
|
||||||
|
export function getPreviousEvent(rundown: OntimeRundownEntry[], currentId: string) {
|
||||||
|
const index = rundown.findIndex((event) => event.id === currentId);
|
||||||
|
if (index !== -1 && index - 1 >= 0) {
|
||||||
|
return rundown[index - 1];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { OntimeEvent } from 'ontime-types';
|
|||||||
|
|
||||||
import CopyTag from '../../common/components/copy-tag/CopyTag';
|
import CopyTag from '../../common/components/copy-tag/CopyTag';
|
||||||
import useRundown from '../../common/hooks-query/useRundown';
|
import useRundown from '../../common/hooks-query/useRundown';
|
||||||
import { useEventEditorStore } from '../../common/stores/eventEditor';
|
import { useAppMode } from '../../common/stores/appModeStore';
|
||||||
import getDelayTo from '../../common/utils/getDelayTo';
|
import getDelayTo from '../../common/utils/getDelayTo';
|
||||||
|
|
||||||
import EventEditorTimes from './composite/EventEditorTimes';
|
import EventEditorTimes from './composite/EventEditorTimes';
|
||||||
@@ -14,7 +14,7 @@ import style from './EventEditor.module.scss';
|
|||||||
export type EventEditorSubmitActions = keyof OntimeEvent;
|
export type EventEditorSubmitActions = keyof OntimeEvent;
|
||||||
|
|
||||||
export default function EventEditor() {
|
export default function EventEditor() {
|
||||||
const { openId } = useEventEditorStore();
|
const openId = useAppMode((state) => state.editId);
|
||||||
const { data } = useRundown();
|
const { data } = useRundown();
|
||||||
const [event, setEvent] = useState<OntimeEvent | null>(null);
|
const [event, setEvent] = useState<OntimeEvent | null>(null);
|
||||||
const [delay, setDelay] = useState(0);
|
const [delay, setDelay] = useState(0);
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { Box, IconButton } from '@chakra-ui/react';
|
|||||||
import { FiX } from '@react-icons/all-files/fi/FiX';
|
import { FiX } from '@react-icons/all-files/fi/FiX';
|
||||||
|
|
||||||
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
|
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
|
||||||
import { useEventEditorStore } from '../../common/stores/eventEditor';
|
import { AppMode, useAppMode } from '../../common/stores/appModeStore';
|
||||||
|
import { cx } from '../../common/utils/styleUtils';
|
||||||
|
|
||||||
import EventEditor from './EventEditor';
|
import EventEditor from './EventEditor';
|
||||||
|
|
||||||
@@ -18,15 +19,27 @@ const closeBtnStyle = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const EventEditorExport = () => {
|
const EventEditorExport = () => {
|
||||||
const { openId, removeOpenEvent } = useEventEditorStore();
|
const appMode = useAppMode((state) => state.mode);
|
||||||
|
const editId = useAppMode((state) => state.editId);
|
||||||
|
const setEditId = useAppMode((state) => state.setEditId);
|
||||||
|
|
||||||
|
const editorStyle = cx([style.eventEditor, !editId ? style.noEvent : null]);
|
||||||
|
const removeOpenEvent = () => setEditId(null);
|
||||||
|
const canRemoveOpenId = appMode === AppMode.Run;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={`${style.eventEditor} ${!openId ? style.noEvent : ''}`}>
|
<Box className={editorStyle}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<div className={style.eventEditorLayout}>
|
<div className={style.eventEditorLayout}>
|
||||||
<EventEditor />
|
<EventEditor />
|
||||||
<div className={style.header}>
|
<div className={style.header}>
|
||||||
<IconButton aria-label='Close Menu' icon={<FiX />} onClick={removeOpenEvent} {...closeBtnStyle} />
|
<IconButton
|
||||||
|
aria-label='Close Menu'
|
||||||
|
icon={<FiX />}
|
||||||
|
onClick={removeOpenEvent}
|
||||||
|
isDisabled={!canRemoveOpenId}
|
||||||
|
{...closeBtnStyle}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { VStack } from '@chakra-ui/react';
|
import { VStack } from '@chakra-ui/react';
|
||||||
import { FiHelpCircle } from '@react-icons/all-files/fi/FiHelpCircle';
|
|
||||||
import { FiMinimize } from '@react-icons/all-files/fi/FiMinimize';
|
|
||||||
import { FiSave } from '@react-icons/all-files/fi/FiSave';
|
import { FiSave } from '@react-icons/all-files/fi/FiSave';
|
||||||
import { FiUpload } from '@react-icons/all-files/fi/FiUpload';
|
import { FiUpload } from '@react-icons/all-files/fi/FiUpload';
|
||||||
import { IoExtensionPuzzle } from '@react-icons/all-files/io5/IoExtensionPuzzle';
|
import { IoExtensionPuzzle } from '@react-icons/all-files/io5/IoExtensionPuzzle';
|
||||||
import { IoExtensionPuzzleOutline } from '@react-icons/all-files/io5/IoExtensionPuzzleOutline';
|
import { IoExtensionPuzzleOutline } from '@react-icons/all-files/io5/IoExtensionPuzzleOutline';
|
||||||
import { IoScan } from '@react-icons/all-files/io5/IoScan';
|
import { IoHelpCircleOutline } from '@react-icons/all-files/io5/IoHelpCircleOutline';
|
||||||
|
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
||||||
|
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||||
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
||||||
|
|
||||||
import { downloadRundown } from '../../common/api/ontimeApi';
|
import { downloadRundown } from '../../common/api/ontimeApi';
|
||||||
import QuitIconBtn from '../../common/components/buttons/QuitIconBtn';
|
import QuitIconBtn from '../../common/components/buttons/QuitIconBtn';
|
||||||
import TooltipActionBtn from '../../common/components/buttons/TooltipActionBtn';
|
import TooltipActionBtn from '../../common/components/buttons/TooltipActionBtn';
|
||||||
import useElectronEvent from '../../common/hooks/useElectronEvent';
|
import useElectronEvent from '../../common/hooks/useElectronEvent';
|
||||||
|
import { AppMode, useAppMode } from '../../common/stores/appModeStore';
|
||||||
|
|
||||||
import style from './MenuBar.module.scss';
|
import style from './MenuBar.module.scss';
|
||||||
|
|
||||||
@@ -52,21 +53,20 @@ export default function MenuBar(props: MenuBarProps) {
|
|||||||
} = props;
|
} = props;
|
||||||
const { isElectron, sendToElectron } = useElectronEvent();
|
const { isElectron, sendToElectron } = useElectronEvent();
|
||||||
|
|
||||||
|
const appMode = useAppMode((state) => state.mode);
|
||||||
|
const setAppMode = useAppMode((state) => state.setMode);
|
||||||
|
|
||||||
|
const setRunMode = () => setAppMode(AppMode.Run);
|
||||||
|
const setEditMode = () => setAppMode(AppMode.Edit);
|
||||||
|
|
||||||
const actionHandler = useCallback(
|
const actionHandler = useCallback(
|
||||||
(action: Actions) => {
|
(action: Actions) => {
|
||||||
// Stop crashes when testing locally
|
|
||||||
if (!isElectron) {
|
if (!isElectron) {
|
||||||
if (action === 'help') {
|
if (action === 'help') {
|
||||||
window.open('https://cpvalente.gitbook.io/ontime/');
|
window.open('https://cpvalente.gitbook.io/ontime/');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'min':
|
|
||||||
sendToElectron('set-window', 'to-tray');
|
|
||||||
break;
|
|
||||||
case 'max':
|
|
||||||
sendToElectron('set-window', 'to-max');
|
|
||||||
break;
|
|
||||||
case 'shutdown':
|
case 'shutdown':
|
||||||
sendToElectron('shutdown', 'now');
|
sendToElectron('shutdown', 'now');
|
||||||
break;
|
break;
|
||||||
@@ -113,22 +113,7 @@ export default function MenuBar(props: MenuBarProps) {
|
|||||||
return (
|
return (
|
||||||
<VStack>
|
<VStack>
|
||||||
<QuitIconBtn clickHandler={() => actionHandler('shutdown')} />
|
<QuitIconBtn clickHandler={() => actionHandler('shutdown')} />
|
||||||
<TooltipActionBtn
|
|
||||||
{...buttonStyle}
|
|
||||||
icon={<IoScan />}
|
|
||||||
clickHandler={() => actionHandler('max')}
|
|
||||||
tooltip='Show full window'
|
|
||||||
aria-label='Show full window'
|
|
||||||
isDisabled={!isElectron}
|
|
||||||
/>
|
|
||||||
<TooltipActionBtn
|
|
||||||
{...buttonStyle}
|
|
||||||
icon={<FiMinimize />}
|
|
||||||
clickHandler={() => actionHandler('min')}
|
|
||||||
tooltip='Minimise to tray'
|
|
||||||
aria-label='Minimise to tray'
|
|
||||||
isDisabled={!isElectron}
|
|
||||||
/>
|
|
||||||
<div className={style.gap} />
|
<div className={style.gap} />
|
||||||
<TooltipActionBtn
|
<TooltipActionBtn
|
||||||
{...buttonStyle}
|
{...buttonStyle}
|
||||||
@@ -145,6 +130,25 @@ export default function MenuBar(props: MenuBarProps) {
|
|||||||
tooltip='Export showfile'
|
tooltip='Export showfile'
|
||||||
aria-label='Export showfile'
|
aria-label='Export showfile'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className={style.gap} />
|
||||||
|
<TooltipActionBtn
|
||||||
|
{...buttonStyle}
|
||||||
|
icon={<IoPlay />}
|
||||||
|
className={appMode === AppMode.Run ? style.open : ''}
|
||||||
|
clickHandler={setRunMode}
|
||||||
|
tooltip='Run mode'
|
||||||
|
aria-label='Run mode'
|
||||||
|
/>
|
||||||
|
<TooltipActionBtn
|
||||||
|
{...buttonStyle}
|
||||||
|
icon={<IoOptions />}
|
||||||
|
className={appMode === AppMode.Edit ? style.open : ''}
|
||||||
|
clickHandler={setEditMode}
|
||||||
|
tooltip='Edit mode'
|
||||||
|
aria-label='Edit mode'
|
||||||
|
/>
|
||||||
|
|
||||||
<div className={style.gap} />
|
<div className={style.gap} />
|
||||||
<TooltipActionBtn
|
<TooltipActionBtn
|
||||||
{...buttonStyle}
|
{...buttonStyle}
|
||||||
@@ -165,7 +169,7 @@ export default function MenuBar(props: MenuBarProps) {
|
|||||||
<div className={style.gap} />
|
<div className={style.gap} />
|
||||||
<TooltipActionBtn
|
<TooltipActionBtn
|
||||||
{...buttonStyle}
|
{...buttonStyle}
|
||||||
icon={<FiHelpCircle />}
|
icon={<IoHelpCircleOutline />}
|
||||||
clickHandler={() => actionHandler('help')}
|
clickHandler={() => actionHandler('help')}
|
||||||
tooltip='Help'
|
tooltip='Help'
|
||||||
aria-label='Help'
|
aria-label='Help'
|
||||||
|
|||||||
@@ -1,15 +1,4 @@
|
|||||||
@use '../../theme/v2Styles' as *;
|
|
||||||
@use '../../theme/ontimeColours' as *;
|
|
||||||
|
|
||||||
.headerButtons {
|
.headerButtons {
|
||||||
align-content: center;
|
text-align: right;
|
||||||
justify-content: space-between;
|
|
||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.labelledSwitch {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $element-spacing;
|
|
||||||
color: $gray-100;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { Button, HStack, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Switch } from '@chakra-ui/react';
|
import { Button, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@chakra-ui/react';
|
||||||
import { FiMinusCircle } from '@react-icons/all-files/fi/FiMinusCircle';
|
import { FiMinusCircle } from '@react-icons/all-files/fi/FiMinusCircle';
|
||||||
import { FiTrash2 } from '@react-icons/all-files/fi/FiTrash2';
|
import { FiTrash2 } from '@react-icons/all-files/fi/FiTrash2';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||||
@@ -7,15 +7,13 @@ import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline';
|
|||||||
import { SupportedEvent } from 'ontime-types';
|
import { SupportedEvent } from 'ontime-types';
|
||||||
|
|
||||||
import { useEventAction } from '../../common/hooks/useEventAction';
|
import { useEventAction } from '../../common/hooks/useEventAction';
|
||||||
import { useCursor } from '../../common/stores/cursorStore';
|
import { useAppMode } from '../../common/stores/appModeStore';
|
||||||
import { useEventEditorStore } from '../../common/stores/eventEditor';
|
|
||||||
|
|
||||||
import style from './RundownMenu.module.scss';
|
import style from './RundownMenu.module.scss';
|
||||||
|
|
||||||
const RundownMenu = () => {
|
const RundownMenu = () => {
|
||||||
const isCursorLocked = useCursor((state) => state.isCursorLocked);
|
const setEditId = useAppMode((state) => state.setEditId);
|
||||||
const toggleCursorLocked = useCursor((state) => state.toggleCursorLocked);
|
const setCursor = useAppMode((state) => state.setCursor);
|
||||||
const removeOpenEvent = useEventEditorStore((state) => state.removeOpenEvent);
|
|
||||||
|
|
||||||
const { addEvent, deleteAllEvents } = useEventAction();
|
const { addEvent, deleteAllEvents } = useEventAction();
|
||||||
|
|
||||||
@@ -33,19 +31,12 @@ const RundownMenu = () => {
|
|||||||
|
|
||||||
const deleteAll = useCallback(() => {
|
const deleteAll = useCallback(() => {
|
||||||
deleteAllEvents();
|
deleteAllEvents();
|
||||||
removeOpenEvent();
|
setEditId(null);
|
||||||
}, [deleteAllEvents, removeOpenEvent]);
|
setCursor(null);
|
||||||
|
}, [deleteAllEvents, setCursor, setEditId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack className={style.headerButtons}>
|
<div className={style.headerButtons}>
|
||||||
<label className={style.labelledSwitch}>
|
|
||||||
<Switch
|
|
||||||
defaultChecked={isCursorLocked}
|
|
||||||
onChange={(event) => toggleCursorLocked(event.target.checked)}
|
|
||||||
variant='ontime'
|
|
||||||
/>
|
|
||||||
Follow loaded event
|
|
||||||
</label>
|
|
||||||
<Menu isLazy lazyBehavior='unmount' variant='ontime-on-dark'>
|
<Menu isLazy lazyBehavior='unmount' variant='ontime-on-dark'>
|
||||||
<MenuButton as={Button} leftIcon={<IoAdd />} size='sm' variant='ontime-subtle'>
|
<MenuButton as={Button} leftIcon={<IoAdd />} size='sm' variant='ontime-subtle'>
|
||||||
Event...
|
Event...
|
||||||
@@ -66,7 +57,7 @@ const RundownMenu = () => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
</HStack>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { OntimeRundown, Playback, SupportedEvent } from 'ontime-types';
|
|||||||
|
|
||||||
import { useEventAction } from '../../common/hooks/useEventAction';
|
import { useEventAction } from '../../common/hooks/useEventAction';
|
||||||
import { useRundownEditor } from '../../common/hooks/useSocket';
|
import { useRundownEditor } from '../../common/hooks/useSocket';
|
||||||
import { useCursor } from '../../common/stores/cursorStore';
|
import { AppMode, useAppMode } from '../../common/stores/appModeStore';
|
||||||
import { useLocalEvent } from '../../common/stores/localEvent';
|
import { useLocalEvent } from '../../common/stores/localEvent';
|
||||||
import { cloneEvent } from '../../common/utils/eventsManager';
|
import { cloneEvent, getFirstEvent, getNextEvent, getPreviousEvent } from '../../common/utils/eventsManager';
|
||||||
|
|
||||||
import QuickAddBlock from './quick-add-block/QuickAddBlock';
|
import QuickAddBlock from './quick-add-block/QuickAddBlock';
|
||||||
import RundownEmpty from './RundownEmpty';
|
import RundownEmpty from './RundownEmpty';
|
||||||
@@ -31,46 +31,47 @@ export default function Rundown(props: RundownProps) {
|
|||||||
const showQuickEntry = eventSettings.showQuickEntry;
|
const showQuickEntry = eventSettings.showQuickEntry;
|
||||||
|
|
||||||
// cursor
|
// cursor
|
||||||
const cursor = useCursor((state) => state.cursor);
|
const cursor = useAppMode((state) => state.cursor);
|
||||||
const isCursorLocked = useCursor((state) => state.isCursorLocked);
|
const appMode = useAppMode((state) => state.mode);
|
||||||
const moveCursorTo = useCursor((state) => state.moveCursorTo);
|
const viewFollowsCursor = appMode === AppMode.Run;
|
||||||
|
const moveCursorTo = useAppMode((state) => state.setCursor);
|
||||||
const cursorRef = useRef<HTMLDivElement>();
|
const cursorRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
// DND KIT
|
// DND KIT
|
||||||
const sensors = useSensors(useSensor(PointerSensor));
|
const sensors = useSensors(useSensor(PointerSensor));
|
||||||
|
|
||||||
const insertAtCursor = useCallback(
|
const insertAtCursor = useCallback(
|
||||||
(type: SupportedEvent | 'clone', cursor: number) => {
|
(type: SupportedEvent | 'clone', cursor: string | null) => {
|
||||||
if (cursor === -1) {
|
if (cursor === null) {
|
||||||
|
// we cant clone without selection
|
||||||
if (type === 'clone') {
|
if (type === 'clone') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// the only thing to do is adding an event at top
|
||||||
addEvent({ type });
|
addEvent({ type });
|
||||||
} else {
|
return;
|
||||||
const previousEvent = entries?.[cursor];
|
}
|
||||||
const nextEvent = entries?.[cursor + 1];
|
|
||||||
|
|
||||||
// prevent adding two non-event blocks consecutively
|
if (type === 'clone') {
|
||||||
const isPreviousDifferent = previousEvent?.type !== type;
|
const cursorEvent = entries.find((event) => event.id === cursor);
|
||||||
const isNextDifferent = nextEvent?.type !== type;
|
if (cursorEvent?.type === SupportedEvent.Event) {
|
||||||
if (type === 'clone' && previousEvent?.type === SupportedEvent.Event) {
|
const newEvent = cloneEvent(cursorEvent);
|
||||||
const newEvent = cloneEvent(previousEvent);
|
newEvent.after = cursorEvent.id;
|
||||||
newEvent.after = previousEvent.id;
|
|
||||||
addEvent(newEvent);
|
addEvent(newEvent);
|
||||||
} else if (type === SupportedEvent.Event) {
|
|
||||||
const newEvent = {
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
};
|
|
||||||
const options = {
|
|
||||||
defaultPublic: defaultPublic,
|
|
||||||
startTimeIsLastEnd: startTimeIsLastEnd,
|
|
||||||
lastEventId: previousEvent.id,
|
|
||||||
after: previousEvent.id,
|
|
||||||
};
|
|
||||||
addEvent(newEvent, options);
|
|
||||||
} else if (isPreviousDifferent && isNextDifferent && type !== 'clone') {
|
|
||||||
addEvent({ type }, { after: previousEvent.id });
|
|
||||||
}
|
}
|
||||||
|
} else if (type === SupportedEvent.Event) {
|
||||||
|
const newEvent = {
|
||||||
|
type: SupportedEvent.Event,
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
defaultPublic: defaultPublic,
|
||||||
|
startTimeIsLastEnd: startTimeIsLastEnd,
|
||||||
|
lastEventId: cursor,
|
||||||
|
after: cursor,
|
||||||
|
};
|
||||||
|
addEvent(newEvent, options);
|
||||||
|
} else {
|
||||||
|
addEvent({ type }, { after: cursor });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[addEvent, defaultPublic, entries, startTimeIsLastEnd],
|
[addEvent, defaultPublic, entries, startTimeIsLastEnd],
|
||||||
@@ -85,41 +86,50 @@ export default function Rundown(props: RundownProps) {
|
|||||||
if (event.altKey && (!event.ctrlKey || !event.shiftKey)) {
|
if (event.altKey && (!event.ctrlKey || !event.shiftKey)) {
|
||||||
switch (event.code) {
|
switch (event.code) {
|
||||||
case 'ArrowDown': {
|
case 'ArrowDown': {
|
||||||
if (cursor < entries.length - 1) moveCursorTo(cursor + 1);
|
if (entries.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nextEvent = cursor == null ? getFirstEvent(entries) : getNextEvent(entries, cursor);
|
||||||
|
if (nextEvent) {
|
||||||
|
moveCursorTo(nextEvent.id, nextEvent.type === SupportedEvent.Event);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowUp': {
|
case 'ArrowUp': {
|
||||||
if (cursor > 0) moveCursorTo(cursor - 1);
|
if (entries.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we check for this before
|
||||||
|
const previousEvent = cursor == null ? getFirstEvent(entries) : getPreviousEvent(entries, cursor);
|
||||||
|
if (previousEvent) {
|
||||||
|
moveCursorTo(previousEvent.id, previousEvent.type === SupportedEvent.Event);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'KeyE': {
|
case 'KeyE': {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (cursor === -1) return;
|
|
||||||
insertAtCursor(SupportedEvent.Event, cursor);
|
insertAtCursor(SupportedEvent.Event, cursor);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'KeyD': {
|
case 'KeyD': {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (cursor < 0) return;
|
|
||||||
insertAtCursor(SupportedEvent.Delay, cursor);
|
insertAtCursor(SupportedEvent.Delay, cursor);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'KeyB': {
|
case 'KeyB': {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (cursor < 0) return;
|
|
||||||
insertAtCursor(SupportedEvent.Block, cursor);
|
insertAtCursor(SupportedEvent.Block, cursor);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'KeyC': {
|
case 'KeyC': {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (cursor < 0) return;
|
|
||||||
insertAtCursor('clone', cursor);
|
insertAtCursor('clone', cursor);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[cursor, entries.length, insertAtCursor, moveCursorTo],
|
[cursor, entries, insertAtCursor, moveCursorTo],
|
||||||
);
|
);
|
||||||
|
|
||||||
// we copy the state from the store here
|
// we copy the state from the store here
|
||||||
@@ -155,28 +165,13 @@ export default function Rundown(props: RundownProps) {
|
|||||||
});
|
});
|
||||||
}, [cursorRef]);
|
}, [cursorRef]);
|
||||||
|
|
||||||
// if selected event
|
|
||||||
// or cursor settings changed
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// and if we are locked
|
// in run mode, we follow selection
|
||||||
if (!isCursorLocked || !featureData?.selectedEventId) {
|
if (!viewFollowsCursor || !featureData?.selectedEventId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
moveCursorTo(featureData.selectedEventId);
|
||||||
// move cursor
|
}, [featureData?.selectedEventId, viewFollowsCursor, moveCursorTo]);
|
||||||
let gotoIndex = -1;
|
|
||||||
let found = false;
|
|
||||||
for (const entry of entries) {
|
|
||||||
gotoIndex++;
|
|
||||||
if (entry.id === featureData.selectedEventId) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (found) {
|
|
||||||
moveCursorTo(gotoIndex);
|
|
||||||
}
|
|
||||||
}, [featureData?.selectedEventId, entries, isCursorLocked, moveCursorTo]);
|
|
||||||
|
|
||||||
const handleOnDragEnd = (event: DragEndEvent) => {
|
const handleOnDragEnd = (event: DragEndEvent) => {
|
||||||
const { active, over } = event;
|
const { active, over } = event;
|
||||||
@@ -195,7 +190,7 @@ export default function Rundown(props: RundownProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (statefulEntries?.length < 1) {
|
if (statefulEntries?.length < 1) {
|
||||||
return <RundownEmpty handleAddNew={() => insertAtCursor(SupportedEvent.Event, -1)} />;
|
return <RundownEmpty handleAddNew={() => insertAtCursor(SupportedEvent.Event, null)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cumulativeDelay = 0;
|
let cumulativeDelay = 0;
|
||||||
@@ -227,16 +222,16 @@ export default function Rundown(props: RundownProps) {
|
|||||||
const isLast = index === entries.length - 1;
|
const isLast = index === entries.length - 1;
|
||||||
const isSelected = featureData?.selectedEventId === entry.id;
|
const isSelected = featureData?.selectedEventId === entry.id;
|
||||||
const isNext = featureData?.nextEventId === entry.id;
|
const isNext = featureData?.nextEventId === entry.id;
|
||||||
|
const hasCursor = entry.id === cursor;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={entry.id} ref={cursor === index ? cursorRef : undefined}>
|
<div key={entry.id} ref={hasCursor ? cursorRef : undefined}>
|
||||||
<RundownEntry
|
<RundownEntry
|
||||||
type={entry.type}
|
type={entry.type}
|
||||||
index={index}
|
|
||||||
eventIndex={eventIndex}
|
eventIndex={eventIndex}
|
||||||
data={entry}
|
data={entry}
|
||||||
selected={isSelected}
|
selected={isSelected}
|
||||||
hasCursor={cursor === index}
|
hasCursor={hasCursor}
|
||||||
next={isNext}
|
next={isNext}
|
||||||
delay={cumulativeDelay}
|
delay={cumulativeDelay}
|
||||||
previousEnd={previousEnd}
|
previousEnd={previousEnd}
|
||||||
@@ -244,13 +239,13 @@ export default function Rundown(props: RundownProps) {
|
|||||||
playback={isSelected ? featureData.playback : undefined}
|
playback={isSelected ? featureData.playback : undefined}
|
||||||
isRolling={featureData.playback === Playback.Roll}
|
isRolling={featureData.playback === Playback.Roll}
|
||||||
/>
|
/>
|
||||||
{((showQuickEntry && index === cursor) || isLast) && (
|
{((showQuickEntry && hasCursor) || isLast) && (
|
||||||
<QuickAddBlock
|
<QuickAddBlock
|
||||||
showKbd={index === cursor}
|
showKbd={hasCursor}
|
||||||
eventId={entry.id}
|
eventId={entry.id}
|
||||||
previousEventId={previousEventId}
|
previousEventId={previousEventId}
|
||||||
disableAddDelay={entry.type === 'delay'}
|
disableAddDelay={entry.type === SupportedEvent.Delay}
|
||||||
disableAddBlock={entry.type === 'block'}
|
disableAddBlock={entry.type === SupportedEvent.Block}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useCallback } from 'react';
|
|||||||
import { OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent } from 'ontime-types';
|
import { OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent } from 'ontime-types';
|
||||||
|
|
||||||
import { useEventAction } from '../../common/hooks/useEventAction';
|
import { useEventAction } from '../../common/hooks/useEventAction';
|
||||||
import { useEventEditorStore } from '../../common/stores/eventEditor';
|
import { useAppMode } from '../../common/stores/appModeStore';
|
||||||
import { useLocalEvent } from '../../common/stores/localEvent';
|
import { useLocalEvent } from '../../common/stores/localEvent';
|
||||||
import { useEmitLog } from '../../common/stores/logger';
|
import { useEmitLog } from '../../common/stores/logger';
|
||||||
import { cloneEvent } from '../../common/utils/eventsManager';
|
import { cloneEvent } from '../../common/utils/eventsManager';
|
||||||
@@ -16,7 +16,6 @@ export type EventItemActions = 'set-cursor' | 'event' | 'delay' | 'block' | 'del
|
|||||||
|
|
||||||
interface RundownEntryProps {
|
interface RundownEntryProps {
|
||||||
type: SupportedEvent;
|
type: SupportedEvent;
|
||||||
index: number;
|
|
||||||
eventIndex: number;
|
eventIndex: number;
|
||||||
data: OntimeRundownEntry;
|
data: OntimeRundownEntry;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
@@ -30,24 +29,25 @@ interface RundownEntryProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function RundownEntry(props: RundownEntryProps) {
|
export default function RundownEntry(props: RundownEntryProps) {
|
||||||
const {
|
const { eventIndex, data, selected, hasCursor, next, delay, previousEnd, previousEventId, playback, isRolling } =
|
||||||
index,
|
props;
|
||||||
eventIndex,
|
|
||||||
data,
|
|
||||||
selected,
|
|
||||||
hasCursor,
|
|
||||||
next,
|
|
||||||
delay,
|
|
||||||
previousEnd,
|
|
||||||
previousEventId,
|
|
||||||
playback,
|
|
||||||
isRolling,
|
|
||||||
} = props;
|
|
||||||
const { emitError } = useEmitLog();
|
const { emitError } = useEmitLog();
|
||||||
const { addEvent, updateEvent, deleteEvent } = useEventAction();
|
const { addEvent, updateEvent, deleteEvent } = useEventAction();
|
||||||
|
|
||||||
const openId = useEventEditorStore((state) => state.openId);
|
const cursor = useAppMode((state) => state.cursor);
|
||||||
const removeOpenEvent = useEventEditorStore((state) => state.removeOpenEvent);
|
const setCursor = useAppMode((state) => state.setCursor);
|
||||||
|
const openId = useAppMode((state) => state.editId);
|
||||||
|
const setEditId = useAppMode((state) => state.setEditId);
|
||||||
|
|
||||||
|
const removeOpenEvent = useCallback(() => {
|
||||||
|
if (openId === data.id) {
|
||||||
|
setEditId(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor === data.id) {
|
||||||
|
setCursor(null);
|
||||||
|
}
|
||||||
|
}, [cursor, data.id, openId, setCursor, setEditId]);
|
||||||
|
|
||||||
const eventSettings = useLocalEvent((state) => state.eventSettings);
|
const eventSettings = useLocalEvent((state) => state.eventSettings);
|
||||||
const defaultPublic = eventSettings.defaultPublic;
|
const defaultPublic = eventSettings.defaultPublic;
|
||||||
@@ -146,7 +146,6 @@ export default function RundownEntry(props: RundownEntryProps) {
|
|||||||
timeStart={data.timeStart}
|
timeStart={data.timeStart}
|
||||||
timeEnd={data.timeEnd}
|
timeEnd={data.timeEnd}
|
||||||
duration={data.duration}
|
duration={data.duration}
|
||||||
index={index}
|
|
||||||
eventIndex={eventIndex + 1}
|
eventIndex={eventIndex + 1}
|
||||||
eventId={data.id}
|
eventId={data.id}
|
||||||
isPublic={data.isPublic}
|
isPublic={data.isPublic}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { CSS } from '@dnd-kit/utilities';
|
|||||||
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||||
import { EndAction, OntimeEvent, Playback, TimerType } from 'ontime-types';
|
import { EndAction, OntimeEvent, Playback, TimerType } from 'ontime-types';
|
||||||
|
|
||||||
import { useCursor } from '../../../common/stores/cursorStore';
|
import { useAppMode } from '../../../common/stores/appModeStore';
|
||||||
import { useEventEditorStore } from '../../../common/stores/eventEditor';
|
|
||||||
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
||||||
import { EventItemActions } from '../RundownEntry';
|
import { EventItemActions } from '../RundownEntry';
|
||||||
|
|
||||||
@@ -17,7 +16,6 @@ interface EventBlockProps {
|
|||||||
timeStart: number;
|
timeStart: number;
|
||||||
timeEnd: number;
|
timeEnd: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
index: number;
|
|
||||||
eventIndex: number;
|
eventIndex: number;
|
||||||
eventId: string;
|
eventId: string;
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
@@ -50,7 +48,6 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
timeStart,
|
timeStart,
|
||||||
timeEnd,
|
timeEnd,
|
||||||
duration,
|
duration,
|
||||||
index,
|
|
||||||
eventIndex,
|
eventIndex,
|
||||||
eventId,
|
eventId,
|
||||||
isPublic = true,
|
isPublic = true,
|
||||||
@@ -70,10 +67,10 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
actionHandler,
|
actionHandler,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const moveCursorTo = useCursor((state) => state.moveCursorTo);
|
const moveCursorTo = useAppMode((state) => state.setCursor);
|
||||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const openId = useEventEditorStore((state) => state.openId);
|
const openId = useAppMode((state) => state.editId);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isDragging,
|
isDragging,
|
||||||
@@ -136,7 +133,12 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={blockClasses} ref={setNodeRef} style={dragStyle}>
|
<div className={blockClasses} ref={setNodeRef} style={dragStyle}>
|
||||||
<div className={style.binder} style={{ ...binderColours }} tabIndex={-1} onClick={() => moveCursorTo(index)}>
|
<div
|
||||||
|
className={style.binder}
|
||||||
|
style={{ ...binderColours }}
|
||||||
|
tabIndex={-1}
|
||||||
|
onClick={() => moveCursorTo(eventId, true)}
|
||||||
|
>
|
||||||
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
|
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
|
||||||
<IoReorderTwo />
|
<IoReorderTwo />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { IoTime } from '@react-icons/all-files/io5/IoTime';
|
|||||||
import { EndAction, Playback, TimerType } from 'ontime-types';
|
import { EndAction, Playback, TimerType } from 'ontime-types';
|
||||||
|
|
||||||
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||||
import { useEventEditorStore } from '../../../common/stores/eventEditor';
|
import { useAppMode } from '../../../common/stores/appModeStore';
|
||||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||||
import EditableBlockTitle from '../common/EditableBlockTitle';
|
import EditableBlockTitle from '../common/EditableBlockTitle';
|
||||||
import { EventItemActions } from '../RundownEntry';
|
import { EventItemActions } from '../RundownEntry';
|
||||||
@@ -76,8 +76,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [renderInner, setRenderInner] = useState(false);
|
const [renderInner, setRenderInner] = useState(false);
|
||||||
const setOpenEvent = useEventEditorStore((state) => state.setOpenEvent);
|
const setEditId = useAppMode((state) => state.setEditId);
|
||||||
const removeOpenEvent = useEventEditorStore((state) => state.removeOpenEvent);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRenderInner(true);
|
setRenderInner(true);
|
||||||
@@ -85,11 +84,11 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
|||||||
|
|
||||||
const toggleOpenEvent = useCallback(() => {
|
const toggleOpenEvent = useCallback(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
removeOpenEvent();
|
setEditId(null);
|
||||||
} else {
|
} else {
|
||||||
setOpenEvent(eventId);
|
setEditId(eventId);
|
||||||
}
|
}
|
||||||
}, [eventId, isOpen, removeOpenEvent, setOpenEvent]);
|
}, [eventId, isOpen, setEditId]);
|
||||||
|
|
||||||
const eventIsPlaying = playback === Playback.Play;
|
const eventIsPlaying = playback === Playback.Play;
|
||||||
const eventIsPaused = playback === Playback.Pause;
|
const eventIsPaused = playback === Playback.Pause;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import NavigationMenu from '../../../common/components/navigation-menu/Navigatio
|
|||||||
import useFitText from '../../../common/hooks/useFitText';
|
import useFitText from '../../../common/hooks/useFitText';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { formatDisplay } from '../../../common/utils/dateConfig';
|
import { formatDisplay } from '../../../common/utils/dateConfig';
|
||||||
import { formatEventList, getEventsWithDelay, trimEventlist } from '../../../common/utils/eventsManager';
|
import { formatEventList, getEventsWithDelay, trimRundown } from '../../../common/utils/eventsManager';
|
||||||
import { formatTime } from '../../../common/utils/time';
|
import { formatTime } from '../../../common/utils/time';
|
||||||
|
|
||||||
import './StudioClock.scss';
|
import './StudioClock.scss';
|
||||||
@@ -59,7 +59,7 @@ export default function StudioClock(props) {
|
|||||||
|
|
||||||
const delayed = getEventsWithDelay(backstageEvents);
|
const delayed = getEventsWithDelay(backstageEvents);
|
||||||
const events = delayed.filter((e) => e.type === 'event');
|
const events = delayed.filter((e) => e.type === 'event');
|
||||||
const trimmed = trimEventlist(events, selectedId, MAX_TITLES);
|
const trimmed = trimRundown(events, selectedId, MAX_TITLES);
|
||||||
const formatted = formatEventList(trimmed, selectedId, nextId, {
|
const formatted = formatEventList(trimmed, selectedId, nextId, {
|
||||||
showEnd: false,
|
showEnd: false,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user