mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 22:24:11 +00:00
refactor: review styles and implement BEM for user override
This commit is contained in:
committed by
Carlos Valente
parent
dc06a9afc3
commit
b539b2ecd5
@@ -8,6 +8,7 @@ $timeline-height: 1rem;
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
|
background-color: $ui-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timelineEvents {
|
.timelineEvents {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
.timeline {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
background-color: $ui-black;
|
|
||||||
color: $ui-white;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
padding-inline: 2rem;
|
|
||||||
font-size: 3.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sections {
|
|
||||||
padding-inline: 2rem;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
row-gap: 1rem;
|
|
||||||
column-gap: 3rem;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
@use '../../../theme/viewerDefs' as *;
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
|
||||||
|
font-family: var(--font-family-override, $viewer-font-family);
|
||||||
|
background: var(--background-color-override, $viewer-background-color);
|
||||||
|
color: var(--color-override, $viewer-color);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
|
||||||
|
.project-header {
|
||||||
|
padding-inline: 2rem;
|
||||||
|
font-size: clamp(32px, 4.5vw, 64px);
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clock-container {
|
||||||
|
.label {
|
||||||
|
font-size: clamp(16px, 1.5vw, 24px);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--label-color-override, $viewer-label-color);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
font-size: clamp(32px, 3.5vw, 50px);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--secondary-color-override, $viewer-secondary-color);
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
line-height: 0.95em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 3fr;
|
||||||
|
row-gap: 1rem;
|
||||||
|
column-gap: 2rem;
|
||||||
|
grid-template-areas:
|
||||||
|
'now next'
|
||||||
|
'now following';
|
||||||
|
padding-inline: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
background-color: var(--card-background-color-override, $viewer-card-bg-color);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border-radius: $element-border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section--now {
|
||||||
|
grid-area: now;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
line-height: 1em;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title__label {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title__status {
|
||||||
|
color: $green-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content {
|
||||||
|
min-height: 2em;
|
||||||
|
line-height: 1em;
|
||||||
|
font-size: 3rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content--now {
|
||||||
|
color: $red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content--next {
|
||||||
|
color: $green-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content--subdue {
|
||||||
|
opacity: $opacity-disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { MaybeString, OntimeEvent, ProjectData, Settings } from 'ontime-types';
|
import { MaybeString, OntimeEvent, ProjectData, Settings, ViewSettings } from 'ontime-types';
|
||||||
|
|
||||||
|
import { overrideStylesURL } from '../../../common/api/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
|
import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
|
||||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||||
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||||
|
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||||
|
|
||||||
import Section from './timeline-section/TimelineSection';
|
import Section from './timeline-section/TimelineSection';
|
||||||
import Timeline from './Timeline';
|
import Timeline from './Timeline';
|
||||||
import { getTimelineOptions } from './timeline.options';
|
import { getTimelineOptions } from './timeline.options';
|
||||||
import { getFormattedTimeToStart, getScopedRundown, getUpcomingEvents } from './timeline.utils';
|
import { getFormattedTimeToStart, getScopedRundown, getUpcomingEvents } from './timeline.utils';
|
||||||
|
|
||||||
import style from './TimelinePage.module.scss';
|
import './TimelinePage.scss';
|
||||||
|
|
||||||
interface TimelinePageProps {
|
interface TimelinePageProps {
|
||||||
backstageEvents: OntimeEvent[];
|
backstageEvents: OntimeEvent[];
|
||||||
@@ -19,6 +23,7 @@ interface TimelinePageProps {
|
|||||||
selectedId: MaybeString;
|
selectedId: MaybeString;
|
||||||
settings: Settings | undefined;
|
settings: Settings | undefined;
|
||||||
time: ViewExtendedTimer;
|
time: ViewExtendedTimer;
|
||||||
|
viewSettings: ViewSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,7 +32,8 @@ interface TimelinePageProps {
|
|||||||
* There is little point splitting or memoising top level elements
|
* There is little point splitting or memoising top level elements
|
||||||
*/
|
*/
|
||||||
export default function TimelinePage(props: TimelinePageProps) {
|
export default function TimelinePage(props: TimelinePageProps) {
|
||||||
const { backstageEvents, general, selectedId, settings, time } = props;
|
const { backstageEvents, general, selectedId, settings, time, viewSettings } = props;
|
||||||
|
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
|
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const clock = formatTime(time.clock);
|
const clock = formatTime(time.clock);
|
||||||
@@ -40,25 +46,44 @@ export default function TimelinePage(props: TimelinePageProps) {
|
|||||||
return getUpcomingEvents(scopedRundown, selectedId);
|
return getUpcomingEvents(scopedRundown, selectedId);
|
||||||
}, [scopedRundown, selectedId]);
|
}, [scopedRundown, selectedId]);
|
||||||
|
|
||||||
|
useWindowTitle('Timeline');
|
||||||
|
|
||||||
|
if (!shouldRender) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// populate options
|
// populate options
|
||||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||||
const progressOptions = getTimelineOptions(defaultFormat);
|
const progressOptions = getTimelineOptions(defaultFormat);
|
||||||
|
|
||||||
const titleNow = now?.title ?? '-';
|
const titleNow = now?.title ?? '-';
|
||||||
const dueText = getLocalizedString('timeline.due');
|
const dueText = getLocalizedString('timeline.due').toUpperCase();
|
||||||
const nextText = next !== null ? `${next.title} · ${getFormattedTimeToStart(next, time.clock, dueText)}` : '-';
|
const nextText = next !== null ? next.title : '-';
|
||||||
const followedByText =
|
const followedByText = followedBy !== null ? followedBy.title : '-';
|
||||||
followedBy !== null ? `${followedBy.title} · ${getFormattedTimeToStart(followedBy, time.clock, dueText)}` : '-';
|
|
||||||
|
const nextStatus = next !== null ? getFormattedTimeToStart(next, time.clock, dueText) : undefined;
|
||||||
|
const followedByStatus = followedBy !== null ? getFormattedTimeToStart(followedBy, time.clock, dueText) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.timeline}>
|
<div className='timeline'>
|
||||||
<ViewParamsEditor viewOptions={progressOptions} />
|
<ViewParamsEditor viewOptions={progressOptions} />
|
||||||
<div className={style.title}>{general.title}</div>
|
<div className='project-header'>
|
||||||
<div className={style.sections}>
|
{general.title}
|
||||||
<Section title={getLocalizedString('common.time_now')} content={clock} category='now' />
|
<div className='clock-container'>
|
||||||
<Section title={getLocalizedString('common.next')} content={nextText} category='next' />
|
<div className='label'>{getLocalizedString('common.time_now')}</div>
|
||||||
|
<SuperscriptTime time={clock} className='time' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='title-grid'>
|
||||||
<Section title={getLocalizedString('timeline.live')} content={titleNow} category='now' />
|
<Section title={getLocalizedString('timeline.live')} content={titleNow} category='now' />
|
||||||
<Section title={getLocalizedString('timeline.followedby')} content={followedByText} category='next' />
|
<Section title={getLocalizedString('common.next')} status={nextStatus} content={nextText} category='next' />
|
||||||
|
<Section
|
||||||
|
title={getLocalizedString('timeline.followedby')}
|
||||||
|
status={followedByStatus}
|
||||||
|
content={followedByText}
|
||||||
|
category='next'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Timeline selectedEventId={selectedId} rundown={scopedRundown} />
|
<Timeline selectedEventId={selectedId} rundown={scopedRundown} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
.sectionTitle {
|
|
||||||
line-height: 1.2em;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sectionContent {
|
|
||||||
min-height: 2em;
|
|
||||||
line-height: 1em;
|
|
||||||
font-size: 3rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 600;
|
|
||||||
|
|
||||||
&.now {
|
|
||||||
color: $red-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.next {
|
|
||||||
color: $green-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.subdue {
|
|
||||||
opacity: $opacity-disabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,28 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
import { MaybeString } from 'ontime-types';
|
import { MaybeString } from 'ontime-types';
|
||||||
|
|
||||||
import { cx } from '../../../../common/utils/styleUtils';
|
import { cx } from '../../../../common/utils/styleUtils';
|
||||||
|
|
||||||
import style from './TimelineSection.module.scss';
|
|
||||||
|
|
||||||
interface SectionProps {
|
interface SectionProps {
|
||||||
category: 'now' | 'next';
|
category: 'now' | 'next';
|
||||||
content: MaybeString;
|
content: MaybeString;
|
||||||
title: string;
|
title: string;
|
||||||
|
status?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Section(props: SectionProps) {
|
export default memo(Section);
|
||||||
const { category, content, title } = props;
|
|
||||||
|
|
||||||
const contentClasses = cx([style.sectionContent, content != null ? style[category] : style.subdue]);
|
export function Section(props: SectionProps) {
|
||||||
|
const { category, content, title, status } = props;
|
||||||
|
|
||||||
|
const sectionClasses = cx(['section', category === 'now' && 'section--now']);
|
||||||
|
const contentClasses = cx(['section-content', content ? `section-content--${category}` : 'section-content--subdue']);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={sectionClasses}>
|
||||||
<div className={style.sectionTitle}>{title}</div>
|
<div className='section-title'>
|
||||||
|
<span className='section-title__label'>{title}</span>
|
||||||
|
{status && <span className='section-title__status'>{status}</span>}
|
||||||
|
</div>
|
||||||
<div className={contentClasses}>{content ?? '-'}</div>
|
<div className={contentClasses}>{content ?? '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user