mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 21:24:11 +00:00
feat: collapsible aux timers section, fix icon button variants
- Add a collapse toggle next to the aux timers settings shortcut, mirroring the chevron pattern already used for rundown groups. State persists in localStorage via the existing editorSettings store, so it's a durable per-user preference rather than resetting on reload. This matters most on small viewports, where the aux timer controls (3 rows) are a disproportionate cost relative to their usage. - When collapsed, a small active-indicator dot appears next to the "Aux timers" label if any aux timer is currently running, so a running timer never goes silently invisible. - Switch both header icon buttons (settings shortcut, collapse toggle) from the 'subtle' variant (blue, reserved for actions) to 'subtle-white', matching how utility/navigation icon buttons are styled elsewhere (eg. the rundown group collapse chevron). Verified in the browser: collapsing reclaims the three-row control block, the indicator dot appears while a timer runs and the panel is collapsed, and both icon buttons render white/gray instead of blue. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCZejVTzuAY3tHTE6nB3JH
This commit is contained in:
@@ -103,6 +103,14 @@ export const useAuxTimersName = createSelector((state: RuntimeStore) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const useAuxTimersActive = createSelector((state: RuntimeStore) => {
|
||||||
|
return (
|
||||||
|
state.auxtimer1.playback === SimplePlayback.Start ||
|
||||||
|
state.auxtimer2.playback === SimplePlayback.Start ||
|
||||||
|
state.auxtimer3.playback === SimplePlayback.Start
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export const useAuxTimerTime = (index: number) =>
|
export const useAuxTimerTime = (index: number) =>
|
||||||
createSelector((state: RuntimeStore) => {
|
createSelector((state: RuntimeStore) => {
|
||||||
if (index === 1) return state.auxtimer1.current;
|
if (index === 1) return state.auxtimer1.current;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type EditorSettingsStore = {
|
|||||||
defaultTimerType: TimerType;
|
defaultTimerType: TimerType;
|
||||||
defaultEndAction: EndAction;
|
defaultEndAction: EndAction;
|
||||||
inheritGroupColour: boolean;
|
inheritGroupColour: boolean;
|
||||||
|
auxTimersCollapsed: boolean;
|
||||||
setDefaultDuration: (defaultDuration: string) => void;
|
setDefaultDuration: (defaultDuration: string) => void;
|
||||||
setLinkPrevious: (linkPrevious: boolean) => void;
|
setLinkPrevious: (linkPrevious: boolean) => void;
|
||||||
setInheritGroupColour: (inheritGroupColour: boolean) => void;
|
setInheritGroupColour: (inheritGroupColour: boolean) => void;
|
||||||
@@ -21,6 +22,7 @@ type EditorSettingsStore = {
|
|||||||
setDangerTime: (dangerTime: string) => void;
|
setDangerTime: (dangerTime: string) => void;
|
||||||
setDefaultTimerType: (defaultTimerType: TimerType) => void;
|
setDefaultTimerType: (defaultTimerType: TimerType) => void;
|
||||||
setDefaultEndAction: (defaultEndAction: EndAction) => void;
|
setDefaultEndAction: (defaultEndAction: EndAction) => void;
|
||||||
|
setAuxTimersCollapsed: (auxTimersCollapsed: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const editorSettingsDefaults = {
|
export const editorSettingsDefaults = {
|
||||||
@@ -32,6 +34,7 @@ export const editorSettingsDefaults = {
|
|||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
inheritGroupColour: false,
|
inheritGroupColour: false,
|
||||||
|
auxTimersCollapsed: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum EditorSettingsKeys {
|
enum EditorSettingsKeys {
|
||||||
@@ -43,6 +46,7 @@ enum EditorSettingsKeys {
|
|||||||
DefaultTimerType = 'ontime-default-timer-type',
|
DefaultTimerType = 'ontime-default-timer-type',
|
||||||
DefaultEndAction = 'ontime-default-end-action',
|
DefaultEndAction = 'ontime-default-end-action',
|
||||||
InheritGroupColour = 'ontime-inherit-group-colour',
|
InheritGroupColour = 'ontime-inherit-group-colour',
|
||||||
|
AuxTimersCollapsed = 'ontime-aux-timers-collapsed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useEditorSettings = create<EditorSettingsStore>((set) => {
|
export const useEditorSettings = create<EditorSettingsStore>((set) => {
|
||||||
@@ -67,6 +71,10 @@ export const useEditorSettings = create<EditorSettingsStore>((set) => {
|
|||||||
EditorSettingsKeys.InheritGroupColour,
|
EditorSettingsKeys.InheritGroupColour,
|
||||||
editorSettingsDefaults.inheritGroupColour,
|
editorSettingsDefaults.inheritGroupColour,
|
||||||
),
|
),
|
||||||
|
auxTimersCollapsed: booleanFromLocalStorage(
|
||||||
|
EditorSettingsKeys.AuxTimersCollapsed,
|
||||||
|
editorSettingsDefaults.auxTimersCollapsed,
|
||||||
|
),
|
||||||
|
|
||||||
setDefaultDuration: (defaultDuration) =>
|
setDefaultDuration: (defaultDuration) =>
|
||||||
set(() => {
|
set(() => {
|
||||||
@@ -110,5 +118,10 @@ export const useEditorSettings = create<EditorSettingsStore>((set) => {
|
|||||||
localStorage.setItem(EditorSettingsKeys.InheritGroupColour, String(inheritGroupColour));
|
localStorage.setItem(EditorSettingsKeys.InheritGroupColour, String(inheritGroupColour));
|
||||||
return { inheritGroupColour };
|
return { inheritGroupColour };
|
||||||
}),
|
}),
|
||||||
|
setAuxTimersCollapsed: (auxTimersCollapsed) =>
|
||||||
|
set(() => {
|
||||||
|
localStorage.setItem(EditorSettingsKeys.AuxTimersCollapsed, String(auxTimersCollapsed));
|
||||||
|
return { auxTimersCollapsed };
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,10 +11,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
font-size: $inner-section-text-size;
|
font-size: $inner-section-text-size;
|
||||||
color: $label-gray;
|
color: $label-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auxHeaderButtons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeIndicator {
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: $active-indicator;
|
||||||
|
}
|
||||||
|
|
||||||
.auxTimers {
|
.auxTimers {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { IoSettingsOutline } from 'react-icons/io5';
|
import { IoChevronDown, IoChevronUp, IoSettingsOutline } from 'react-icons/io5';
|
||||||
|
|
||||||
import IconButton from '../../../common/components/buttons/IconButton';
|
import IconButton from '../../../common/components/buttons/IconButton';
|
||||||
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
||||||
import { usePlaybackControl } from '../../../common/hooks/useSocket';
|
import { useAuxTimersActive, usePlaybackControl } from '../../../common/hooks/useSocket';
|
||||||
|
import { useEditorSettings } from '../../../common/stores/editorSettings';
|
||||||
import useAppSettingsNavigation from '../../app-settings/useAppSettingsNavigation';
|
import useAppSettingsNavigation from '../../app-settings/useAppSettingsNavigation';
|
||||||
import AddTime from './add-time/AddTime';
|
import AddTime from './add-time/AddTime';
|
||||||
import { AuxTimer } from './aux-timer/AuxTimer';
|
import { AuxTimer } from './aux-timer/AuxTimer';
|
||||||
@@ -14,6 +15,8 @@ import style from './PlaybackControl.module.scss';
|
|||||||
export default function PlaybackControl() {
|
export default function PlaybackControl() {
|
||||||
const data = usePlaybackControl();
|
const data = usePlaybackControl();
|
||||||
const { setLocation } = useAppSettingsNavigation();
|
const { setLocation } = useAppSettingsNavigation();
|
||||||
|
const { auxTimersCollapsed, setAuxTimersCollapsed } = useEditorSettings();
|
||||||
|
const isAuxTimerActive = useAuxTimersActive();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.mainContainer}>
|
<div className={style.mainContainer}>
|
||||||
@@ -27,26 +30,41 @@ export default function PlaybackControl() {
|
|||||||
timerPhase={data.timerPhase}
|
timerPhase={data.timerPhase}
|
||||||
/>
|
/>
|
||||||
<div className={style.auxHeader}>
|
<div className={style.auxHeader}>
|
||||||
<span className={style.label}>Aux timers</span>
|
<span className={style.label}>
|
||||||
<Tooltip
|
Aux timers
|
||||||
text='Name aux timers'
|
{auxTimersCollapsed && isAuxTimerActive && <span className={style.activeIndicator} />}
|
||||||
render={
|
</span>
|
||||||
<IconButton
|
<div className={style.auxHeaderButtons}>
|
||||||
size='small'
|
<Tooltip
|
||||||
variant='subtle'
|
text='Name aux timers'
|
||||||
aria-label='Name aux timers'
|
render={
|
||||||
onClick={() => setLocation('settings__aux-timers')}
|
<IconButton
|
||||||
/>
|
size='small'
|
||||||
}
|
variant='subtle-white'
|
||||||
>
|
aria-label='Name aux timers'
|
||||||
<IoSettingsOutline />
|
onClick={() => setLocation('settings__aux-timers')}
|
||||||
</Tooltip>
|
/>
|
||||||
</div>
|
}
|
||||||
<div className={style.auxTimers}>
|
>
|
||||||
<AuxTimer index={1} />
|
<IoSettingsOutline />
|
||||||
<AuxTimer index={2} />
|
</Tooltip>
|
||||||
<AuxTimer index={3} />
|
<IconButton
|
||||||
|
size='small'
|
||||||
|
variant='subtle-white'
|
||||||
|
aria-label={auxTimersCollapsed ? 'Expand aux timers' : 'Collapse aux timers'}
|
||||||
|
onClick={() => setAuxTimersCollapsed(!auxTimersCollapsed)}
|
||||||
|
>
|
||||||
|
{auxTimersCollapsed ? <IoChevronUp /> : <IoChevronDown />}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{!auxTimersCollapsed && (
|
||||||
|
<div className={style.auxTimers}>
|
||||||
|
<AuxTimer index={1} />
|
||||||
|
<AuxTimer index={2} />
|
||||||
|
<AuxTimer index={3} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user