mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-15 20:33:47 +00:00
chore: folder structure
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
@use '../../../theme/viewerDefs' as *;
|
||||
|
||||
.schedule {
|
||||
width: 100%;
|
||||
border-spacing: 50px;
|
||||
|
||||
.entry {
|
||||
font-size: clamp(16px, 1.5vw, 24px);
|
||||
|
||||
.entry-colour {
|
||||
background-color: var(--card-background-color-override, $viewer-card-bg-color);
|
||||
height: clamp(8px, 0.75vw, 12px);
|
||||
width: clamp(8px, 0.75vw, 12px);
|
||||
border-radius: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.entry-times {
|
||||
font-family: $viewer-font-family;
|
||||
color: var(--secondary-color-override, $viewer-secondary-color);
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.05em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.entry-title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.entry-secondary {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
&--past {
|
||||
color: var(--secondary-color-override, $viewer-secondary-color);
|
||||
}
|
||||
|
||||
&--now {
|
||||
.entry-title {
|
||||
color: var(--accent-color-override, $accent-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&.skip {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-nav {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.schedule-nav__item {
|
||||
background-color: var(--card-background-color-override, $viewer-card-bg-color);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
margin-left: 8px;
|
||||
|
||||
&--selected {
|
||||
background-color: var(--color-override, $viewer-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { useSchedule } from './ScheduleContext';
|
||||
import ScheduleItem from './ScheduleItem';
|
||||
|
||||
import './Schedule.scss';
|
||||
|
||||
interface ScheduleProps {
|
||||
isProduction?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Schedule({ isProduction, className }: ScheduleProps) {
|
||||
const { paginatedEvents, selectedEventId, isBackstage, scheduleType } = useSchedule();
|
||||
|
||||
// TODO: design a nice placeholder for empty schedules
|
||||
if (paginatedEvents?.length < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let selectedState: 'past' | 'now' | 'future' = 'past';
|
||||
|
||||
return (
|
||||
<ul className={`schedule ${className}`}>
|
||||
{paginatedEvents.map((event) => {
|
||||
if (scheduleType === 'past' || scheduleType === 'future') {
|
||||
selectedState = scheduleType;
|
||||
} else {
|
||||
if (event.id === selectedEventId) {
|
||||
selectedState = 'now';
|
||||
} else if (selectedState === 'now') {
|
||||
selectedState = 'future';
|
||||
}
|
||||
}
|
||||
|
||||
const timeStart = isProduction ? event.timeStart + (event?.delay ?? 0) : event.timeStart;
|
||||
const timeEnd = isProduction ? event.timeEnd + (event?.delay ?? 0) : event.timeEnd;
|
||||
|
||||
return (
|
||||
<ScheduleItem
|
||||
key={event.id}
|
||||
selected={selectedState}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
title={event.title}
|
||||
colour={isBackstage ? event.colour : ''}
|
||||
backstageEvent={!event.isPublic}
|
||||
skip={event.skip}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import { createContext, PropsWithChildren, useContext, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { OntimeEvent } from 'ontime-types';
|
||||
|
||||
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
|
||||
import { useInterval } from '../../hooks/useInterval';
|
||||
|
||||
interface ScheduleContextState {
|
||||
events: OntimeEvent[];
|
||||
paginatedEvents: OntimeEvent[];
|
||||
selectedEventId: string | null;
|
||||
scheduleType: 'past' | 'now' | 'future';
|
||||
numPages: number;
|
||||
visiblePage: number;
|
||||
isBackstage: boolean;
|
||||
}
|
||||
|
||||
const ScheduleContext = createContext<ScheduleContextState | undefined>(undefined);
|
||||
|
||||
interface ScheduleProviderProps {
|
||||
events: OntimeEvent[];
|
||||
selectedEventId: string | null;
|
||||
isBackstage?: boolean;
|
||||
time?: number;
|
||||
}
|
||||
|
||||
const numEventsPerPage = 8;
|
||||
|
||||
export const ScheduleProvider = ({
|
||||
children,
|
||||
events,
|
||||
selectedEventId,
|
||||
isBackstage = false,
|
||||
time = 10,
|
||||
}: PropsWithChildren<ScheduleProviderProps>) => {
|
||||
const [visiblePage, setVisiblePage] = useState(0);
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// look for overrides from views
|
||||
const hidePast = isStringBoolean(searchParams.get('hidePast'));
|
||||
const stopCycle = isStringBoolean(searchParams.get('stopCycle'));
|
||||
const eventsPerPage = Number(searchParams.get('eventsPerPage') ?? numEventsPerPage);
|
||||
|
||||
let selectedEventIndex = events.findIndex((event) => event.id === selectedEventId);
|
||||
|
||||
const viewEvents = [...events];
|
||||
if (hidePast) {
|
||||
// we want to show the event after the next
|
||||
viewEvents.splice(0, selectedEventIndex + 2);
|
||||
selectedEventIndex = 0;
|
||||
}
|
||||
|
||||
const numPages = Math.ceil(viewEvents.length / eventsPerPage);
|
||||
const eventStart = eventsPerPage * visiblePage;
|
||||
const eventEnd = eventsPerPage * (visiblePage + 1);
|
||||
const paginatedEvents = viewEvents.slice(eventStart, eventEnd);
|
||||
|
||||
const resolveScheduleType = () => {
|
||||
if (selectedEventIndex >= eventStart && selectedEventIndex < eventEnd) {
|
||||
return 'now';
|
||||
}
|
||||
if (selectedEventIndex > eventEnd) {
|
||||
return 'past';
|
||||
}
|
||||
return 'future';
|
||||
};
|
||||
const scheduleType = resolveScheduleType();
|
||||
|
||||
// every SCROLL_TIME go to the next array
|
||||
useInterval(() => {
|
||||
if (stopCycle) {
|
||||
setVisiblePage(0);
|
||||
} else if (events.length > eventsPerPage) {
|
||||
const next = (visiblePage + 1) % numPages;
|
||||
setVisiblePage(next);
|
||||
}
|
||||
}, time * 1000);
|
||||
|
||||
return (
|
||||
<ScheduleContext.Provider
|
||||
value={{
|
||||
events,
|
||||
paginatedEvents,
|
||||
selectedEventId,
|
||||
scheduleType,
|
||||
numPages,
|
||||
visiblePage,
|
||||
isBackstage,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ScheduleContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSchedule = () => {
|
||||
const context = useContext(ScheduleContext);
|
||||
if (!context) {
|
||||
throw new Error('useSchedule() can only be used inside a ScheduleContext');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
import SuperscriptTime from '../../../features/viewers/common/superscript-time/SuperscriptTime';
|
||||
import { formatTime } from '../../utils/time';
|
||||
|
||||
import './Schedule.scss';
|
||||
|
||||
const formatOptions = {
|
||||
format12: 'hh:mm a',
|
||||
format24: 'HH:mm',
|
||||
};
|
||||
|
||||
interface ScheduleItemProps {
|
||||
selected: 'past' | 'now' | 'future';
|
||||
timeStart: number;
|
||||
timeEnd: number;
|
||||
title: string;
|
||||
backstageEvent: boolean;
|
||||
colour: string;
|
||||
skip: boolean;
|
||||
}
|
||||
|
||||
export default function ScheduleItem(props: ScheduleItemProps) {
|
||||
const { selected, timeStart, timeEnd, title, backstageEvent, colour, skip } = props;
|
||||
|
||||
const start = formatTime(timeStart, formatOptions);
|
||||
const end = formatTime(timeEnd, formatOptions);
|
||||
const userColour = colour !== '' ? colour : '';
|
||||
const selectStyle = `entry--${selected}`;
|
||||
|
||||
return (
|
||||
<li className={`entry ${selectStyle} ${skip ? 'skip' : ''}`}>
|
||||
<div className='entry-times'>
|
||||
<span className='entry-colour' style={{ backgroundColor: userColour }} />
|
||||
<div style={{ display: 'flex' }}>
|
||||
<SuperscriptTime time={start} />
|
||||
{' → '}
|
||||
<SuperscriptTime time={end} />
|
||||
{backstageEvent ? '*' : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className='entry-title'>{title}</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { useSchedule } from './ScheduleContext';
|
||||
|
||||
import './Schedule.scss';
|
||||
|
||||
interface ScheduleNavProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function ScheduleNav({ className }: ScheduleNavProps) {
|
||||
const { numPages, visiblePage } = useSchedule();
|
||||
|
||||
return (
|
||||
<div className={`schedule-nav ${className}`}>
|
||||
{numPages > 1 &&
|
||||
[...Array(numPages).keys()].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={i === visiblePage ? 'schedule-nav__item schedule-nav__item--selected' : 'schedule-nav__item'}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user