Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,16 @@ Other useful commands

- __List running processes__ by running `docker ps`
- __Kill running process__ by running `docker kill <process-id>`

## CONTRIBUTION GUIDELINES

If you want to propose changes to the codebase, please reach out before opening a Pull Request.

For new PRs, please follow the following checklist:
* [ ] You have updated and ran unit locally and they are passing. Unit tests are generally created for all utility functions and business logic
* [ ] You have ran code formatting and linting in all your changes
* [ ] The branch is clean and the commits are meaningfully separated and contain descriptive messages
* [ ] The PR body contains description and motivation for the changes

After this checklist is complete, you can request a review from one of the maintainers to get feedback and approval on the changes. \
We will review as soon as possible
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@getontime/cli",
"version": "4.0.0-beta.2",
"version": "4.0.0-beta.3",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
Expand Down
2 changes: 1 addition & 1 deletion apps/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ontime-ui",
"version": "4.0.0-beta.2",
"version": "4.0.0-beta.3",
"private": true,
"type": "module",
"dependencies": {
Expand Down
3 changes: 1 addition & 2 deletions apps/client/src/common/api/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ export const VIEW_SETTINGS = ['viewSettings'];
export const CLIENT_LIST = ['clientList'];
export const REPORT = ['report'];
export const TRANSLATION = ['translation'];
export const CSS_OVERRIDE = ['cssOverride']

// API URLs
export const apiEntryUrl = `${serverURL}/data`;

const userAssetsPath = 'user';
const cssOverridePath = 'styles/override.css';
const customTranslationsPath = 'translations/translations.json';

export const overrideStylesURL = `${serverURL}/${userAssetsPath}/${cssOverridePath}`;
export const projectLogoPath = `${serverURL}/${userAssetsPath}/logo`;
export const customTranslationsURL = `${serverURL}/${userAssetsPath}/${customTranslationsPath}`;
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
}

.pin {
margin-top: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;

input {
font-size: 4rem;
Expand Down
75 changes: 16 additions & 59 deletions apps/client/src/common/hooks/useFollowComponent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { RefObject, useCallback, useEffect, useRef } from 'react';

import { useSelectedEventId } from './useSocket';
import { RefObject, useCallback, useEffect } from 'react';
import { MaybeString } from 'ontime-types';

function scrollToComponent<ComponentRef extends HTMLElement, ScrollRef extends HTMLElement>(
componentRef: RefObject<ComponentRef>,
Expand All @@ -18,37 +17,26 @@ function scrollToComponent<ComponentRef extends HTMLElement, ScrollRef extends H
scrollRef.current.scrollTo({ top, behavior: 'smooth' });
}

function snapToComponent<ComponentRef extends HTMLElement, ScrollRef extends HTMLElement>(
componentRef: RefObject<ComponentRef>,
scrollRef: RefObject<ScrollRef>,
topOffset: number,
) {
if (!componentRef.current || !scrollRef.current) {
return;
}

const componentRect = componentRef.current.getBoundingClientRect();
const scrollRect = scrollRef.current.getBoundingClientRect();
const top = componentRect.top - scrollRect.top + scrollRef.current.scrollTop - topOffset;

// maintain current x scroll position
scrollRef.current.scrollTo(scrollRef.current.scrollLeft, top);
}

interface UseFollowComponentProps {
followRef: RefObject<HTMLElement | null>;
scrollRef: RefObject<HTMLElement | null>;
doFollow: boolean;
topOffset?: number;
setScrollFlag?: (newValue: boolean) => void;
followTrigger?: MaybeString; // this would be an entry id or null
}

export default function useFollowComponent(props: UseFollowComponentProps) {
const { followRef, scrollRef, doFollow, topOffset = 100, setScrollFlag } = props;

// when cursor moves, view should follow
export default function useFollowComponent({
followRef,
scrollRef,
doFollow,
topOffset = 100,
setScrollFlag,
followTrigger,
}: UseFollowComponentProps) {
// when trigger moves, view should follow
useEffect(() => {
if (!doFollow) {
if (!doFollow || !followTrigger) {
return;
}

Expand All @@ -60,49 +48,18 @@ export default function useFollowComponent(props: UseFollowComponentProps) {
setScrollFlag?.(false);
});
}

// eslint-disable-next-line -- the prompt seems incorrect
}, [followRef?.current, scrollRef?.current]);
}, [followTrigger, doFollow, followRef, scrollRef, setScrollFlag, topOffset]);

const scrollToRefComponent = useCallback(
(componentRef = followRef, containerRef = scrollRef, offset = topOffset) => {
if (componentRef.current && containerRef.current) {
if (componentRef && containerRef) {
// @ts-expect-error -- we know this are not null
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
scrollToComponent(componentRef!, scrollRef!, offset);
scrollToComponent(componentRef!, containerRef!, offset);
}
},
[followRef, scrollRef, topOffset],
);

return scrollToRefComponent;
}

export function useFollowSelected(doFollow: boolean, topOffset = 100) {
const selectedEvenId = useSelectedEventId();

const selectedRef = useRef<HTMLTableRowElement>(null);
const scrollRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (!doFollow) {
return;
}

if (selectedEvenId && selectedRef.current && scrollRef.current) {
// Use requestAnimationFrame to ensure the component is fully loaded
window.requestAnimationFrame(() => {
snapToComponent(
{ current: selectedRef.current } as RefObject<HTMLElement>,
{ current: scrollRef.current } as RefObject<HTMLElement>,
topOffset,
);
});
}
}, [doFollow, selectedEvenId, topOffset]);

return {
selectedRef,
scrollRef,
};
}
81 changes: 31 additions & 50 deletions apps/client/src/common/hooks/useRuntimeStylesheet.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import { useEffect, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { MILLIS_PER_HOUR } from 'ontime-utils';

import { getCSSContents } from '../api/assets';
import { CSS_OVERRIDE } from '../api/constants';
import useViewSettings from '../hooks-query/useViewSettings';

const scriptTagId = 'ontime-override';

export const useRuntimeStylesheet = (pathToFile?: string): { shouldRender: boolean } => {
export const useRuntimeStylesheet = (): { shouldRender: boolean } => {
const [shouldRender, setShouldRender] = useState(false);
const { data } = useViewSettings();

const { data: cssData } = useQuery({
queryKey: CSS_OVERRIDE,
queryFn: getCSSContents,
enabled: data.overrideStyles,
placeholderData: (previousData, _previousQuery) => previousData,
staleTime: MILLIS_PER_HOUR,
});

/**
* When a view mounts or the stylesheet path changes we need to handle potentially loading a new stylesheet
Expand All @@ -12,64 +27,30 @@
* @returns { shouldRender: boolean } - after the stylesheet is handled and the clients are ready to render
*/
useEffect(() => {
if (!pathToFile) {
handleNoStylesheet();
return;
}
let styleSheet = document.getElementById(scriptTagId);

Check failure on line 30 in apps/client/src/common/hooks/useRuntimeStylesheet.ts

View workflow job for this annotation

GitHub Actions / unit-test

'styleSheet' is never reassigned. Use 'const' instead

// there is already a stylesheet loaded, nothing further to do
if (document.getElementById(scriptTagId)) {
if (!cssData || !data.overrideStyles) {
// No stylesheet was provided, remove any existing stylesheet
styleSheet?.remove();
setShouldRender(true);
return;
}

setShouldRender(false);

fetchStylesheetData(pathToFile)
.then((data: string | undefined) => {
if (!data) {
console.error('Error loading stylesheet: no data');
return;
}
return injectStylesheet(data);
})
.catch((error: unknown) => {
console.error(`Error loading stylesheet: ${error}`);
})
.finally(() => {
// schedule render for next tick
setTimeout(() => setShouldRender(true), 0);
});
// Ensure the stylesheet is given to the document head
// if (!styleSheet) {
// styleSheet = document.createElement('style');
// styleSheet.setAttribute('id', scriptTagId);
// document.head.append(styleSheet);
// }

/**
* No stylesheet was provided, remove any existing stylesheet
*/
function handleNoStylesheet() {
document.getElementById(scriptTagId)?.remove();
setShouldRender(true);
}

/**
* Get data from backend
*/
async function fetchStylesheetData(path: string) {
const response = await fetch(path);
if (response.ok) {
return response.text();
}
return undefined;
}
// // set style sheet content
// styleSheet.textContent = cssData;

/**
* Add a stylesheet with given content to the document head
*/
async function injectStylesheet(styleContent: string) {
const styleSheet = document.createElement('style');
styleSheet.setAttribute('id', scriptTagId);
styleSheet.innerHTML = styleContent;
document.head.append(styleSheet);
}
}, [pathToFile]);
// schedule render for next tick
setTimeout(() => setShouldRender(true), 0);
}, [cssData, data.overrideStyles]);

return { shouldRender };
};
1 change: 0 additions & 1 deletion apps/client/src/common/hooks/useSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const createSelector =
export const setClientRemote = {
setIdentify: (payload: { target: string; identify: boolean }) => sendSocket('client', payload),
setRedirect: (payload: { target: string; redirect: string }) => {
console.log('--- got', payload);
sendSocket('client', payload);
},
setClientName: (payload: { target: string; rename: string }) => sendSocket('client', payload),
Expand Down
19 changes: 0 additions & 19 deletions apps/client/src/common/models/View.types.ts

This file was deleted.

4 changes: 4 additions & 0 deletions apps/client/src/common/utils/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { isProduction, websocketUrl } from '../../externals';
import {
APP_SETTINGS,
CLIENT_LIST,
CSS_OVERRIDE,
CUSTOM_FIELDS,
PROJECT_DATA,
REPORT,
Expand Down Expand Up @@ -181,6 +182,9 @@ export const connectSocket = () => {
case RefetchKey.Settings:
ontimeQueryClient.invalidateQueries({ queryKey: APP_SETTINGS });
break;
case RefetchKey.CssOverride:
ontimeQueryClient.invalidateQueries({ queryKey: CSS_OVERRIDE });
break;
default: {
target satisfies never;
break;
Expand Down
3 changes: 2 additions & 1 deletion apps/client/src/features/operator/Operator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default function Operator() {
scrollRef,
doFollow: !lockAutoScroll,
topOffset: selectedOffset,
followTrigger: selectedEventId,
});

useWindowTitle('Operator');
Expand Down Expand Up @@ -181,7 +182,7 @@ export default function Operator() {
const { isPast, isSelected, isLinkedToLoaded, totalGap } = process(nestedEntry);

// hide past events (if setting) and skipped events
if (hidePast && isPast) {
if ((hidePast && isPast) || nestedEntry.skip) {
return null;
}

Expand Down
9 changes: 7 additions & 2 deletions apps/client/src/features/rundown/Rundown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ export default function Rundown({ data, rundownMetadata }: RundownProps) {

const cursorRef = useRef<HTMLDivElement | null>(null);
const scrollRef = useRef<HTMLDivElement | null>(null);
useFollowComponent({ followRef: cursorRef, scrollRef, doFollow: editorMode === AppMode.Run });
useFollowComponent({
followRef: cursorRef,
scrollRef,
doFollow: true,
followTrigger: editorMode === AppMode.Edit ? cursor : featureData?.selectedEventId,
});

// DND KIT
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }));
Expand Down Expand Up @@ -312,7 +317,7 @@ export default function Rundown({ data, rundownMetadata }: RundownProps) {
setMetadata(rundownMetadata);
}, [order, entries, rundownMetadata]);

// in run mode, we follow selection
// in run mode, we follow the playback selection and open groups as needed
useEffect(() => {
if (editorMode !== AppMode.Run || !featureData?.selectedEventId) {
return;
Expand Down
12 changes: 7 additions & 5 deletions apps/client/src/features/rundown/RundownExport.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,22 @@
padding-inline: 0;
box-shadow: $box-shadow-right;

flex: 1 2 auto; /* flex-grow: 1, flex-shrink: 2, flex-basis: auto */
flex: 1 1 0; /* flex-grow: 1, flex-shrink: 1, flex-basis: 0 */
min-width: 38rem;
max-width: 60rem;
max-width: none;
width: 0;
}

.side {
max-height: 100%;
max-width: 45rem;
margin: 0.5rem 0;
padding: 1rem;
padding-right: 0;
background-color: $gray-1325;
border-radius: 0 8px 8px 0;

flex: 1 1 auto; /* flex-grow: 1, flex-shrink: 1, flex-basis: auto */
max-width: 45rem;
flex: 1 1 0; /* flex-grow: 1, flex-shrink: 1, flex-basis: 0 */
// width is locked to swatch picker elements
min-width: calc(15 * 2rem + 13 * 0.5rem);
width: 0;
}
Loading
Loading