mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-25 00:49:10 +00:00
feat: app settings (#658)
* feat: app settings * refactor: cleanup routes * style: smaller base font * chore: migrate about modal * fix: import links
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
@use '../../../theme/v2Styles' as *;
|
||||||
|
@use '../../../theme/ontimeColours' as *;
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
color: $blue-500;
|
||||||
|
transition-property: color;
|
||||||
|
transition-duration: $transition-time-action;
|
||||||
|
|
||||||
|
&.inline {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $ontime-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { MouseEvent, ReactNode } from 'react';
|
||||||
|
import { IoOpenOutline } from '@react-icons/all-files/io5/IoOpenOutline';
|
||||||
|
|
||||||
|
import { openLink } from '../../utils/linkUtils';
|
||||||
|
import { cx } from '../../utils/styleUtils';
|
||||||
|
|
||||||
|
import style from './ExternalLink.module.scss';
|
||||||
|
|
||||||
|
interface ExternalLinkProps {
|
||||||
|
href: string;
|
||||||
|
children: ReactNode;
|
||||||
|
inline?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ModalLink(props: ExternalLinkProps) {
|
||||||
|
const { href, inline, children } = props;
|
||||||
|
const classes = cx([style.link, inline ? style.inline : null]);
|
||||||
|
|
||||||
|
const handleClick = (event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
openLink(href);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a href='#!' target='_blank' rel='noreferrer' className={classes} onClick={handleClick}>
|
||||||
|
{children} <IoOpenOutline />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export const githubUrl = 'https://www.github.com/cpvalente/ontime';
|
export const githubUrl = 'https://www.github.com/cpvalente/ontime';
|
||||||
export const apiRepoLatest = 'https://api.github.com/repos/cpvalente/ontime/releases/latest';
|
export const apiRepoLatest = 'https://api.github.com/repos/cpvalente/ontime/releases/latest';
|
||||||
|
export const websiteUrl = 'https://www.getontime.no';
|
||||||
|
|
||||||
export const gitbookUrl = 'https://ontime.gitbook.io';
|
export const gitbookUrl = 'https://ontime.gitbook.io';
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
@use '../../theme/ontimeColours' as *;
|
||||||
|
@use '../../theme/v2Styles' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
grid-area: main;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { ErrorBoundary } from '@sentry/react';
|
||||||
|
|
||||||
|
import { useKeyDown } from '../../common/hooks/useKeyDown';
|
||||||
|
|
||||||
|
import AboutPanel from './panel/about-panel/AboutPanel';
|
||||||
|
import PanelContent from './panel-content/PanelContent';
|
||||||
|
import PanelList from './panel-list/PanelList';
|
||||||
|
import { useSettingsStore } from './settingsStore';
|
||||||
|
|
||||||
|
import style from './AppSettings.module.scss';
|
||||||
|
|
||||||
|
export default function AppSettings() {
|
||||||
|
const setShowSettings = useSettingsStore((state) => state.setShowSettings);
|
||||||
|
const selectedPanel = useSettingsStore((state) => state.showSettings);
|
||||||
|
|
||||||
|
const closeSettings = () => {
|
||||||
|
setShowSettings(null);
|
||||||
|
};
|
||||||
|
useKeyDown(closeSettings, 'Escape');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.container}>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<PanelList />
|
||||||
|
<PanelContent onClose={closeSettings}>{selectedPanel === 'about' && <AboutPanel />}</PanelContent>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
.corner {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentWrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
import { IconButton } from '@chakra-ui/react';
|
||||||
|
import { IoClose } from '@react-icons/all-files/io5/IoClose';
|
||||||
|
|
||||||
|
import style from './PanelContent.module.scss';
|
||||||
|
|
||||||
|
interface PanelContentProps {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PanelContent(props: PropsWithChildren<PanelContentProps>) {
|
||||||
|
const { onClose, children } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.contentWrapper}>
|
||||||
|
<div className={style.corner}>
|
||||||
|
<IconButton onClick={onClose} aria-label='close' icon={<IoClose />} variant='ontime-ghosted-white' />
|
||||||
|
</div>
|
||||||
|
<div className={style.content}>{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
@use '../../../theme/ontimeColours' as *;
|
||||||
|
@use '../../../theme/v2Styles' as *;
|
||||||
|
|
||||||
|
.tabs,
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
width: min(30vw, 300px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary,
|
||||||
|
.secondary {
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: $gray-1000;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $gray-1000;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
font-size: 1rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: $blue-400;
|
||||||
|
background-color: $gray-1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unsaved::before {
|
||||||
|
content: '';
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: $blue-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.split {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
margin-left: 1rem;
|
||||||
|
color: $secondary-text-gray;
|
||||||
|
border-left: 1px solid $white-10;
|
||||||
|
font-size: $inner-section-text-size;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { KeyboardEvent } from 'react';
|
||||||
|
|
||||||
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
|
import { settingPanels, SettingsOption, useSettingsStore } from '../settingsStore';
|
||||||
|
|
||||||
|
import style from './PanelList.module.scss';
|
||||||
|
|
||||||
|
export default function PanelList() {
|
||||||
|
const { showSettings, setShowSettings, hasUnsavedChanges } = useSettingsStore();
|
||||||
|
|
||||||
|
const handleSelect = (panel: SettingsOption) => {
|
||||||
|
setShowSettings(panel.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isKeyEnter = (event: KeyboardEvent<HTMLLIElement>) => event.key === 'Enter';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className={style.tabs}>
|
||||||
|
{settingPanels.map((panel) => {
|
||||||
|
const unsaved = hasUnsavedChanges(panel.id);
|
||||||
|
|
||||||
|
const classes = cx([
|
||||||
|
style.primary,
|
||||||
|
showSettings === panel.id ? style.active : null,
|
||||||
|
panel.split ? style.split : null,
|
||||||
|
unsaved ? style.unsaved : null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<li
|
||||||
|
key={panel.id}
|
||||||
|
onClick={() => handleSelect(panel)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
isKeyEnter(event) && handleSelect(panel);
|
||||||
|
}}
|
||||||
|
className={classes}
|
||||||
|
tabIndex={0}
|
||||||
|
role='button'
|
||||||
|
>
|
||||||
|
{panel.label}
|
||||||
|
</li>
|
||||||
|
{panel.secondary?.map((secondary) => {
|
||||||
|
return (
|
||||||
|
<li key={secondary.id} onClick={() => handleSelect(panel)} className={style.secondary}>
|
||||||
|
{secondary.label}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
@use '../../../theme/ontimeColours' as *;
|
||||||
|
@use '../../../theme/v2Styles' as *;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: 2rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
border-bottom: 1px solid $white-10;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheader {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin: 2rem 0;
|
||||||
|
font-size: calc(1rem - 1px);
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paragraph {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: $white-1;
|
||||||
|
border: 1px solid $gray-1100;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
font-size: $inner-section-text-size;
|
||||||
|
display: block;
|
||||||
|
color: $error-red;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import style from './Panel.module.scss';
|
||||||
|
|
||||||
|
export function Header({ children }: { children: ReactNode }) {
|
||||||
|
return <h2 className={style.header}>{children}</h2>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SubHeader({ children }: { children: ReactNode }) {
|
||||||
|
return <h3 className={style.subheader}>{children}</h3>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Section({ children }: { children: ReactNode }) {
|
||||||
|
return <p className={style.section}>{children}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Paragraph({ children }: { children: ReactNode }) {
|
||||||
|
return <p className={style.paragraph}>{children}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Card({ children }: { children: ReactNode }) {
|
||||||
|
return <div className={style.card}>{children}</div>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { version } from '../../../../../package.json';
|
||||||
|
import ExternalLink from '../../../../common/components/external-link/ExternalLink';
|
||||||
|
import { gitbookUrl, githubUrl, websiteUrl } from '../../../../externals';
|
||||||
|
import * as Panel from '../PanelUtils';
|
||||||
|
|
||||||
|
import CheckUpdatesButton from './CheckUpdatesButton';
|
||||||
|
|
||||||
|
export default function AboutPanel() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Panel.Header>About Ontime</Panel.Header>
|
||||||
|
<Panel.Section>
|
||||||
|
<Panel.SubHeader>Ontime</Panel.SubHeader>
|
||||||
|
<Panel.Paragraph>
|
||||||
|
Free, open-source software for managing rundowns and event timers
|
||||||
|
<ExternalLink href={websiteUrl}>www.getontime.no</ExternalLink>
|
||||||
|
</Panel.Paragraph>
|
||||||
|
</Panel.Section>
|
||||||
|
<Panel.Section>
|
||||||
|
<Panel.Card>
|
||||||
|
<Panel.SubHeader>Links</Panel.SubHeader>
|
||||||
|
<ExternalLink href={gitbookUrl}>Read the docs over at GitBook</ExternalLink>
|
||||||
|
<ExternalLink href={githubUrl}>Follow the project on GitHub</ExternalLink>
|
||||||
|
</Panel.Card>
|
||||||
|
</Panel.Section>
|
||||||
|
<Panel.Section>
|
||||||
|
<Panel.Card>
|
||||||
|
<Panel.SubHeader>Current version</Panel.SubHeader>
|
||||||
|
<Panel.Paragraph>{`You are currently using Ontime ${version}`}</Panel.Paragraph>
|
||||||
|
<CheckUpdatesButton version={version} />
|
||||||
|
</Panel.Card>
|
||||||
|
</Panel.Section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { getLatestVersion, HasUpdate } from '../../../../common/api/ontimeApi';
|
||||||
|
import ExternalLink from '../../../../common/components/external-link/ExternalLink';
|
||||||
|
|
||||||
|
import style from '../Panel.module.scss';
|
||||||
|
|
||||||
|
type CheckFail = {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CheckIsLatest = {
|
||||||
|
latest: true;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CheckRemote = CheckFail | CheckIsLatest | HasUpdate;
|
||||||
|
|
||||||
|
interface CheckUpdatesButtonProps {
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CheckUpdatesButton(props: CheckUpdatesButtonProps) {
|
||||||
|
const { version } = props;
|
||||||
|
|
||||||
|
const [updateMessage, setUpdateMessage] = useState<CheckRemote | null>(null);
|
||||||
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles version comparison and returns component with message
|
||||||
|
*/
|
||||||
|
const versionCheck = async () => {
|
||||||
|
setIsFetching(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const latest = await getLatestVersion();
|
||||||
|
|
||||||
|
if (!latest.version.includes(version)) {
|
||||||
|
// new version, pass data to component
|
||||||
|
setUpdateMessage(latest);
|
||||||
|
} else {
|
||||||
|
setUpdateMessage({ latest: true });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setUpdateMessage({ error: 'Error reaching server' });
|
||||||
|
} finally {
|
||||||
|
setIsFetching(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const disableButton = Boolean(updateMessage && 'version' in updateMessage);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={versionCheck} variant='ontime-filled' isLoading={isFetching} isDisabled={disableButton}>
|
||||||
|
Check for updates
|
||||||
|
</Button>
|
||||||
|
<ResolveUpdateMessage updateMessage={updateMessage} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResolveUpdateMessage(props: { updateMessage: CheckRemote | null }) {
|
||||||
|
const { updateMessage } = props;
|
||||||
|
|
||||||
|
if (updateMessage && 'error' in updateMessage) {
|
||||||
|
return <span className={style.error}>{updateMessage.error}</span>;
|
||||||
|
}
|
||||||
|
if (updateMessage && 'url' in updateMessage) {
|
||||||
|
return <ExternalLink href={updateMessage?.url}>{`New version available: ${updateMessage.version}`}</ExternalLink>;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
export type SettingsOption = {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
secondary?: SettingsOption[];
|
||||||
|
split?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const settingPanels: SettingsOption[] = [
|
||||||
|
{ id: 'project', label: 'Project' },
|
||||||
|
{ id: 'general', label: 'General' },
|
||||||
|
{ id: 'interface', label: 'Interface' },
|
||||||
|
{ id: 'views', label: 'Views' },
|
||||||
|
{
|
||||||
|
id: 'sources',
|
||||||
|
label: 'Data Sources',
|
||||||
|
secondary: [{ id: 'g-sheet', label: 'Sync with Google Sheet' }],
|
||||||
|
split: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'integrations',
|
||||||
|
label: 'Integrations',
|
||||||
|
secondary: [
|
||||||
|
{ id: 'osc', label: 'OSC Integration' },
|
||||||
|
{ id: 'http', label: 'HTTP Integration' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ id: 'log', label: 'Log', split: true },
|
||||||
|
{
|
||||||
|
id: 'about',
|
||||||
|
label: 'About',
|
||||||
|
split: true,
|
||||||
|
secondary: [
|
||||||
|
{ id: 'links', label: 'Links' },
|
||||||
|
{ id: 'version', label: 'Version' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type SettingsOptionId = (typeof settingPanels)[number]['id'];
|
||||||
|
const firstPanel = settingPanels[0].id;
|
||||||
|
|
||||||
|
type SettingsStore = {
|
||||||
|
showSettings: SettingsOptionId | null;
|
||||||
|
setShowSettings: (panelId?: SettingsOptionId | null) => void;
|
||||||
|
unsavedChanges: Set<SettingsOptionId>;
|
||||||
|
hasUnsavedChanges: (panelId: SettingsOptionId) => boolean;
|
||||||
|
addUnsavedChanges: (panelId: SettingsOptionId) => void;
|
||||||
|
removeUnsavedChanges: (panelId: SettingsOptionId) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||||
|
showSettings: null,
|
||||||
|
setShowSettings: (panelId?: SettingsOptionId | null) => {
|
||||||
|
const newPanel = panelId === undefined ? firstPanel : panelId;
|
||||||
|
set((state) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
showSettings: newPanel,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unsavedChanges: new Set(),
|
||||||
|
hasUnsavedChanges: (panelId: SettingsOptionId) => get().unsavedChanges.has(panelId),
|
||||||
|
addUnsavedChanges: (panelId: SettingsOptionId) =>
|
||||||
|
set((state) => {
|
||||||
|
state.unsavedChanges.add(panelId);
|
||||||
|
return { unsavedChanges: new Set(state.unsavedChanges) };
|
||||||
|
}),
|
||||||
|
removeUnsavedChanges: (panelId: SettingsOptionId) =>
|
||||||
|
set((state) => {
|
||||||
|
state.unsavedChanges.delete(panelId);
|
||||||
|
return { unsavedChanges: new Set(state.unsavedChanges) };
|
||||||
|
}),
|
||||||
|
}));
|
||||||
@@ -13,6 +13,7 @@ $playback-width: 26rem;
|
|||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
transition-property: color;
|
transition-property: color;
|
||||||
transition-duration: $transition-time-action;
|
transition-duration: $transition-time-action;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $ontime-color;
|
color: $ontime-color;
|
||||||
}
|
}
|
||||||
@@ -28,152 +29,54 @@ $playback-width: 26rem;
|
|||||||
background: $ui-black;
|
background: $ui-black;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: auto;
|
|
||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
padding: 1rem 0.5rem;
|
padding: 1rem 0.5rem;
|
||||||
font-family: $ontime-font-family;
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: $menu-width auto;
|
||||||
|
grid-template-rows: 2rem 1fr;
|
||||||
|
grid-template-areas:
|
||||||
|
'menu overview'
|
||||||
|
'menu main';
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview {
|
||||||
|
grid-area: overview;
|
||||||
|
background-color: $white-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelContainer {
|
||||||
|
grid-area: main;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
grid-template-columns: $menu-width $rundown-width $playback-width auto;
|
grid-template-columns: $rundown-width $playback-width 1fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
'sett rundown play info'
|
'rundown play info'
|
||||||
'sett rundown mess info';
|
'rundown mess info';
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.rundown,
|
.rundown,
|
||||||
.playback,
|
.playback,
|
||||||
.messages,
|
.messages,
|
||||||
.info,
|
.info {
|
||||||
.settings {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: $bg-container-l2;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
.corner {
|
.corner {
|
||||||
|
/* we show this if the component hasnt been extracted */
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2/3 window, hide info */
|
|
||||||
@media (max-width: 1450px) and (min-height: 700px) {
|
|
||||||
.mainContainer {
|
|
||||||
height: 100%;
|
|
||||||
grid-template-rows: auto 1fr;
|
|
||||||
grid-template-columns: $menu-width 1fr $playback-width;
|
|
||||||
|
|
||||||
.info {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 1/2 window, event list only */
|
|
||||||
@media (max-width: 1100px) {
|
|
||||||
.mainContainer {
|
|
||||||
height: 100%;
|
|
||||||
grid-template-rows: 100%;
|
|
||||||
grid-template-columns: $menu-width $rundown-width;
|
|
||||||
grid-template-areas:
|
|
||||||
'sett rundown';
|
|
||||||
|
|
||||||
.info,
|
|
||||||
.messages,
|
|
||||||
.playback {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 1/3 window, show control only */
|
|
||||||
@media (max-width: 850px) and (min-height: 500px) {
|
|
||||||
.mainContainer {
|
|
||||||
grid-template-rows: auto 1fr;
|
|
||||||
grid-template-columns: 100%;
|
|
||||||
grid-template-areas:
|
|
||||||
'play'
|
|
||||||
'mess';
|
|
||||||
|
|
||||||
.playback,
|
|
||||||
.messages {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rundown,
|
|
||||||
.info,
|
|
||||||
.settings {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 1/3 corner window, playback only */
|
|
||||||
@media (max-width: 850px) and (max-height: 500px) {
|
|
||||||
.mainContainer {
|
|
||||||
grid-template-rows: 100%;
|
|
||||||
grid-template-columns: 100%;
|
|
||||||
grid-template-areas: 'play';
|
|
||||||
|
|
||||||
.playback {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rundown,
|
|
||||||
.messages,
|
|
||||||
.info,
|
|
||||||
.settings {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainContainer {
|
|
||||||
.settings,
|
|
||||||
.rundown,
|
|
||||||
.messages,
|
|
||||||
.playback,
|
|
||||||
.info {
|
|
||||||
border-radius: 8px;
|
|
||||||
height: 100%;
|
|
||||||
background-color: $bg-container-l2;
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.eventEditor {
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
background-color: $bg-container-l2;
|
|
||||||
box-shadow: $large-bottom-drawer-shadow;
|
|
||||||
border-top: 1px solid $white-20;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100vw;
|
|
||||||
left: 0;
|
|
||||||
z-index: 10;
|
|
||||||
color: white;
|
|
||||||
transition: bottom $transition-time-feedback;
|
|
||||||
|
|
||||||
&.noEvent {
|
|
||||||
bottom: -500px;
|
|
||||||
transition: bottom 0.7s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eventEditorLayout {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
background-color: $gray-1250;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-left: 1px solid $white-10;
|
|
||||||
border-radius: 0 8px 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rundown {
|
.rundown {
|
||||||
grid-area: rundown;
|
grid-area: rundown;
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
height: calc(100% - 1.5rem);
|
height: calc(100% - 1.5rem);
|
||||||
@@ -204,22 +107,6 @@ $playback-width: 26rem;
|
|||||||
min-width: 26rem;
|
min-width: 26rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mainContainer > .settings {
|
|
||||||
grid-area: sett;
|
|
||||||
background-color: transparent;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 0.5rem 0 0;
|
|
||||||
width: fit-content;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.mainContainer > .rundown {
|
|
||||||
padding: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-top: 1.5rem;
|
padding-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { lazy, useEffect } from 'react';
|
|||||||
import { useDisclosure } from '@chakra-ui/react';
|
import { useDisclosure } from '@chakra-ui/react';
|
||||||
|
|
||||||
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
|
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
|
||||||
|
import AppSettings from '../app-settings/AppSettings';
|
||||||
|
import { SettingsOptionId, useSettingsStore } from '../app-settings/settingsStore';
|
||||||
import MenuBar from '../menu/MenuBar';
|
import MenuBar from '../menu/MenuBar';
|
||||||
import AboutModal from '../modals/about-modal/AboutModal';
|
import AboutModal from '../modals/about-modal/AboutModal';
|
||||||
import QuickStart from '../modals/quick-start/QuickStart';
|
import QuickStart from '../modals/quick-start/QuickStart';
|
||||||
@@ -18,8 +20,31 @@ const EventEditor = lazy(() => import('../event-editor/EventEditorExport'));
|
|||||||
const IntegrationModal = lazy(() => import('../modals/integration-modal/IntegrationModal'));
|
const IntegrationModal = lazy(() => import('../modals/integration-modal/IntegrationModal'));
|
||||||
const SettingsModal = lazy(() => import('../modals/settings-modal/SettingsModal'));
|
const SettingsModal = lazy(() => import('../modals/settings-modal/SettingsModal'));
|
||||||
|
|
||||||
|
// TODO: add breakpoints for body font size ??
|
||||||
|
// - 15px for normal
|
||||||
|
// - 16px for large screens
|
||||||
|
|
||||||
|
// TODO: can we delete all the font-family stuff and leave it only at the top?
|
||||||
|
|
||||||
|
// TODO: change scrollbar colours to use ontime stuff?
|
||||||
|
|
||||||
|
// TODO: rename v2Styles to appStyles?
|
||||||
|
|
||||||
|
// TODO: remove onAir as a setting
|
||||||
|
|
||||||
|
// TODO: add error boundaries
|
||||||
|
|
||||||
|
// TODO: should nav menu have same rules as app settings
|
||||||
|
|
||||||
export default function Editor() {
|
export default function Editor() {
|
||||||
const { isOpen: isSettingsOpen, onOpen: onSettingsOpen, onClose: onSettingsClose } = useDisclosure();
|
const showSettings = useSettingsStore((state) => state.showSettings);
|
||||||
|
const setShowSettings = useSettingsStore((state) => state.setShowSettings);
|
||||||
|
|
||||||
|
const handleSettings = (newTab?: SettingsOptionId) => {
|
||||||
|
setShowSettings(newTab);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { isOpen: isOldSettingsOpen, onOpen: onSettingsOpen, onClose: onSettingsClose } = useDisclosure();
|
||||||
const { isOpen: isUploadModalOpen, onOpen: onUploadModalOpen, onClose: onUploadModalClose } = useDisclosure();
|
const { isOpen: isUploadModalOpen, onOpen: onUploadModalOpen, onClose: onUploadModalClose } = useDisclosure();
|
||||||
const {
|
const {
|
||||||
isOpen: isIntegrationModalOpen,
|
isOpen: isIntegrationModalOpen,
|
||||||
@@ -34,6 +59,8 @@ export default function Editor() {
|
|||||||
document.title = 'ontime - Editor';
|
document.title = 'ontime - Editor';
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const isSettingsOpen = Boolean(showSettings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
@@ -41,30 +68,42 @@ export default function Editor() {
|
|||||||
<UploadModal onClose={onUploadModalClose} isOpen={isUploadModalOpen} />
|
<UploadModal onClose={onUploadModalClose} isOpen={isUploadModalOpen} />
|
||||||
<IntegrationModal onClose={onIntegrationModalClose} isOpen={isIntegrationModalOpen} />
|
<IntegrationModal onClose={onIntegrationModalClose} isOpen={isIntegrationModalOpen} />
|
||||||
<AboutModal onClose={onAboutModalClose} isOpen={isAboutModalOpen} />
|
<AboutModal onClose={onAboutModalClose} isOpen={isAboutModalOpen} />
|
||||||
<SettingsModal isOpen={isSettingsOpen} onClose={onSettingsClose} />
|
<SettingsModal isOpen={isOldSettingsOpen} onClose={onSettingsClose} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<div className={styles.mainContainer} data-testid='event-editor'>
|
<div className={styles.mainContainer} data-testid='editor-container'>
|
||||||
<div id='settings' className={styles.settings}>
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<MenuBar
|
||||||
<MenuBar
|
isOldSettingsOpen={isOldSettingsOpen}
|
||||||
onSettingsOpen={onSettingsOpen}
|
onSettingsOpen={onSettingsOpen}
|
||||||
isSettingsOpen={isSettingsOpen}
|
onSettingsClose={onSettingsClose}
|
||||||
onSettingsClose={onSettingsClose}
|
isUploadOpen={isUploadModalOpen}
|
||||||
isUploadOpen={isUploadModalOpen}
|
onUploadOpen={onUploadModalOpen}
|
||||||
onUploadOpen={onUploadModalOpen}
|
isIntegrationOpen={isIntegrationModalOpen}
|
||||||
isIntegrationOpen={isIntegrationModalOpen}
|
onIntegrationOpen={onIntegrationModalOpen}
|
||||||
onIntegrationOpen={onIntegrationModalOpen}
|
isAboutOpen={isAboutModalOpen}
|
||||||
isAboutOpen={isAboutModalOpen}
|
onAboutOpen={onAboutModalOpen}
|
||||||
onAboutOpen={onAboutModalOpen}
|
isQuickStartOpen={isQuickStartOpen}
|
||||||
isQuickStartOpen={isQuickStartOpen}
|
onQuickStartOpen={onQuickStartOpen}
|
||||||
onQuickStartOpen={onQuickStartOpen}
|
openSettings={handleSettings}
|
||||||
/>
|
isSettingsOpen={isSettingsOpen}
|
||||||
</ErrorBoundary>
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
|
{showSettings ? (
|
||||||
|
<AppSettings />
|
||||||
|
) : (
|
||||||
|
<div id='panels' className={styles.panelContainer}>
|
||||||
|
<Rundown />
|
||||||
|
<MessageControl />
|
||||||
|
<TimerControl />
|
||||||
|
<Info />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={styles.overview}>
|
||||||
|
<ErrorBoundary></ErrorBoundary>
|
||||||
|
{
|
||||||
|
// TODO: the information about the event
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<Rundown />
|
|
||||||
<MessageControl />
|
|
||||||
<TimerControl />
|
|
||||||
<Info />
|
|
||||||
</div>
|
</div>
|
||||||
<EventEditor />
|
<EventEditor />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,36 @@
|
|||||||
|
@use '../../theme/ontimeColours' as *;
|
||||||
@use '../../theme/v2Styles' as *;
|
@use '../../theme/v2Styles' as *;
|
||||||
|
|
||||||
|
.eventEditorContainer {
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
background-color: $bg-container-l2;
|
||||||
|
box-shadow: $large-bottom-drawer-shadow;
|
||||||
|
border-top: 1px solid $white-20;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100vw;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
color: white;
|
||||||
|
transition: bottom $transition-time-feedback;
|
||||||
|
|
||||||
|
&.noEvent {
|
||||||
|
bottom: -500px;
|
||||||
|
transition: bottom 0.7s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eventEditorLayout {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: $gray-1250;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-left: 1px solid $white-10;
|
||||||
|
border-radius: 0 8px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.eventEditor {
|
.eventEditor {
|
||||||
padding: 1rem 2rem 2rem 2rem;
|
padding: 1rem 2rem 2rem 2rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -8,21 +8,13 @@ import { cx } from '../../common/utils/styleUtils';
|
|||||||
|
|
||||||
import EventEditor from './EventEditor';
|
import EventEditor from './EventEditor';
|
||||||
|
|
||||||
import style from '../editors/Editor.module.scss';
|
import style from './EventEditor.module.scss';
|
||||||
|
|
||||||
/* Styling for action buttons */
|
|
||||||
const closeBtnStyle = {
|
|
||||||
size: 'md',
|
|
||||||
variant: 'ghost',
|
|
||||||
colorScheme: 'white',
|
|
||||||
_hover: { bg: '#ebedf0', color: '#333' },
|
|
||||||
};
|
|
||||||
|
|
||||||
const EventEditorExport = () => {
|
const EventEditorExport = () => {
|
||||||
const editId = useAppMode((state) => state.editId);
|
const editId = useAppMode((state) => state.editId);
|
||||||
const setEditId = useAppMode((state) => state.setEditId);
|
const setEditId = useAppMode((state) => state.setEditId);
|
||||||
|
|
||||||
const editorStyle = cx([style.eventEditor, !editId ? style.noEvent : null]);
|
const editorStyle = cx([style.eventEditorContainer, !editId ? style.noEvent : null]);
|
||||||
const removeOpenEvent = () => setEditId(null);
|
const removeOpenEvent = () => setEditId(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -31,7 +23,12 @@ const EventEditorExport = () => {
|
|||||||
<div className={style.eventEditorLayout}>
|
<div className={style.eventEditorLayout}>
|
||||||
<EventEditor />
|
<EventEditor />
|
||||||
<div className={style.header}>
|
<div className={style.header}>
|
||||||
<IconButton aria-label='Close Menu' icon={<IoClose />} onClick={removeOpenEvent} {...closeBtnStyle} />
|
<IconButton
|
||||||
|
aria-label='Close Menu'
|
||||||
|
icon={<IoClose />}
|
||||||
|
onClick={removeOpenEvent}
|
||||||
|
variant='ontime-ghosted-white'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
@use '../../theme/ontimeColours' as *;
|
@use '../../theme/ontimeColours' as *;
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
grid-area: menu;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.gap {
|
.gap {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.open {
|
.open {
|
||||||
background: $blue-700;
|
background: $blue-700;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { memo, useCallback, useEffect, useState } from 'react';
|
import { memo, useCallback, useEffect, useState } from 'react';
|
||||||
import { VStack } from '@chakra-ui/react';
|
|
||||||
import { IoColorWand } from '@react-icons/all-files/io5/IoColorWand';
|
import { IoColorWand } from '@react-icons/all-files/io5/IoColorWand';
|
||||||
import { IoExtensionPuzzle } from '@react-icons/all-files/io5/IoExtensionPuzzle';
|
import { IoExtensionPuzzle } from '@react-icons/all-files/io5/IoExtensionPuzzle';
|
||||||
import { IoExtensionPuzzleOutline } from '@react-icons/all-files/io5/IoExtensionPuzzleOutline';
|
import { IoExtensionPuzzleOutline } from '@react-icons/all-files/io5/IoExtensionPuzzleOutline';
|
||||||
@@ -20,7 +19,7 @@ import ExportModal, { ExportType } from '../modals/export-modal/ExportModal';
|
|||||||
import style from './MenuBar.module.scss';
|
import style from './MenuBar.module.scss';
|
||||||
|
|
||||||
interface MenuBarProps {
|
interface MenuBarProps {
|
||||||
isSettingsOpen: boolean;
|
isOldSettingsOpen: boolean;
|
||||||
onSettingsOpen: () => void;
|
onSettingsOpen: () => void;
|
||||||
onSettingsClose: () => void;
|
onSettingsClose: () => void;
|
||||||
isUploadOpen: boolean;
|
isUploadOpen: boolean;
|
||||||
@@ -31,6 +30,8 @@ interface MenuBarProps {
|
|||||||
onAboutOpen: () => void;
|
onAboutOpen: () => void;
|
||||||
isQuickStartOpen: boolean;
|
isQuickStartOpen: boolean;
|
||||||
onQuickStartOpen: () => void;
|
onQuickStartOpen: () => void;
|
||||||
|
openSettings: (newTab?: string) => void;
|
||||||
|
isSettingsOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttonStyle = {
|
const buttonStyle = {
|
||||||
@@ -47,7 +48,7 @@ const buttonStyle = {
|
|||||||
|
|
||||||
const MenuBar = (props: MenuBarProps) => {
|
const MenuBar = (props: MenuBarProps) => {
|
||||||
const {
|
const {
|
||||||
isSettingsOpen,
|
isOldSettingsOpen,
|
||||||
onSettingsOpen,
|
onSettingsOpen,
|
||||||
onSettingsClose,
|
onSettingsClose,
|
||||||
isUploadOpen,
|
isUploadOpen,
|
||||||
@@ -58,6 +59,8 @@ const MenuBar = (props: MenuBarProps) => {
|
|||||||
onAboutOpen,
|
onAboutOpen,
|
||||||
isQuickStartOpen,
|
isQuickStartOpen,
|
||||||
onQuickStartOpen,
|
onQuickStartOpen,
|
||||||
|
openSettings,
|
||||||
|
isSettingsOpen,
|
||||||
} = props;
|
} = props;
|
||||||
const { isElectron, sendToElectron } = useElectronEvent();
|
const { isElectron, sendToElectron } = useElectronEvent();
|
||||||
|
|
||||||
@@ -118,7 +121,7 @@ const MenuBar = (props: MenuBarProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack>
|
<div className={style.menu}>
|
||||||
<QuitIconBtn disabled={!isElectron} clickHandler={sendShutdown} size='md' />
|
<QuitIconBtn disabled={!isElectron} clickHandler={sendShutdown} size='md' />
|
||||||
|
|
||||||
<div className={style.gap} />
|
<div className={style.gap} />
|
||||||
@@ -188,7 +191,7 @@ const MenuBar = (props: MenuBarProps) => {
|
|||||||
{...buttonStyle}
|
{...buttonStyle}
|
||||||
isDisabled={appMode === AppMode.Run}
|
isDisabled={appMode === AppMode.Run}
|
||||||
icon={<IoSettingsOutline />}
|
icon={<IoSettingsOutline />}
|
||||||
className={isSettingsOpen ? style.open : ''}
|
className={isOldSettingsOpen ? style.open : ''}
|
||||||
clickHandler={onSettingsOpen}
|
clickHandler={onSettingsOpen}
|
||||||
tooltip='Settings'
|
tooltip='Settings'
|
||||||
aria-label='Settings'
|
aria-label='Settings'
|
||||||
@@ -204,7 +207,17 @@ const MenuBar = (props: MenuBarProps) => {
|
|||||||
aria-label='About'
|
aria-label='About'
|
||||||
size='sm'
|
size='sm'
|
||||||
/>
|
/>
|
||||||
</VStack>
|
<div className={style.gap} />
|
||||||
|
<TooltipActionBtn
|
||||||
|
{...buttonStyle}
|
||||||
|
className={isSettingsOpen ? style.open : ''}
|
||||||
|
icon={<IoSettingsOutline />}
|
||||||
|
clickHandler={() => openSettings()}
|
||||||
|
tooltip='About'
|
||||||
|
aria-label='About'
|
||||||
|
size='sm'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ export const ontimeButtonSubtle = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: revise colours
|
||||||
|
export const ontimeButtonGhostedWhite = {
|
||||||
|
...ontimeButtonSubtle,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: 'white',
|
||||||
|
_hover: { background: '#ebedf0', color: '#333' },
|
||||||
|
};
|
||||||
|
|
||||||
export const ontimeButtonGhosted = {
|
export const ontimeButtonGhosted = {
|
||||||
...ontimeButtonSubtle,
|
...ontimeButtonSubtle,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ontimeAlertOnLight } from './OntimeAlert';
|
|||||||
import {
|
import {
|
||||||
ontimeButtonFilled,
|
ontimeButtonFilled,
|
||||||
ontimeButtonGhosted,
|
ontimeButtonGhosted,
|
||||||
|
ontimeButtonGhostedWhite,
|
||||||
ontimeButtonOutlined,
|
ontimeButtonOutlined,
|
||||||
ontimeButtonSubtle,
|
ontimeButtonSubtle,
|
||||||
ontimeButtonSubtleOnLight,
|
ontimeButtonSubtleOnLight,
|
||||||
@@ -46,6 +47,7 @@ const theme = extendTheme({
|
|||||||
'ontime-outlined': { ...ontimeButtonOutlined },
|
'ontime-outlined': { ...ontimeButtonOutlined },
|
||||||
'ontime-subtle': { ...ontimeButtonSubtle },
|
'ontime-subtle': { ...ontimeButtonSubtle },
|
||||||
'ontime-ghosted': { ...ontimeButtonGhosted },
|
'ontime-ghosted': { ...ontimeButtonGhosted },
|
||||||
|
'ontime-ghosted-white': { ...ontimeButtonGhostedWhite },
|
||||||
'ontime-subtle-white': { ...ontimeButtonSubtleWhite },
|
'ontime-subtle-white': { ...ontimeButtonSubtleWhite },
|
||||||
'ontime-subtle-on-light': { ...ontimeButtonSubtleOnLight },
|
'ontime-subtle-on-light': { ...ontimeButtonSubtleOnLight },
|
||||||
'ontime-ghost-on-light': { ...ontimeGhostOnLight },
|
'ontime-ghost-on-light': { ...ontimeGhostOnLight },
|
||||||
|
|||||||
@@ -413,7 +413,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"app": "ontime",
|
"app": "ontime",
|
||||||
"version": "2.0.0",
|
"version": "3.0.0-alpha",
|
||||||
"serverPort": 4001,
|
"serverPort": 4001,
|
||||||
"editorKey": null,
|
"editorKey": null,
|
||||||
"operatorKey": null,
|
"operatorKey": null,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ test.describe('pages routes are available', () => {
|
|||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
await expect(page).toHaveTitle(/ontime/);
|
await expect(page).toHaveTitle(/ontime/);
|
||||||
await expect(page.getByTestId('event-editor')).toBeVisible();
|
await expect(page.getByTestId('editor-container')).toBeVisible();
|
||||||
await expect(page.getByTestId('panel-rundown')).toBeVisible();
|
await expect(page.getByTestId('panel-rundown')).toBeVisible();
|
||||||
await expect(page.getByTestId('panel-timer-control')).toBeVisible();
|
await expect(page.getByTestId('panel-timer-control')).toBeVisible();
|
||||||
await expect(page.getByTestId('panel-messages-control')).toBeVisible();
|
await expect(page.getByTestId('panel-messages-control')).toBeVisible();
|
||||||
|
|||||||
Reference in New Issue
Block a user