mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 18:03:47 +00:00
Merge remote-tracking branch 'origin/master' into feat/http-integration
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import SuperscriptTime from '../../../features/viewers/common/superscript-time/SuperscriptTime';
|
||||
import { formatTime } from '../../utils/time';
|
||||
|
||||
import './Schedule.scss';
|
||||
|
||||
const formatOptions = {
|
||||
format: 'hh:mm a',
|
||||
};
|
||||
|
||||
interface ScheduleItemProps {
|
||||
selected: 'past' | 'now' | 'future';
|
||||
timeStart: number;
|
||||
@@ -16,8 +21,8 @@ interface ScheduleItemProps {
|
||||
export default function ScheduleItem(props: ScheduleItemProps) {
|
||||
const { selected, timeStart, timeEnd, title, presenter, backstageEvent, colour, skip } = props;
|
||||
|
||||
const start = formatTime(timeStart, { format: 'hh:mm' });
|
||||
const end = formatTime(timeEnd, { format: 'hh:mm' });
|
||||
const start = formatTime(timeStart, formatOptions);
|
||||
const end = formatTime(timeEnd, formatOptions);
|
||||
const userColour = colour !== '' ? colour : '';
|
||||
const selectStyle = `entry--${selected}`;
|
||||
|
||||
@@ -25,7 +30,12 @@ export default function ScheduleItem(props: ScheduleItemProps) {
|
||||
<li className={`entry ${selectStyle} ${skip ? 'skip' : ''}`}>
|
||||
<div className='entry-times'>
|
||||
<span className='entry-colour' style={{ backgroundColor: userColour }} />
|
||||
{`${start} → ${end} ${backstageEvent ? '*' : ''}`}
|
||||
<div style={{ display: 'flex' }}>
|
||||
<SuperscriptTime time={start} />
|
||||
{' → '}
|
||||
<SuperscriptTime time={end} />
|
||||
{backstageEvent ? '*' : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className='entry-title'>{title}</div>
|
||||
{presenter && <div className='entry-presenter'>{presenter}</div>}
|
||||
|
||||
@@ -59,10 +59,10 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) {
|
||||
setSearchParams(defaultSearchParams);
|
||||
}
|
||||
|
||||
// linter is asking for `setSearchParams` in the useEffect deps
|
||||
// rule is disabled since adding `setSearchParams` results in unnecessary re-renders
|
||||
// linter is asking for `setSearchParams` & `storedViewParams` in the useEffect deps
|
||||
// rule is disabled since adding `setSearchParams` & `storedViewParams` results in unnecessary re-renders
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [storedViewParams, pathname]);
|
||||
}, [pathname]);
|
||||
|
||||
const onEditDrawerClose = () => {
|
||||
onClose();
|
||||
|
||||
@@ -1,53 +1,46 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSyncExternalStore } from 'react';
|
||||
|
||||
/**
|
||||
* @description utility hook to handle state in local storage
|
||||
* @param key
|
||||
* @param initialValue
|
||||
*/
|
||||
export const useLocalStorage = <T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void] => {
|
||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(`ontime-${key}`);
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
} catch (error) {
|
||||
return initialValue;
|
||||
}
|
||||
});
|
||||
const STORAGE_EVENT = 'ontime-storage';
|
||||
|
||||
useEffect(() => {
|
||||
const handleStorageChange = (event: StorageEvent) => {
|
||||
if (event.storageArea === window.localStorage && event.key === key) {
|
||||
try {
|
||||
const newValue = event.newValue ? JSON.parse(event.newValue) : initialValue;
|
||||
setStoredValue(newValue);
|
||||
} catch (_) {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
};
|
||||
function getSnapshot(key: string): string | null {
|
||||
try {
|
||||
return window.localStorage.getItem(`ontime-${key}`);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
function getParsedJson<T>(localStorageValue: string | null, initialValue: T): T {
|
||||
try {
|
||||
return localStorageValue ? JSON.parse(localStorageValue) : initialValue;
|
||||
} catch {
|
||||
return initialValue;
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
};
|
||||
}, [initialValue, key]);
|
||||
export const useLocalStorage = <T>(key: string, initialValue: T) => {
|
||||
const localStorageValue = useSyncExternalStore(subscribe, () => getSnapshot(key));
|
||||
const parsedLocalStorageValue = getParsedJson(localStorageValue, initialValue);
|
||||
|
||||
/**
|
||||
* @description Set value to local storage
|
||||
* @param value
|
||||
*/
|
||||
const setValue = (value: T | ((val: T) => T)) => {
|
||||
try {
|
||||
// Allow value to be a function so we have same API as useState
|
||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||
const setLocalStorageValue = (value: T | ((val: T) => T)) => {
|
||||
// Allow value to be a function so we have same API as useState
|
||||
const valueToStore = value instanceof Function ? value(parsedLocalStorageValue) : value;
|
||||
|
||||
setStoredValue(valueToStore);
|
||||
window.localStorage.setItem(`ontime-${key}`, JSON.stringify(valueToStore));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
localStorage.setItem(`ontime-${key}`, JSON.stringify(valueToStore));
|
||||
window.dispatchEvent(new StorageEvent(STORAGE_EVENT));
|
||||
};
|
||||
return [storedValue, setValue];
|
||||
|
||||
return [parsedLocalStorageValue, setLocalStorageValue] as const;
|
||||
};
|
||||
|
||||
function subscribe(callback: () => void) {
|
||||
window.addEventListener(STORAGE_EVENT, callback);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(STORAGE_EVENT, callback);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export const useMessageControl = () => {
|
||||
timerMessage: state.timerMessage,
|
||||
publicMessage: state.publicMessage,
|
||||
lowerMessage: state.lowerMessage,
|
||||
externalMessage: state.externalMessage,
|
||||
onAir: state.onAir,
|
||||
});
|
||||
|
||||
@@ -40,6 +41,8 @@ export const setMessage = {
|
||||
publicVisible: (payload: boolean) => socketSendJson('set-public-message-visible', payload),
|
||||
lowerText: (payload: string) => socketSendJson('set-lower-message-text', payload),
|
||||
lowerVisible: (payload: boolean) => socketSendJson('set-lower-message-visible', payload),
|
||||
externalText: (payload: string) => socketSendJson('set-external-message-text', payload),
|
||||
externalVisible: (payload: boolean) => socketSendJson('set-external-message-visible', payload),
|
||||
onAir: (payload: boolean) => socketSendJson('set-onAir', payload),
|
||||
timerBlink: (payload: boolean) => socketSendJson('set-timer-blink', payload),
|
||||
timerBlackout: (payload: boolean) => socketSendJson('set-timer-blackout', payload),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Playback, RuntimeStore } from 'ontime-types';
|
||||
import { useStore } from 'zustand';
|
||||
import { createStore } from 'zustand/vanilla';
|
||||
|
||||
export const runtimeStorePlaceholder = {
|
||||
export const runtimeStorePlaceholder: RuntimeStore = {
|
||||
timer: {
|
||||
clock: 0,
|
||||
current: null,
|
||||
@@ -33,6 +33,10 @@ export const runtimeStorePlaceholder = {
|
||||
text: '',
|
||||
visible: false,
|
||||
},
|
||||
externalMessage: {
|
||||
text: '',
|
||||
visible: false,
|
||||
},
|
||||
onAir: false,
|
||||
loaded: {
|
||||
numEvents: 0,
|
||||
|
||||
+10
@@ -26,4 +26,14 @@ describe('formatTime()', () => {
|
||||
const time = formatTime(ms);
|
||||
expect(time).toStrictEqual('...');
|
||||
});
|
||||
|
||||
it('shows 12h format without times', () => {
|
||||
const ms = 13 * 60 * 60 * 1000;
|
||||
const options = {
|
||||
showSeconds: false,
|
||||
format: 'hh:mm a',
|
||||
};
|
||||
const time = formatTime(ms, options, () => '12');
|
||||
expect(time).toStrictEqual('01:00 PM');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user