From a463cb491b28c1f94803844973cdc043588d4b1d Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Mon, 25 Dec 2023 21:25:09 +0100 Subject: [PATCH] feat: app settings (#658) * feat: app settings * refactor: cleanup routes * style: smaller base font * chore: migrate about modal * fix: import links --- .../external-link/ExternalLink.module.scss | 20 +++ .../components/external-link/ExternalLink.tsx | 29 +++ apps/client/src/externals.ts | 1 + .../app-settings/AppSettings.module.scss | 13 ++ .../src/features/app-settings/AppSettings.tsx | 29 +++ .../panel-content/PanelContent.module.scss | 20 +++ .../panel-content/PanelContent.tsx | 22 +++ .../panel-list/PanelList.module.scss | 64 +++++++ .../app-settings/panel-list/PanelList.tsx | 55 ++++++ .../app-settings/panel/Panel.module.scss | 37 ++++ .../app-settings/panel/PanelUtils.tsx | 23 +++ .../panel/about-panel/AboutPanel.tsx | 35 ++++ .../panel/about-panel/CheckUpdatesButton.tsx | 73 ++++++++ .../features/app-settings/settingsStore.ts | 76 ++++++++ .../src/features/editors/Editor.module.scss | 169 +++--------------- apps/client/src/features/editors/Editor.tsx | 85 ++++++--- .../event-editor/EventEditor.module.scss | 31 ++++ .../event-editor/EventEditorExport.tsx | 19 +- .../src/features/menu/MenuBar.module.scss | 10 +- apps/client/src/features/menu/MenuBar.tsx | 25 ++- apps/client/src/theme/ontimeButton.ts | 8 + apps/client/src/theme/theme.ts | 2 + apps/test-db/db.json | 2 +- e2e/tests/000-smoke-tests.spec.ts | 2 +- 24 files changed, 666 insertions(+), 184 deletions(-) create mode 100644 apps/client/src/common/components/external-link/ExternalLink.module.scss create mode 100644 apps/client/src/common/components/external-link/ExternalLink.tsx create mode 100644 apps/client/src/features/app-settings/AppSettings.module.scss create mode 100644 apps/client/src/features/app-settings/AppSettings.tsx create mode 100644 apps/client/src/features/app-settings/panel-content/PanelContent.module.scss create mode 100644 apps/client/src/features/app-settings/panel-content/PanelContent.tsx create mode 100644 apps/client/src/features/app-settings/panel-list/PanelList.module.scss create mode 100644 apps/client/src/features/app-settings/panel-list/PanelList.tsx create mode 100644 apps/client/src/features/app-settings/panel/Panel.module.scss create mode 100644 apps/client/src/features/app-settings/panel/PanelUtils.tsx create mode 100644 apps/client/src/features/app-settings/panel/about-panel/AboutPanel.tsx create mode 100644 apps/client/src/features/app-settings/panel/about-panel/CheckUpdatesButton.tsx create mode 100644 apps/client/src/features/app-settings/settingsStore.ts diff --git a/apps/client/src/common/components/external-link/ExternalLink.module.scss b/apps/client/src/common/components/external-link/ExternalLink.module.scss new file mode 100644 index 000000000..a09a53ba0 --- /dev/null +++ b/apps/client/src/common/components/external-link/ExternalLink.module.scss @@ -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; + } +} diff --git a/apps/client/src/common/components/external-link/ExternalLink.tsx b/apps/client/src/common/components/external-link/ExternalLink.tsx new file mode 100644 index 000000000..25e4b0e21 --- /dev/null +++ b/apps/client/src/common/components/external-link/ExternalLink.tsx @@ -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 ( + + {children} + + ); +} diff --git a/apps/client/src/externals.ts b/apps/client/src/externals.ts index 64ed471ce..6dd7aec6b 100644 --- a/apps/client/src/externals.ts +++ b/apps/client/src/externals.ts @@ -1,4 +1,5 @@ export const githubUrl = 'https://www.github.com/cpvalente/ontime'; 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'; diff --git a/apps/client/src/features/app-settings/AppSettings.module.scss b/apps/client/src/features/app-settings/AppSettings.module.scss new file mode 100644 index 000000000..d0db47c0c --- /dev/null +++ b/apps/client/src/features/app-settings/AppSettings.module.scss @@ -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; +} diff --git a/apps/client/src/features/app-settings/AppSettings.tsx b/apps/client/src/features/app-settings/AppSettings.tsx new file mode 100644 index 000000000..07f7394cd --- /dev/null +++ b/apps/client/src/features/app-settings/AppSettings.tsx @@ -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 ( +
+ + + {selectedPanel === 'about' && } + +
+ ); +} diff --git a/apps/client/src/features/app-settings/panel-content/PanelContent.module.scss b/apps/client/src/features/app-settings/panel-content/PanelContent.module.scss new file mode 100644 index 000000000..8d4ed1fe7 --- /dev/null +++ b/apps/client/src/features/app-settings/panel-content/PanelContent.module.scss @@ -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; +} diff --git a/apps/client/src/features/app-settings/panel-content/PanelContent.tsx b/apps/client/src/features/app-settings/panel-content/PanelContent.tsx new file mode 100644 index 000000000..fbe215077 --- /dev/null +++ b/apps/client/src/features/app-settings/panel-content/PanelContent.tsx @@ -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) { + const { onClose, children } = props; + + return ( +
+
+ } variant='ontime-ghosted-white' /> +
+
{children}
+
+ ); +} diff --git a/apps/client/src/features/app-settings/panel-list/PanelList.module.scss b/apps/client/src/features/app-settings/panel-list/PanelList.module.scss new file mode 100644 index 000000000..3f92337a4 --- /dev/null +++ b/apps/client/src/features/app-settings/panel-list/PanelList.module.scss @@ -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; +} diff --git a/apps/client/src/features/app-settings/panel-list/PanelList.tsx b/apps/client/src/features/app-settings/panel-list/PanelList.tsx new file mode 100644 index 000000000..1a4282021 --- /dev/null +++ b/apps/client/src/features/app-settings/panel-list/PanelList.tsx @@ -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) => event.key === 'Enter'; + + return ( + + ); +} diff --git a/apps/client/src/features/app-settings/panel/Panel.module.scss b/apps/client/src/features/app-settings/panel/Panel.module.scss new file mode 100644 index 000000000..d4fe23e75 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/Panel.module.scss @@ -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; +} diff --git a/apps/client/src/features/app-settings/panel/PanelUtils.tsx b/apps/client/src/features/app-settings/panel/PanelUtils.tsx new file mode 100644 index 000000000..57aa6367f --- /dev/null +++ b/apps/client/src/features/app-settings/panel/PanelUtils.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from 'react'; + +import style from './Panel.module.scss'; + +export function Header({ children }: { children: ReactNode }) { + return

{children}

; +} + +export function SubHeader({ children }: { children: ReactNode }) { + return

{children}

; +} + +export function Section({ children }: { children: ReactNode }) { + return

{children}

; +} + +export function Paragraph({ children }: { children: ReactNode }) { + return

{children}

; +} + +export function Card({ children }: { children: ReactNode }) { + return
{children}
; +} diff --git a/apps/client/src/features/app-settings/panel/about-panel/AboutPanel.tsx b/apps/client/src/features/app-settings/panel/about-panel/AboutPanel.tsx new file mode 100644 index 000000000..60496e2ac --- /dev/null +++ b/apps/client/src/features/app-settings/panel/about-panel/AboutPanel.tsx @@ -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 ( + <> + About Ontime + + Ontime + + Free, open-source software for managing rundowns and event timers + www.getontime.no + + + + + Links + Read the docs over at GitBook + Follow the project on GitHub + + + + + Current version + {`You are currently using Ontime ${version}`} + + + + + ); +} diff --git a/apps/client/src/features/app-settings/panel/about-panel/CheckUpdatesButton.tsx b/apps/client/src/features/app-settings/panel/about-panel/CheckUpdatesButton.tsx new file mode 100644 index 000000000..1732a9689 --- /dev/null +++ b/apps/client/src/features/app-settings/panel/about-panel/CheckUpdatesButton.tsx @@ -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(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 ( + <> + + + + ); +} + +function ResolveUpdateMessage(props: { updateMessage: CheckRemote | null }) { + const { updateMessage } = props; + + if (updateMessage && 'error' in updateMessage) { + return {updateMessage.error}; + } + if (updateMessage && 'url' in updateMessage) { + return {`New version available: ${updateMessage.version}`}; + } + return null; +} diff --git a/apps/client/src/features/app-settings/settingsStore.ts b/apps/client/src/features/app-settings/settingsStore.ts new file mode 100644 index 000000000..4442562a8 --- /dev/null +++ b/apps/client/src/features/app-settings/settingsStore.ts @@ -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; + hasUnsavedChanges: (panelId: SettingsOptionId) => boolean; + addUnsavedChanges: (panelId: SettingsOptionId) => void; + removeUnsavedChanges: (panelId: SettingsOptionId) => void; +}; + +export const useSettingsStore = create((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) }; + }), +})); diff --git a/apps/client/src/features/editors/Editor.module.scss b/apps/client/src/features/editors/Editor.module.scss index 3d569eb5a..88d26b2ae 100644 --- a/apps/client/src/features/editors/Editor.module.scss +++ b/apps/client/src/features/editors/Editor.module.scss @@ -13,6 +13,7 @@ $playback-width: 26rem; color: $ui-white; transition-property: color; transition-duration: $transition-time-action; + &:hover { color: $ontime-color; } @@ -28,152 +29,54 @@ $playback-width: 26rem; background: $ui-black; width: 100%; height: 100%; - margin: auto; color: $ui-white; 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; 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: - 'sett rundown play info' - 'sett rundown mess info'; + 'rundown play info' + 'rundown mess info'; gap: 0.5rem; + overflow: hidden; .rundown, .playback, .messages, - .info, - .settings { + .info { position: relative; + border-radius: 8px; + height: 100%; + background-color: $bg-container-l2; + padding: 1rem; .corner { + /* we show this if the component hasnt been extracted */ 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 { grid-area: rundown; - height: 100%; .content { height: calc(100% - 1.5rem); @@ -204,22 +107,6 @@ $playback-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 { padding-top: 1.5rem; } diff --git a/apps/client/src/features/editors/Editor.tsx b/apps/client/src/features/editors/Editor.tsx index b195d7d6b..3d0abf275 100644 --- a/apps/client/src/features/editors/Editor.tsx +++ b/apps/client/src/features/editors/Editor.tsx @@ -2,6 +2,8 @@ import { lazy, useEffect } from 'react'; import { useDisclosure } from '@chakra-ui/react'; 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 AboutModal from '../modals/about-modal/AboutModal'; 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 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() { - 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: isIntegrationModalOpen, @@ -34,6 +59,8 @@ export default function Editor() { document.title = 'ontime - Editor'; }, []); + const isSettingsOpen = Boolean(showSettings); + return ( <> @@ -41,30 +68,42 @@ export default function Editor() { - + -
-
- - - +
+ + + + {showSettings ? ( + + ) : ( +
+ + + + +
+ )} +
+ + { + // TODO: the information about the event + }
- - - -
diff --git a/apps/client/src/features/event-editor/EventEditor.module.scss b/apps/client/src/features/event-editor/EventEditor.module.scss index 9ea11948b..82a7295b8 100644 --- a/apps/client/src/features/event-editor/EventEditor.module.scss +++ b/apps/client/src/features/event-editor/EventEditor.module.scss @@ -1,5 +1,36 @@ +@use '../../theme/ontimeColours' 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 { padding: 1rem 2rem 2rem 2rem; width: 100%; diff --git a/apps/client/src/features/event-editor/EventEditorExport.tsx b/apps/client/src/features/event-editor/EventEditorExport.tsx index 757117c4c..a845c342a 100644 --- a/apps/client/src/features/event-editor/EventEditorExport.tsx +++ b/apps/client/src/features/event-editor/EventEditorExport.tsx @@ -8,21 +8,13 @@ import { cx } from '../../common/utils/styleUtils'; import EventEditor from './EventEditor'; -import style from '../editors/Editor.module.scss'; - -/* Styling for action buttons */ -const closeBtnStyle = { - size: 'md', - variant: 'ghost', - colorScheme: 'white', - _hover: { bg: '#ebedf0', color: '#333' }, -}; +import style from './EventEditor.module.scss'; const EventEditorExport = () => { const editId = useAppMode((state) => state.editId); 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); return ( @@ -31,7 +23,12 @@ const EventEditorExport = () => {
- } onClick={removeOpenEvent} {...closeBtnStyle} /> + } + onClick={removeOpenEvent} + variant='ontime-ghosted-white' + />
diff --git a/apps/client/src/features/menu/MenuBar.module.scss b/apps/client/src/features/menu/MenuBar.module.scss index 197f8df2c..b1841a2d6 100644 --- a/apps/client/src/features/menu/MenuBar.module.scss +++ b/apps/client/src/features/menu/MenuBar.module.scss @@ -1,9 +1,17 @@ @use '../../theme/ontimeColours' as *; +.menu { + grid-area: menu; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5em; +} + .gap { height: 1em; } .open { background: $blue-700; -} \ No newline at end of file +} diff --git a/apps/client/src/features/menu/MenuBar.tsx b/apps/client/src/features/menu/MenuBar.tsx index 5281bc24d..91cbc1779 100644 --- a/apps/client/src/features/menu/MenuBar.tsx +++ b/apps/client/src/features/menu/MenuBar.tsx @@ -1,5 +1,4 @@ import { memo, useCallback, useEffect, useState } from 'react'; -import { VStack } from '@chakra-ui/react'; import { IoColorWand } from '@react-icons/all-files/io5/IoColorWand'; import { IoExtensionPuzzle } from '@react-icons/all-files/io5/IoExtensionPuzzle'; 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'; interface MenuBarProps { - isSettingsOpen: boolean; + isOldSettingsOpen: boolean; onSettingsOpen: () => void; onSettingsClose: () => void; isUploadOpen: boolean; @@ -31,6 +30,8 @@ interface MenuBarProps { onAboutOpen: () => void; isQuickStartOpen: boolean; onQuickStartOpen: () => void; + openSettings: (newTab?: string) => void; + isSettingsOpen: boolean; } const buttonStyle = { @@ -47,7 +48,7 @@ const buttonStyle = { const MenuBar = (props: MenuBarProps) => { const { - isSettingsOpen, + isOldSettingsOpen, onSettingsOpen, onSettingsClose, isUploadOpen, @@ -58,6 +59,8 @@ const MenuBar = (props: MenuBarProps) => { onAboutOpen, isQuickStartOpen, onQuickStartOpen, + openSettings, + isSettingsOpen, } = props; const { isElectron, sendToElectron } = useElectronEvent(); @@ -118,7 +121,7 @@ const MenuBar = (props: MenuBarProps) => { }; return ( - +
@@ -188,7 +191,7 @@ const MenuBar = (props: MenuBarProps) => { {...buttonStyle} isDisabled={appMode === AppMode.Run} icon={} - className={isSettingsOpen ? style.open : ''} + className={isOldSettingsOpen ? style.open : ''} clickHandler={onSettingsOpen} tooltip='Settings' aria-label='Settings' @@ -204,7 +207,17 @@ const MenuBar = (props: MenuBarProps) => { aria-label='About' size='sm' /> - +
+ } + clickHandler={() => openSettings()} + tooltip='About' + aria-label='About' + size='sm' + /> +
); }; diff --git a/apps/client/src/theme/ontimeButton.ts b/apps/client/src/theme/ontimeButton.ts index 50442c6a8..3ed4dc33e 100644 --- a/apps/client/src/theme/ontimeButton.ts +++ b/apps/client/src/theme/ontimeButton.ts @@ -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 = { ...ontimeButtonSubtle, backgroundColor: 'transparent', diff --git a/apps/client/src/theme/theme.ts b/apps/client/src/theme/theme.ts index 2378a270e..0515b96f9 100644 --- a/apps/client/src/theme/theme.ts +++ b/apps/client/src/theme/theme.ts @@ -4,6 +4,7 @@ import { ontimeAlertOnLight } from './OntimeAlert'; import { ontimeButtonFilled, ontimeButtonGhosted, + ontimeButtonGhostedWhite, ontimeButtonOutlined, ontimeButtonSubtle, ontimeButtonSubtleOnLight, @@ -46,6 +47,7 @@ const theme = extendTheme({ 'ontime-outlined': { ...ontimeButtonOutlined }, 'ontime-subtle': { ...ontimeButtonSubtle }, 'ontime-ghosted': { ...ontimeButtonGhosted }, + 'ontime-ghosted-white': { ...ontimeButtonGhostedWhite }, 'ontime-subtle-white': { ...ontimeButtonSubtleWhite }, 'ontime-subtle-on-light': { ...ontimeButtonSubtleOnLight }, 'ontime-ghost-on-light': { ...ontimeGhostOnLight }, diff --git a/apps/test-db/db.json b/apps/test-db/db.json index 6bbc1d0ea..acc0c718b 100644 --- a/apps/test-db/db.json +++ b/apps/test-db/db.json @@ -413,7 +413,7 @@ }, "settings": { "app": "ontime", - "version": "2.0.0", + "version": "3.0.0-alpha", "serverPort": 4001, "editorKey": null, "operatorKey": null, diff --git a/e2e/tests/000-smoke-tests.spec.ts b/e2e/tests/000-smoke-tests.spec.ts index 3901cf8af..c75cd006b 100644 --- a/e2e/tests/000-smoke-tests.spec.ts +++ b/e2e/tests/000-smoke-tests.spec.ts @@ -8,7 +8,7 @@ test.describe('pages routes are available', () => { await page.goto('http://localhost:4001/editor'); 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-timer-control')).toBeVisible(); await expect(page.getByTestId('panel-messages-control')).toBeVisible();