From bfb483508dfd7113226943586470e68666950cbe Mon Sep 17 00:00:00 2001 From: asharonbaltazar <58940073+asharonbaltazar@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:54:03 -0400 Subject: [PATCH] URL Params Form (#420) * feat create form to edit URL params in views --- .../EditFormDrawer.module.scss | 45 ++++ .../edit-form-drawer/EditFormDrawer.tsx | 95 ++++++++ .../edit-form-drawer/EditFormInput.tsx | 47 ++++ .../components/edit-form-drawer/constants.ts | 219 ++++++++++++++++++ .../components/edit-form-drawer/types.ts | 12 + .../NavigationMenu.module.scss | 35 ++- .../navigation-menu/NavigationMenu.tsx | 113 ++++----- apps/client/src/common/hooks/useKeyDown.ts | 27 ++- .../features/viewers/backstage/Backstage.tsx | 4 +- .../src/features/viewers/clock/Clock.tsx | 3 + .../features/viewers/countdown/Countdown.tsx | 3 + .../viewers/lower-thirds/LowerClean.jsx | 8 +- .../viewers/lower-thirds/LowerLines.jsx | 24 +- .../viewers/minimal-timer/MinimalTimer.tsx | 3 + .../src/features/viewers/public/Public.tsx | 4 +- .../features/viewers/studio/StudioClock.jsx | 3 + .../src/features/viewers/timer/Timer.tsx | 3 + 17 files changed, 549 insertions(+), 99 deletions(-) create mode 100644 apps/client/src/common/components/edit-form-drawer/EditFormDrawer.module.scss create mode 100644 apps/client/src/common/components/edit-form-drawer/EditFormDrawer.tsx create mode 100644 apps/client/src/common/components/edit-form-drawer/EditFormInput.tsx create mode 100644 apps/client/src/common/components/edit-form-drawer/constants.ts create mode 100644 apps/client/src/common/components/edit-form-drawer/types.ts diff --git a/apps/client/src/common/components/edit-form-drawer/EditFormDrawer.module.scss b/apps/client/src/common/components/edit-form-drawer/EditFormDrawer.module.scss new file mode 100644 index 000000000..108fcae6c --- /dev/null +++ b/apps/client/src/common/components/edit-form-drawer/EditFormDrawer.module.scss @@ -0,0 +1,45 @@ +@use '../../../theme/v2Styles' as *; +@use '../../../theme/ontimeColours' as *; + +.drawerContent { + background-color: $gray-1200; +} + +.drawerHeader { + @extend .drawerContent; + color: $section-white; +} + +.drawerFooter { + @extend .drawerContent; + display: flex; + gap: $element-spacing; + + button[type='submit'] { + padding: 0 2em; + } +} + +.label { + font-size: $inner-section-text-size; + color: $label-gray; +} + +.columnSection { + display: flex; + padding: $element-spacing; + flex-direction: column; + gap: $element-inner-spacing; +} + +.title { + font-size: $inner-section-text-size; + display: block; + width: 100%; +} + +.description { + font-size: $inner-section-text-size; + display: block; + color: $modal-note-color; +} diff --git a/apps/client/src/common/components/edit-form-drawer/EditFormDrawer.tsx b/apps/client/src/common/components/edit-form-drawer/EditFormDrawer.tsx new file mode 100644 index 000000000..9a68be302 --- /dev/null +++ b/apps/client/src/common/components/edit-form-drawer/EditFormDrawer.tsx @@ -0,0 +1,95 @@ +import { FormEvent, useEffect } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { + Button, + Drawer, + DrawerBody, + DrawerCloseButton, + DrawerContent, + DrawerFooter, + DrawerHeader, + DrawerOverlay, + useDisclosure, +} from '@chakra-ui/react'; + +import EditFormInput from './EditFormInput'; +import { ParamField } from './types'; + +import style from './EditFormDrawer.module.scss'; + +interface EditFormDrawerProps { + paramFields: ParamField[]; +} + +export default function EditFormDrawer({ paramFields }: EditFormDrawerProps) { + const [searchParams, setSearchParams] = useSearchParams(); + const { isOpen, onClose, onOpen } = useDisclosure(); + + useEffect(() => { + const isEditing = searchParams.get('edit'); + + if (isEditing === 'true') { + return onOpen(); + } + }, [searchParams, onOpen]); + + const onEditDrawerClose = () => { + onClose(); + + searchParams.delete('edit'); + setSearchParams(searchParams); + }; + + const onParamsFormSubmit = (formEvent: FormEvent) => { + formEvent.preventDefault(); + + const newParamsObject = Object.fromEntries(new FormData(formEvent.currentTarget)); + const newSearchParams = Object.entries(newParamsObject).reduce((newSearchParams, [id, value]) => { + if (typeof value === 'string' && value.length) { + newSearchParams.set(id, value); + + return newSearchParams; + } + + return newSearchParams; + }, new URLSearchParams()); + + onEditDrawerClose(); + setSearchParams(newSearchParams); + }; + + return ( + + + + + + Customise + + + +
+ {paramFields.map((field) => ( +
+ +
+ ))} +
+
+ + + + + +
+
+ ); +} diff --git a/apps/client/src/common/components/edit-form-drawer/EditFormInput.tsx b/apps/client/src/common/components/edit-form-drawer/EditFormInput.tsx new file mode 100644 index 000000000..6c2380921 --- /dev/null +++ b/apps/client/src/common/components/edit-form-drawer/EditFormInput.tsx @@ -0,0 +1,47 @@ +import { useSearchParams } from 'react-router-dom'; +import { Input, Select, Switch } from '@chakra-ui/react'; + +import { isStringBoolean } from '../../../common/utils/viewUtils'; + +import { ParamField } from './types'; + +interface EditFormInputProps { + paramField: ParamField; +} + +export default function EditFormInput({ paramField }: EditFormInputProps) { + const [searchParams] = useSearchParams(); + const { id, type } = paramField; + + if (type === 'option') { + const optionFromParams = searchParams.get(id); + const defaultOptionValue = paramField.values.find((value) => value === optionFromParams); + + return ( + + ); + } + + if (type === 'boolean') { + const defaultCheckedValue = isStringBoolean(searchParams.get(id)) ?? false; + + // checked value should be 'true', so it can be captured by the form event + return ; + } + + if (type === 'number') { + const defaultNumberValue = searchParams.get(id) ?? ''; + + return ; + } + + const defaultStringValue = searchParams.get(id) ?? ''; + + return ; +} diff --git a/apps/client/src/common/components/edit-form-drawer/constants.ts b/apps/client/src/common/components/edit-form-drawer/constants.ts new file mode 100644 index 000000000..ca9363e26 --- /dev/null +++ b/apps/client/src/common/components/edit-form-drawer/constants.ts @@ -0,0 +1,219 @@ +import { ParamField } from './types'; + +export const TIME_FORMAT_OPTION: ParamField = { + id: 'format', + title: '12 / 24 hour timer', + description: 'Whether to show the time in 12 or 24 hour mode. Overrides the global setting from preferences', + type: 'option', + values: ['12', '24'], +}; + +export const CLOCK_OPTIONS: ParamField[] = [ + { + id: 'key', + title: 'Key', + description: 'Background colour in hexadecimal', + type: 'string', + }, + { + id: 'text', + title: 'Text Colour', + description: 'Text colour in hexadecimal', + type: 'string', + }, + { + id: 'textbg', + title: 'Text Background', + description: 'Colour of text background in hexadecimal', + type: 'string', + }, + { + id: 'font', + title: 'Font', + description: 'Font family, will use the fonts available in the system', + type: 'string', + }, + { + id: 'size', + title: 'Text Size', + description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)', + type: 'number', + }, + { + id: 'alignx', + title: 'Align Horizontal', + description: 'Moves the horizontally in page to start = left | center | end = right', + type: 'option', + values: ['start', 'center', 'end'], + }, + { + id: 'offsetx', + title: 'Offset Horizontal', + description: 'Offsets the timer horizontal position by a given amount in pixels', + type: 'number', + }, + { + id: 'aligny', + title: 'Align Vertical', + description: 'Moves the vertically in page to start = left | center | end = right', + type: 'option', + values: ['start', 'center', 'end'], + }, + { + id: 'offsety', + title: 'Offset Vertical', + description: 'Offsets the timer vertical position by a given amount in pixels', + type: 'number', + }, + { + id: 'hidenav', + title: 'Hide Nav', + description: 'Whether to hide the nav logo in the right corner', + type: 'boolean', + }, + TIME_FORMAT_OPTION, +]; + +export const TIMER_OPTIONS: ParamField[] = [ + { + id: 'progress', + title: 'Progress Bar', + description: 'Whether bar counts up or down', + type: 'option', + values: ['up', 'down'], + }, + TIME_FORMAT_OPTION, +]; + +export const MINIMAL_TIMER_OPTIONS: ParamField[] = [ + { + id: 'key', + title: 'Key', + description: 'Background colour in hexadecimal', + type: 'string', + }, + { + id: 'text', + title: 'Text Colour', + description: 'Text colour in hexadecimal', + type: 'string', + }, + { + id: 'textbg', + title: 'Text Background', + description: 'Colour of text background in hexadecimal', + type: 'string', + }, + { + id: 'font', + title: 'Font', + description: 'Font family, will use the fonts available in the system', + type: 'string', + }, + { + id: 'size', + title: 'Text Size', + description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)', + type: 'number', + }, + { + id: 'alignx', + title: 'Align Horizontal', + description: 'Moves the horizontally in page to start = left | center | end = right', + type: 'option', + values: ['start', 'center', 'end'], + }, + { + id: 'offsetx', + title: 'Offset Horizontal', + description: 'Offsets the timer horizontal position by a given amount in pixels', + type: 'number', + }, + { + id: 'aligny', + title: 'Align Vertical', + description: 'Moves the vertically in page to start = left | center | end = right', + type: 'option', + values: ['start', 'center', 'end'], + }, + { + id: 'offsety', + title: 'Offset Vertical', + description: 'Offsets the timer vertical position by a given amount in pixels', + type: 'number', + }, + { + id: 'hidenav', + title: 'Hide Nav', + description: 'Whether to hide the nav logo in the right corner', + type: 'boolean', + }, + { + id: 'hideovertime', + title: 'Hide Overtime', + description: 'Whether to supress overtime styles (red borders and red text)', + type: 'boolean', + }, + { + id: 'hidemessages', + title: 'Hide Message Overlay', + description: 'Whether to hide the overlay from showing timer screen messages', + type: 'boolean', + }, +]; + +export const LOWER_THIRDS_OPTIONS: ParamField[] = [ + { + id: 'preset', + title: 'Preset', + description: 'Selects a style preset', + type: 'number', + }, + { + id: 'size', + title: 'Size', + description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)', + type: 'number', + }, + { + id: 'transition', + title: 'Transition', + description: 'Transition in time in seconds (default 5)', + type: 'number', + }, + { + id: 'text', + title: 'Text', + description: 'Text colour in hexadecimal', + type: 'string', + }, + { + id: 'bg', + title: 'BG', + description: 'Text background colour in hexadecimal', + type: 'string', + }, + { + id: 'key', + title: 'Key', + description: 'Screen background colour in hexadecimal', + type: 'string', + }, + { + id: 'fadeout', + title: 'Fadeout', + description: 'Time (in seconds) the lower third displays before fading out', + type: 'number', + }, + TIME_FORMAT_OPTION, +]; + +export const STUDIO_CLOCK_OPTIONS: ParamField[] = [ + { + id: 'seconds', + title: 'Seconds', + description: 'Shows seconds in clock', + type: 'boolean', + }, + TIME_FORMAT_OPTION, +]; diff --git a/apps/client/src/common/components/edit-form-drawer/types.ts b/apps/client/src/common/components/edit-form-drawer/types.ts new file mode 100644 index 000000000..de086c422 --- /dev/null +++ b/apps/client/src/common/components/edit-form-drawer/types.ts @@ -0,0 +1,12 @@ +type BaseField = { + id: string; + title: string; + description: string; +}; + +type OptionsField = { type: 'option'; values: string[] }; +type StringField = { type: 'string' }; +type BooleanField = { type: 'boolean' }; +type NumberField = { type: 'number' }; + +export type ParamField = BaseField & (StringField | BooleanField | NumberField | OptionsField); diff --git a/apps/client/src/common/components/navigation-menu/NavigationMenu.module.scss b/apps/client/src/common/components/navigation-menu/NavigationMenu.module.scss index 1f7b671e2..dcfb044dc 100644 --- a/apps/client/src/common/components/navigation-menu/NavigationMenu.module.scss +++ b/apps/client/src/common/components/navigation-menu/NavigationMenu.module.scss @@ -1,6 +1,6 @@ -@use "../../../theme/v2Styles" as *; -@use "../../../theme/mixins" as *; -@use "../../../theme/ontimeColours" as *; +@use '../../../theme/v2Styles' as *; +@use '../../../theme/mixins' as *; +@use '../../../theme/ontimeColours' as *; $menu-bg: $gray-1200; $menu-hover-bg: $gray-1350; @@ -14,14 +14,26 @@ $button-size: 48px; transform: rotate(180deg); } -.navButton { - z-index: 2; - position: absolute; - left: 0.5em; - top: 0.5em; +.buttonContainer { + display: flex; + flex-direction: column; + row-gap: 1rem; + padding: 0.5em; + transition-property: opacity; transition-duration: 0.3s; + opacity: 1; + position: fixed; + left: 0; + top: 0; + + &.hidden { + opacity: 0; + } +} + +.button { font-size: 24px; color: $icon-color; background-color: $button-bg; @@ -30,10 +42,11 @@ $button-size: 48px; display: grid; place-content: center; border-radius: 3px; +} - &.hidden { - opacity: 0; - } +.navButton { + @extend .button; + z-index: 2; } .menuContainer { diff --git a/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx b/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx index db7360ae5..7195e668e 100644 --- a/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx +++ b/apps/client/src/common/components/navigation-menu/NavigationMenu.tsx @@ -1,10 +1,11 @@ import { KeyboardEvent, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import { Link, useLocation } from 'react-router-dom'; +import { Link, useLocation, useSearchParams } from 'react-router-dom'; import { IoApps } from '@react-icons/all-files/io5/IoApps'; import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp'; import { IoContract } from '@react-icons/all-files/io5/IoContract'; import { IoExpand } from '@react-icons/all-files/io5/IoExpand'; +import { IoPencilSharp } from '@react-icons/all-files/io5/IoPencilSharp'; import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical'; import { navigatorConstants } from '../../../viewerConfig'; @@ -21,12 +22,13 @@ export default function NavigationMenu() { const { isFullScreen, toggleFullScreen } = useFullscreen(); const { mirror, toggleMirror } = useViewOptionsStore(); const [showButton, setShowButton] = useState(false); + const [searchParams, setSearchParams] = useSearchParams(); const [showMenu, setShowMenu] = useState(false); const menuRef = useRef(null); useClickOutside(menuRef, () => setShowMenu(false)); const toggleMenu = () => setShowMenu((prev) => !prev); - useKeyDown(toggleMenu, ' '); + useKeyDown(toggleMenu, ' ', { isDisabled: searchParams.get('edit') === 'true' }); useEffect(() => { let fadeOut: NodeJS.Timeout | null = null; @@ -49,63 +51,68 @@ export default function NavigationMenu() { const isKeyEnter = (event: KeyboardEvent) => event.key === 'Enter'; const handleFullscreen = () => toggleFullScreen(); const handleMirror = () => toggleMirror(); + const showEditFormDrawer = () => { + searchParams.append('edit', 'true'); + setSearchParams(searchParams); + }; return createPortal(