From bab45abef57e8c99118793955ac111943fd66cd8 Mon Sep 17 00:00:00 2001 From: Arnei Date: Thu, 18 Jan 2024 14:17:50 +0100 Subject: [PATCH] Enforce eslint semicolons --- .eslintrc.js | 4 - src/config.ts | 32 +-- src/cssStyles.tsx | 50 ++-- src/globalKeys.ts | 32 +-- src/i18n/config.tsx | 2 +- src/index.tsx | 12 +- src/main/Body.tsx | 12 +- src/main/Cutting.tsx | 37 +-- src/main/CuttingActions.tsx | 56 ++-- src/main/Discard.tsx | 34 +-- src/main/Error.tsx | 24 +- src/main/Finish.tsx | 58 ++-- src/main/FinishMenu.tsx | 40 +-- src/main/Header.tsx | 58 ++-- src/main/KeyboardControls.tsx | 50 ++-- src/main/Landing.tsx | 10 +- src/main/Lock.tsx | 62 ++--- src/main/MainContent.tsx | 58 ++-- src/main/MainMenu.tsx | 36 +-- src/main/Metadata.tsx | 250 ++++++++--------- src/main/Save.tsx | 140 +++++----- src/main/Subtitle.tsx | 8 +- src/main/SubtitleEditor.tsx | 82 +++--- src/main/SubtitleListEditor.tsx | 274 ++++++++++--------- src/main/SubtitleSelect.tsx | 171 ++++++------ src/main/SubtitleTimeline.tsx | 170 ++++++------ src/main/SubtitleVideoArea.tsx | 88 +++--- src/main/TheEnd.tsx | 36 +-- src/main/Thumbnail.tsx | 232 ++++++++-------- src/main/Timeline.tsx | 208 +++++++------- src/main/Tooltip.tsx | 4 +- src/main/TrackSelection.tsx | 54 ++-- src/main/VideoControls.tsx | 100 +++---- src/main/VideoPlayers.tsx | 174 ++++++------ src/main/WorkflowConfiguration.tsx | 104 +++---- src/main/WorkflowSelection.tsx | 106 ++++---- src/redux/endSlice.ts | 16 +- src/redux/errorSlice.ts | 32 +-- src/redux/finishSlice.ts | 14 +- src/redux/mainMenuSlice.ts | 14 +- src/redux/metadataSlice.ts | 124 ++++----- src/redux/store.ts | 28 +- src/redux/subtitleSlice.ts | 196 +++++++------- src/redux/videoSlice.ts | 330 ++++++++++++----------- src/redux/workflowPostAndProcessSlice.ts | 66 ++--- src/redux/workflowPostSlice.ts | 74 ++--- src/themes.ts | 132 ++++----- src/util/client.js | 42 +-- src/util/utilityFunctions.ts | 112 ++++---- src/util/waveform.js | 10 +- 50 files changed, 2063 insertions(+), 1995 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 82c508a8a..86a8a2665 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,10 +4,6 @@ module.exports = { "react-app/jest" ], rules: { - // Semicolon usage is inconsistent right now. Both, "always" and "never", - // result in a significant number of warnings. - "semi": "off", - // Both kinds of quotes are used a lot "quotes": "off", diff --git a/src/config.ts b/src/config.ts index 1ce4ca06a..966744ea5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -51,7 +51,7 @@ interface iSettings { }, metadata: { show: boolean, - configureFields: { [key: string]: { [key: string]: configureFieldsAttributes } } | undefined, + configureFields: { [key: string]: { [key: string]: configureFieldsAttributes; }; } | undefined, }, trackSelection: { show: boolean, @@ -63,10 +63,10 @@ interface iSettings { subtitles: { show: boolean, mainFlavor: string, - languages: { [key: string]: subtitleTags } | undefined, - icons: { [key: string]: string } | undefined, + languages: { [key: string]: subtitleTags; } | undefined, + icons: { [key: string]: string; } | undefined, defaultVideoFlavor: Flavor | undefined, - } + }; } /** @@ -105,10 +105,10 @@ const defaultSettings: iSettings = { icons: undefined, defaultVideoFlavor: undefined, } -} -let configFileSettings: iSettings -let urlParameterSettings: iSettings -export let settings: iSettings +}; +let configFileSettings: iSettings; +let urlParameterSettings: iSettings; +export let settings: iSettings; /** * Entry point. Loads values from settings into the exported variables @@ -130,8 +130,8 @@ export const init = async () => { // Get settings from config file await loadContextSettings().then(result => { - configFileSettings = validate(result, false, SRC_SERVER, "from server settings file") - }) + configFileSettings = validate(result, false, SRC_SERVER, "from server settings file"); + }); // Get settings from URL query. const urlParams = new URLSearchParams(window.location.search); @@ -140,7 +140,7 @@ export const init = async () => { urlParams.forEach((value, key) => { // Create empty objects for full path (if the key contains '.') and set // the value at the end. - let obj : {[k: string]: any} = rawUrlSettings; + let obj: { [k: string]: any; } = rawUrlSettings; if (key.startsWith('opencast.') || key === 'allowedCallbackPrefixes') { return; } @@ -256,7 +256,7 @@ const validate = (obj: Record | null, allowParse: boolean, src: str } catch (e) { console.warn( `Validation of setting '${path}' (${sourceDescription}) with value '${value}' failed: ` - + `${e}. Ignoring.` + + `${e}. Ignoring.` ); return null; } @@ -267,7 +267,7 @@ const validate = (obj: Record | null, allowParse: boolean, src: str const validateObj = (schema: any, obj: Record | null, path: string) => { // We iterate through all keys of the given settings object, checking if // each key is valid and recursively validating the value of that key. - const out : {[k: string]: any} = {}; + const out: { [k: string]: any; } = {}; for (const key in obj) { const newPath = path ? `${path}.${key}` : key; if (key in schema) { @@ -289,7 +289,7 @@ const validate = (obj: Record | null, allowParse: boolean, src: str }; return validate(SCHEMA, obj, ""); -} +}; // Validation functions for different types. @@ -399,10 +399,10 @@ const SCHEMA = { show: types.boolean, simpleMode: types.boolean, } -} +}; const merge = (a: iSettings, b: iSettings) => { return deepmerge(a, b, { arrayMerge }); }; -merge.all = (array: object[]) => deepmerge.all(array, { arrayMerge }) +merge.all = (array: object[]) => deepmerge.all(array, { arrayMerge }); const arrayMerge = (_destinationArray: any, sourceArray: any, _options: any) => sourceArray; diff --git a/src/cssStyles.tsx b/src/cssStyles.tsx index 427b301f4..9b0002220 100644 --- a/src/cssStyles.tsx +++ b/src/cssStyles.tsx @@ -1,7 +1,7 @@ /** * This file contains general css stylings */ -import { css, Global, keyframes } from '@emotion/react' +import { css, Global, keyframes } from '@emotion/react'; import React from "react"; import emotionNormalize from 'emotion-normalize'; import { checkFlexGapSupport } from './util/utilityFunctions'; @@ -17,7 +17,7 @@ export const GlobalStyle: React.FC = () => { return ( ); -} +}; /** * CSS for the global style component @@ -46,13 +46,13 @@ export const BREAKPOINT_MEDIUM = 650; */ export const flexGapReplacementStyle = (flexGapValue: number, flexDirectionIsRow: boolean) => { - const half = flexGapValue / 2 - const quarter = flexGapValue / 4 + const half = flexGapValue / 2; + const quarter = flexGapValue / 4; return ( { - // Use gap if supported - ...(checkFlexGapSupport()) && {gap: `${flexGapValue}px`}, + // Use gap if supported + ...(checkFlexGapSupport()) && { gap: `${flexGapValue}px` }, // Else use margins ...(!checkFlexGapSupport()) && { @@ -73,7 +73,7 @@ export const flexGapReplacementStyle = (flexGapValue: number, flexDirectionIsRow } } ); -} +}; /** * CSS for buttons @@ -122,7 +122,7 @@ export const navigationButtonStyle = (theme: Theme) => css({ justifyContent: 'space-around', boxShadow: `${theme.boxShadow}`, background: `${theme.element_bg}`, -}) +}); /** * CSS for a container that holds back/forward buttons @@ -131,7 +131,7 @@ export const backOrContinueStyle = css(({ display: 'flex', flexDirection: 'row', ...(flexGapReplacementStyle(20, false)), -})) +})); /** * CSS for big buttons in a dynamic grid @@ -161,7 +161,7 @@ export const disableButtonAnimation = css({ "&:active": { transform: 'none', }, -}) +}); /** * CSS for a title @@ -173,7 +173,7 @@ export const titleStyle = (theme: Theme) => css(({ textOverflow: 'ellipsis', maxWidth: '100%', color: `${theme.text}`, -})) +})); /** * Addendum for the titleStyle @@ -184,7 +184,7 @@ export const titleStyleBold = (theme: Theme) => css({ fontSize: '24px', verticalAlign: '-2.5px', color: `${theme.text}`, -}) +}); /** * CSS for ariaLive regions that should not be visible @@ -195,7 +195,7 @@ export const ariaLive = css({ height: '1px', width: '1px', overflow: 'hidden', -}) +}); /** * CSS for displaying of errors @@ -203,22 +203,22 @@ export const ariaLive = css({ export const errorBoxStyle = (errorStatus: boolean, theme: Theme) => { return ( css({ - ...(!errorStatus) && {display: "none"}, + ...(!errorStatus) && { display: "none" }, borderColor: `${theme.error}`, borderStyle: 'dashed', fontWeight: 'bold', padding: '10px', }) ); -} +}; export function selectFieldStyle(theme: Theme) { return { control: (provided: any, state: any) => ({ ...provided, background: theme.menu_background, - ...(state.isFocused && {borderColor: theme.metadata_highlight}), - ...(state.isFocused && {boxShadow: `0 0 0 1px ${theme.metadata_highlight}`}), + ...(state.isFocused && { borderColor: theme.metadata_highlight }), + ...(state.isFocused && { boxShadow: `0 0 0 1px ${theme.metadata_highlight}` }), "&:hover": { borderColor: theme.menu_background, boxShadow: `0 0 0 1px ${theme.metadata_highlight}` @@ -249,7 +249,7 @@ export function selectFieldStyle(theme: Theme) { ...provided, background: state.isFocused ? theme.focused : theme.menu_background && state.isSelected ? theme.selected : theme.menu_background, - ...(state.isFocused && {color: theme.focus_text}), + ...(state.isFocused && { color: theme.focus_text }), color: state.isFocused ? theme.focus_text : theme.text && state.isSelected ? theme.selected_text : theme.text, }), @@ -273,7 +273,7 @@ export function selectFieldStyle(theme: Theme) { ...provided, color: theme.text, }), - } + }; } export const calendarStyle = (theme: Theme) => createTheme({ @@ -368,7 +368,7 @@ export const calendarStyle = (theme: Theme) => createTheme({ } }, } -}) +}); export const subtitleSelectStyle = (theme: Theme) => createTheme({ components: { @@ -427,19 +427,19 @@ export const subtitleSelectStyle = (theme: Theme) => createTheme({ } } } -}) +}); export const spinningStyle = css({ animation: `2s linear infinite none ${keyframes({ "0%": { transform: "rotate(0)" }, "100%": { transform: "rotate(360deg)" }, })}`, -}) +}); export const customIconStyle = css(({ maxWidth: '16px', height: 'auto' -})) +})); export const videosStyle = (theme: Theme) => css(({ display: 'flex', @@ -453,7 +453,7 @@ export const videosStyle = (theme: Theme) => css(({ boxSizing: "border-box", padding: '10px', ...(flexGapReplacementStyle(10, false)), -})) +})); export const backgroundBoxStyle = (theme: Theme) => css(({ background: `${theme.menu_background}`, @@ -462,4 +462,4 @@ export const backgroundBoxStyle = (theme: Theme) => css(({ boxSizing: "border-box", padding: '20px', ...(flexGapReplacementStyle(25, false)), -})) +})); diff --git a/src/globalKeys.ts b/src/globalKeys.ts index 0476d48ff..bd203a02f 100644 --- a/src/globalKeys.ts +++ b/src/globalKeys.ts @@ -12,43 +12,43 @@ import { ParseKeys } from 'i18next'; import { isMacOs } from 'react-device-detect'; // Groups for displaying hotkeys in the overview page -const groupVideoPlayer = "keyboardControls.groupVideoPlayer" -const groupCuttingView = 'keyboardControls.groupCuttingView' -const groupCuttingViewScrubber = 'keyboardControls.groupCuttingViewScrubber' -const groupSubtitleList = "keyboardControls.groupSubtitleList" +const groupVideoPlayer = "keyboardControls.groupVideoPlayer"; +const groupCuttingView = 'keyboardControls.groupCuttingView'; +const groupCuttingViewScrubber = 'keyboardControls.groupCuttingViewScrubber'; +const groupSubtitleList = "keyboardControls.groupSubtitleList"; /** * Helper function that rewrites keys based on the OS */ export const rewriteKeys = (key: string) => { - let newKey = key + let newKey = key; if (isMacOs) { - newKey = newKey.replace("Alt", "Option") + newKey = newKey.replace("Alt", "Option"); } - return newKey -} + return newKey; +}; -export const getGroupName = (groupName: string) : ParseKeys => { +export const getGroupName = (groupName: string): ParseKeys => { return match(groupName, { videoPlayer: () => groupVideoPlayer, cutting: () => groupCuttingView, timeline: () => groupCuttingViewScrubber, subtitleList: () => groupSubtitleList, - }) -} + }); +}; export interface IKeyMap { - [property: string]: IKeyGroup + [property: string]: IKeyGroup; } export interface IKeyGroup { - [property: string]: IKey + [property: string]: IKey; } export interface IKey { - name: string - key: string + name: string; + key: string; } export const KEYMAP: IKeyMap = { @@ -120,4 +120,4 @@ export const KEYMAP: IKeyMap = { key: "Shift+Alt+d", } } -} +}; diff --git a/src/i18n/config.tsx b/src/i18n/config.tsx index 38fa4abc3..617914d0e 100644 --- a/src/i18n/config.tsx +++ b/src/i18n/config.tsx @@ -10,7 +10,7 @@ const resources: InitOptions["resources"] = {}; for (const lang of locales) { const code = lang.replace(/\..*$/, ''); const short = code.replace(/-.*$/, ''); - const main = locales.filter(l => l.indexOf(short) === 0).length === 1 + const main = locales.filter(l => l.indexOf(short) === 0).length === 1; /* eslint-disable-next-line @typescript-eslint/no-var-requires */ const translations = require('./locales/' + lang); if (!main) { diff --git a/src/index.tsx b/src/index.tsx index 4ce4aa329..23f60cedd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,20 +2,20 @@ import React from 'react'; import ReactDOMClient from 'react-dom/client'; import './index.css'; import App from './App'; -import { Provider } from 'react-redux' -import store from './redux/store' +import { Provider } from 'react-redux'; +import store from './redux/store'; -import { init } from './config' -import { sleep } from './util/utilityFunctions' +import { init } from './config'; +import { sleep } from './util/utilityFunctions'; import "@fontsource-variable/roboto-flex"; import './i18n/config'; -import '@opencast/appkit/dist/colors.css' +import '@opencast/appkit/dist/colors.css'; import { ColorSchemeProvider } from '@opencast/appkit'; -const container = document.getElementById('root') +const container = document.getElementById('root'); if (!container) { throw new Error('Failed to find the root element'); } diff --git a/src/main/Body.tsx b/src/main/Body.tsx index e1b1cdbd5..545a80974 100644 --- a/src/main/Body.tsx +++ b/src/main/Body.tsx @@ -7,17 +7,17 @@ import Error from './Error'; import Landing from "./Landing"; import Lock from "./Lock"; -import { css } from '@emotion/react' +import { css } from '@emotion/react'; import { useAppSelector } from "../redux/store"; -import { selectIsEnd } from '../redux/endSlice' +import { selectIsEnd } from '../redux/endSlice'; import { selectIsError } from "../redux/errorSlice"; import { settings } from '../config'; const Body: React.FC = () => { - const isEnd = useAppSelector(selectIsEnd) - const isError = useAppSelector(selectIsError) + const isEnd = useAppSelector(selectIsEnd); + const isError = useAppSelector(selectIsError); // If we're in a special state, display a special page // Otherwise display the normal page @@ -25,7 +25,7 @@ const Body: React.FC = () => { if (!settings.id) { return ( - ) + ); } else if (isEnd) { return (
@@ -46,7 +46,7 @@ const Body: React.FC = () => {
); } - } + }; const bodyStyle = css({ display: 'flex', diff --git a/src/main/Cutting.tsx b/src/main/Cutting.tsx index c96d92d7c..397ffc0e6 100644 --- a/src/main/Cutting.tsx +++ b/src/main/Cutting.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from "react"; -import CuttingActions from "./CuttingActions" +import CuttingActions from "./CuttingActions"; import Timeline from './Timeline'; import { fetchVideoInformation, @@ -35,18 +35,19 @@ const Cutting: React.FC = () => { // Init redux variables const dispatch = useAppDispatch(); - const videoURLStatus = useAppSelector((state: { videoState: { status: httpRequestState["status"] } }) => + const videoURLStatus = useAppSelector((state: { videoState: { status: httpRequestState["status"]; }; }) => state.videoState.status); - const error = useAppSelector((state: { videoState: { error: httpRequestState["error"] } }) => state.videoState.error) - const duration = useAppSelector(selectDuration) + const error = useAppSelector((state: { videoState: { error: httpRequestState["error"]; }; }) => + state.videoState.error); + const duration = useAppSelector(selectDuration); const theme = useTheme(); - const errorReason = useAppSelector((state: { videoState: { errorReason: httpRequestState["errorReason"] } }) => - state.videoState.errorReason) + const errorReason = useAppSelector((state: { videoState: { errorReason: httpRequestState["errorReason"]; }; }) => + state.videoState.errorReason); // Try to fetch URL from external API useEffect(() => { if (videoURLStatus === 'idle') { - dispatch(fetchVideoInformation()) + dispatch(fetchVideoInformation()); } else if (videoURLStatus === 'failed') { if (errorReason === 'workflowActive') { dispatch(setError({ @@ -54,13 +55,13 @@ const Cutting: React.FC = () => { errorTitle: t("error.workflowActive-errorTitle"), errorMessage: t("error.workflowActive-errorMessage"), errorIcon: LuMoreHorizontal - })) + })); } else { dispatch(setError({ error: true, errorMessage: t("video.comError-text"), errorDetails: error - })) + })); } } else if (videoURLStatus === 'success') { if (duration === null) { @@ -68,10 +69,10 @@ const Cutting: React.FC = () => { error: true, errorMessage: t("video.durationError-text"), errorDetails: error - })) + })); } } - }, [videoURLStatus, dispatch, error, t, errorReason, duration]) + }, [videoURLStatus, dispatch, error, t, errorReason, duration]); // Style const cuttingStyle = css({ @@ -86,7 +87,7 @@ const Cutting: React.FC = () => { return (
- +
{ />
- ) -} + ); +}; const CuttingHeader: React.FC = () => { - const title = useAppSelector(selectTitle) - const metadataTitle = useAppSelector(selectTitleFromEpisodeDc) + const title = useAppSelector(selectTitle); + const metadataTitle = useAppSelector(selectTitleFromEpisodeDc); const theme = useTheme(); return ( @@ -125,6 +126,6 @@ const CuttingHeader: React.FC = () => { {metadataTitle ? metadataTitle : title} ); -} +}; -export default Cutting +export default Cutting; diff --git a/src/main/CuttingActions.tsx b/src/main/CuttingActions.tsx index c33ce32e5..4570672ca 100644 --- a/src/main/CuttingActions.tsx +++ b/src/main/CuttingActions.tsx @@ -1,17 +1,17 @@ import React from "react"; -import { basicButtonStyle, customIconStyle } from '../cssStyles' +import { basicButtonStyle, customIconStyle } from '../cssStyles'; import { IconType } from "react-icons"; -import { LuScissors, LuChevronLeft, LuChevronRight, LuTrash, LuMoveHorizontal} from "react-icons/lu"; +import { LuScissors, LuChevronLeft, LuChevronRight, LuTrash, LuMoveHorizontal } from "react-icons/lu"; import { ReactComponent as TrashRestore } from '../img/trash-restore.svg'; -import { css } from '@emotion/react' +import { css } from '@emotion/react'; import { useAppDispatch, useAppSelector } from "../redux/store"; import { cut, markAsDeletedOrAlive, selectIsCurrentSegmentAlive, mergeLeft, mergeRight, mergeAll -} from '../redux/videoSlice' +} from '../redux/videoSlice'; import { KEYMAP, rewriteKeys } from "../globalKeys"; import { ActionCreatorWithoutPayload } from "@reduxjs/toolkit"; @@ -37,37 +37,37 @@ const CuttingActions: React.FC = () => { * @param ref Pass a reference if the clicked element should lose focus */ const dispatchAction = (action: ActionCreatorWithoutPayload, ref?: React.RefObject) => { - dispatch(action()) + dispatch(action()); // Lose focus if clicked by mouse if (ref) { - ref.current?.blur() + ref.current?.blur(); } - } + }; // Maps functions to hotkeys useHotkeys( KEYMAP.cutting.cut.key, () => dispatchAction(cut), - {preventDefault: true}, + { preventDefault: true }, [cut] ); useHotkeys( KEYMAP.cutting.delete.key, () => dispatchAction(markAsDeletedOrAlive), - {preventDefault: true}, + { preventDefault: true }, [markAsDeletedOrAlive] ); useHotkeys( KEYMAP.cutting.mergeLeft.key, () => dispatchAction(mergeLeft), - {preventDefault: true}, + { preventDefault: true }, [mergeLeft] ); useHotkeys( KEYMAP.cutting.mergeRight.key, () => dispatchAction(mergeRight), - {preventDefault: true}, + { preventDefault: true }, [mergeRight] ); @@ -76,12 +76,12 @@ const CuttingActions: React.FC = () => { flexDirection: 'row' as const, justifyContent: 'center', alignItems: 'center', - }) + }); const verticalLineStyle = css({ borderLeft: '2px solid #DDD;', height: '32px', - }) + }); return (
@@ -111,7 +111,7 @@ const CuttingActions: React.FC = () => { actionName={t("cuttingActions.mergeRight-button")} actionHandler={dispatchAction} action={mergeRight} - tooltip={t('cuttingActions.mergeRight-tooltip', { hotkeyName: rewriteKeys(KEYMAP.cutting.mergeRight.key)})} + tooltip={t('cuttingActions.mergeRight-tooltip', { hotkeyName: rewriteKeys(KEYMAP.cutting.mergeRight.key) })} ariaLabelText={ t('cuttingActions.mergeRight-tooltip-aria', { hotkeyName: rewriteKeys(KEYMAP.cutting.mergeRight.key) }) } @@ -166,7 +166,7 @@ const CuttingActionsButton: React.FC = ({ tooltip, ariaLabelText }) => { - const ref = React.useRef(null) + const ref = React.useRef(null); const theme = useTheme(); return ( @@ -175,9 +175,11 @@ const CuttingActionsButton: React.FC = ({ ref={ref} role="button" tabIndex={0} aria-label={ariaLabelText} onClick={() => actionHandler(action, ref)} - onKeyDown={(event: React.KeyboardEvent) => { if (event.key === " " || event.key === "Enter") { - actionHandler(action) - } }} + onKeyDown={(event: React.KeyboardEvent) => { + if (event.key === " " || event.key === "Enter") { + actionHandler(action); + } + }} > {actionName} @@ -195,14 +197,14 @@ interface markAsDeleteButtonInterface { /** * Button that changes its function based on context */ -const MarkAsDeletedButton : React.FC = ({ +const MarkAsDeletedButton: React.FC = ({ actionHandler, action, hotKeyName }) => { const { t } = useTranslation(); - const isCurrentSegmentAlive = useAppSelector(selectIsCurrentSegmentAlive) - const ref = React.useRef(null) + const isCurrentSegmentAlive = useAppSelector(selectIsCurrentSegmentAlive); + const ref = React.useRef(null); const theme = useTheme(); @@ -213,15 +215,17 @@ const MarkAsDeletedButton : React.FC = ({ role="button" tabIndex={0} aria-label={t('cuttingActions.delete-restore-tooltip-aria', { hotkeyName: hotKeyName })} onClick={() => actionHandler(action, ref)} - onKeyDown={(event: React.KeyboardEvent) => { if (event.key === " " || event.key === "Enter") { - actionHandler(action) - } }} + onKeyDown={(event: React.KeyboardEvent) => { + if (event.key === " " || event.key === "Enter") { + actionHandler(action); + } + }} > - {isCurrentSegmentAlive ? : } + {isCurrentSegmentAlive ? : }
{isCurrentSegmentAlive ? t('cuttingActions.delete-button') : t("cuttingActions.restore-button")}
); -} +}; export default CuttingActions; diff --git a/src/main/Discard.tsx b/src/main/Discard.tsx index 666204f74..383ff2c51 100644 --- a/src/main/Discard.tsx +++ b/src/main/Discard.tsx @@ -1,15 +1,15 @@ import React from "react"; -import { css } from '@emotion/react' -import { basicButtonStyle, backOrContinueStyle, navigationButtonStyle, flexGapReplacementStyle} from '../cssStyles' +import { css } from '@emotion/react'; +import { basicButtonStyle, backOrContinueStyle, navigationButtonStyle, flexGapReplacementStyle } from '../cssStyles'; import { LuChevronLeft, LuXCircle } from "react-icons/lu"; import { useAppDispatch, useAppSelector } from "../redux/store"; -import { selectFinishState } from '../redux/finishSlice' -import { setEnd } from '../redux/endSlice' +import { selectFinishState } from '../redux/finishSlice'; +import { setEnd } from '../redux/endSlice'; -import { PageButton } from './Finish' +import { PageButton } from './Finish'; import { useTranslation } from 'react-i18next'; import { useTheme } from "../themes"; @@ -18,18 +18,18 @@ import { useTheme } from "../themes"; * Shown if the user wishes to abort. * Informs the user about aborting and displays abort button. */ -const Discard : React.FC = () => { +const Discard: React.FC = () => { const { t } = useTranslation(); - const finishState = useAppSelector(selectFinishState) + const finishState = useAppSelector(selectFinishState); const cancelStyle = css({ display: finishState !== "Discard changes" ? 'none' : 'flex', flexDirection: 'column' as const, alignItems: 'center', ...(flexGapReplacementStyle(30, false)), - }) + }); return (
@@ -43,12 +43,12 @@ const Discard : React.FC = () => {
); -} +}; /** * Button that sets the app into an aborted state */ -const DiscardButton : React.FC = () => { +const DiscardButton: React.FC = () => { const { t } = useTranslation(); @@ -57,20 +57,22 @@ const DiscardButton : React.FC = () => { const theme = useTheme(); const discard = () => { - dispatch(setEnd({hasEnded: true, value: 'discarded'})) - } + dispatch(setEnd({ hasEnded: true, value: 'discarded' })); + }; return (
) => { if (event.key === " " || event.key === "Enter") { - discard() - } }}> + onKeyDown={(event: React.KeyboardEvent) => { + if (event.key === " " || event.key === "Enter") { + discard(); + } + }}> {t("discard.confirm-button")}
); -} +}; export default Discard; diff --git a/src/main/Error.tsx b/src/main/Error.tsx index 02d352f93..8a3ae70d5 100644 --- a/src/main/Error.tsx +++ b/src/main/Error.tsx @@ -1,11 +1,11 @@ import React from "react"; -import { css } from '@emotion/react' +import { css } from '@emotion/react'; import { LuFrown } from "react-icons/lu"; import { useAppSelector } from "../redux/store"; -import { selectErrorDetails, selectErrorIcon, selectErrorMessage, selectErrorTitle } from '../redux/errorSlice' +import { selectErrorDetails, selectErrorIcon, selectErrorMessage, selectErrorTitle } from '../redux/errorSlice'; import { flexGapReplacementStyle } from "../cssStyles"; import { useTranslation } from 'react-i18next'; @@ -14,21 +14,21 @@ import { useTranslation } from 'react-i18next'; * This page is to be displayed when the application has run into a critical error * from which it cannot recover. */ -const Error : React.FC = () => { +const Error: React.FC = () => { const { t } = useTranslation(); // Init redux variables - const errorTitle = useAppSelector(selectErrorTitle) - const errorMessage = useAppSelector(selectErrorMessage) - const errorDetails = useAppSelector(selectErrorDetails) - const ErrorIcon = useAppSelector(selectErrorIcon) + const errorTitle = useAppSelector(selectErrorTitle); + const errorMessage = useAppSelector(selectErrorMessage); + const errorDetails = useAppSelector(selectErrorDetails); + const ErrorIcon = useAppSelector(selectErrorIcon); const detailsStyle = css({ display: 'flex', flexDirection: 'column', alignItems: 'center', - }) + }); const theEndStyle = css({ height: '100%', @@ -37,12 +37,12 @@ const Error : React.FC = () => { justifyContent: 'center', alignItems: 'center', ...(flexGapReplacementStyle(10, false)), - }) + }); return (
{errorTitle ? errorTitle : t("error.generic-message")}
- {ErrorIcon ? : } + {ErrorIcon ? : } {errorMessage}
{errorDetails &&
@@ -52,6 +52,6 @@ const Error : React.FC = () => { }
); -} +}; -export default Error +export default Error; diff --git a/src/main/Finish.tsx b/src/main/Finish.tsx index 4c19d034b..1e3758327 100644 --- a/src/main/Finish.tsx +++ b/src/main/Finish.tsx @@ -1,15 +1,15 @@ import React from "react"; import FinishMenu from "./FinishMenu"; -import Save from "./Save" -import Discard from "./Discard" +import Save from "./Save"; +import Discard from "./Discard"; import WorkflowSelection from "./WorkflowSelection"; import WorkflowConfiguration from "./WorkflowConfiguration"; -import { LuDoorOpen} from "react-icons/lu"; +import { LuDoorOpen } from "react-icons/lu"; -import { css } from '@emotion/react' -import { basicButtonStyle, navigationButtonStyle } from '../cssStyles' +import { css } from '@emotion/react'; +import { basicButtonStyle, navigationButtonStyle } from '../cssStyles'; import { IconType } from "react-icons"; @@ -22,21 +22,21 @@ import { useTranslation } from "react-i18next"; /** * Displays a menu for selecting what should be done with the current changes */ -const Finish : React.FC = () => { +const Finish: React.FC = () => { - const pageNumber = useAppSelector(selectPageNumber) + const pageNumber = useAppSelector(selectPageNumber); const pageZeroStyle = css({ display: pageNumber !== 0 ? 'none' : 'block', - }) + }); const pageOneStyle = css({ display: pageNumber !== 1 ? 'none' : 'block', - }) + }); const pageTwoStyle = css({ display: pageNumber !== 2 ? 'none' : 'block', - }) + }); return (
@@ -53,15 +53,15 @@ const Finish : React.FC = () => {
); -} +}; /** * Takes you to a different page */ -export const PageButton : React.FC<{ +export const PageButton: React.FC<{ pageNumber: number, label: string, - Icon: IconType + Icon: IconType; }> = ({ pageNumber, label, @@ -74,8 +74,8 @@ export const PageButton : React.FC<{ const dispatch = useAppDispatch(); const onPageChange = () => { - dispatch(setPageNumber(pageNumber)) - } + dispatch(setPageNumber(pageNumber)); + }; const pageButtonStyle = css({ minWidth: '100px', @@ -83,25 +83,27 @@ export const PageButton : React.FC<{ justifyContent: 'center', boxShadow: `${theme.boxShadow}`, background: `${theme.element_bg}`, - }) + }); return (
) => { if (event.key === " " || event.key === "Enter") { - onPageChange() - } }}> + onKeyDown={(event: React.KeyboardEvent) => { + if (event.key === " " || event.key === "Enter") { + onPageChange(); + } + }}> {label}
); -} +}; /** * Takes you back to the callback url resource */ -export const CallbackButton : React.FC = () => { +export const CallbackButton: React.FC = () => { const { t } = useTranslation(); @@ -109,7 +111,7 @@ export const CallbackButton : React.FC = () => { const openCallbackUrl = () => { window.open(settings.callbackUrl, "_self"); - } + }; return ( <> @@ -117,13 +119,15 @@ export const CallbackButton : React.FC = () => {
) => { if (event.key === " " || event.key === "Enter") { - openCallbackUrl() - } }}> + onKeyDown={(event: React.KeyboardEvent) => { + if (event.key === " " || event.key === "Enter") { + openCallbackUrl(); + } + }}> {settings.callbackSystem ? - t("various.callback-button-system", {system: settings.callbackSystem}) : + t("various.callback-button-system", { system: settings.callbackSystem }) : t("various.callback-button-generic") } @@ -131,7 +135,7 @@ export const CallbackButton : React.FC = () => { } ); -} +}; export default Finish; diff --git a/src/main/FinishMenu.tsx b/src/main/FinishMenu.tsx index 6194c24c8..e4370b83b 100644 --- a/src/main/FinishMenu.tsx +++ b/src/main/FinishMenu.tsx @@ -1,13 +1,13 @@ import React from "react"; -import { css } from '@emotion/react' -import { basicButtonStyle, flexGapReplacementStyle, tileButtonStyle } from '../cssStyles' +import { css } from '@emotion/react'; +import { basicButtonStyle, flexGapReplacementStyle, tileButtonStyle } from '../cssStyles'; import { IconType } from "react-icons"; import { LuSave, LuDatabase, LuXCircle } from "react-icons/lu"; import { useAppDispatch } from "../redux/store"; -import { setState, setPageNumber, finish } from '../redux/finishSlice' +import { setState, setPageNumber, finish } from '../redux/finishSlice'; import { useTranslation } from 'react-i18next'; import { useTheme } from "../themes"; @@ -15,7 +15,7 @@ import { useTheme } from "../themes"; /** * Displays a menu for selecting what should be done with the current changes */ -const FinishMenu : React.FC = () => { +const FinishMenu: React.FC = () => { const finishMenuStyle = css({ display: 'flex', @@ -23,30 +23,30 @@ const FinishMenu : React.FC = () => { justifyContent: 'center', flexWrap: 'wrap', ...(flexGapReplacementStyle(30, false)), - }) + }); return (
- - - + + +
); -} +}; /** * Buttons for the finish menu */ -const FinishMenuButton: React.FC<{Icon: IconType, stateName: finish["value"]}> = ({Icon, stateName}) => { +const FinishMenuButton: React.FC<{ Icon: IconType, stateName: finish["value"]; }> = ({ Icon, stateName }) => { const { t } = useTranslation(); - const theme = useTheme() + const theme = useTheme(); const dispatch = useAppDispatch(); const finish = () => { dispatch(setState(stateName)); - dispatch(setPageNumber(1)) - } + dispatch(setPageNumber(1)); + }; let buttonString; switch (stateName) { @@ -74,21 +74,23 @@ const FinishMenuButton: React.FC<{Icon: IconType, stateName: finish["value"]}> = borderRadius: '50%', width: '90px', height: '90px', - }) + }); const labelStyle = css({ padding: '0px 20px', - }) + }); return (
) => { if (event.key === " " || event.key === "Enter") { - finish() - } }}> + onKeyDown={(event: React.KeyboardEvent) => { + if (event.key === " " || event.key === "Enter") { + finish(); + } + }}>
- +
{buttonString}
diff --git a/src/main/Header.tsx b/src/main/Header.tsx index 0b21e214c..d5f08f7e4 100644 --- a/src/main/Header.tsx +++ b/src/main/Header.tsx @@ -2,14 +2,14 @@ import React from "react"; import { useAppSelector } from "../redux/store"; import { useTheme } from "../themes"; -import { css } from '@emotion/react' +import { css } from '@emotion/react'; import { useTranslation } from "react-i18next"; import { MainMenuButton } from "./MainMenu"; import { LuMoon, LuSun } from "react-icons/lu"; import { HiOutlineTranslate } from "react-icons/hi"; import { LuKeyboard } from "react-icons/lu"; import { MainMenuStateNames } from "../types"; -import { basicButtonStyle, BREAKPOINT_MEDIUM, BREAKPOINT_SMALL, flexGapReplacementStyle } from '../cssStyles' +import { basicButtonStyle, BREAKPOINT_MEDIUM, BREAKPOINT_SMALL, flexGapReplacementStyle } from '../cssStyles'; import { ReactComponent as LogoSvg } from '../img/opencast-editor.svg'; import { selectIsEnd } from "../redux/endSlice"; @@ -18,18 +18,18 @@ import { IconType } from "react-icons"; import i18next from "i18next"; function Header() { - const theme = useTheme() + const theme = useTheme(); const { scheme } = useColorScheme(); - const { t } = useTranslation() + const { t } = useTranslation(); - const isEnd = useAppSelector(selectIsEnd) + const isEnd = useAppSelector(selectIsEnd); const headerStyle = css({ display: 'flex', alignItems: 'center', justifyContent: 'space-between', backgroundColor: `${theme.header_bg}`, - }) + }); const headerStyleThemed = scheme.includes('high-contrast-') ? css({ @@ -38,7 +38,7 @@ function Header() { }) : css({ height: '64px', - }) + }); const rightSideButtonsStyle = css({ display: 'flex', @@ -47,7 +47,7 @@ function Header() { alignItems: 'center', paddingRight: '24px', ...(flexGapReplacementStyle(16, false)), - }) + }); const settingsButtonCSS = css({ display: 'flex', @@ -67,7 +67,7 @@ function Header() { backgroundColor: theme.header_button_hover_bg, color: `${theme.header_text}` }, - }) + }); return (
@@ -75,14 +75,14 @@ function Header() {
- { !isEnd && + {!isEnd && }
@@ -92,7 +92,7 @@ function Header() { const Logo: React.FC = () => { - const { t } = useTranslation() + const { t } = useTranslation(); const { scheme } = useColorScheme(); const logo = css({ @@ -112,7 +112,7 @@ const Logo: React.FC = () => { "&:focus": { backgroundColor: `unset`, }, - }) + }); return ( { bottomText={""} ariaLabelText={t("mainMenu.cutting-button")} customCSS={logo} - iconCustomCSS={css({width: 'auto', height: '60px'})} + iconCustomCSS={css({ width: 'auto', height: '60px' })} /> - ) -} + ); +}; const LanguageButton: React.FC = () => { const { t } = useTranslation(); @@ -133,24 +133,24 @@ const LanguageButton: React.FC = () => { const changeLanguage = (lng: string | undefined) => { i18next.changeLanguage(lng); - } + }; const languageNames = (language: string) => { return new Intl.DisplayNames([language], { type: 'language' }).of(language); - } + }; const resourcesArray: string[] | undefined = i18next.options.resources && Object.keys(i18next.options.resources); const languages = resourcesArray?.map(entry => { - return { value: entry, label: languageNames(entry) } - }) + return { value: entry, label: languageNames(entry) }; + }); // menuItems can't deal with languages being undefined, so we return early // until we reach a rerender with actual information if (languages === undefined) { - return (<>) + return (<>); } const menuItems = Object.values(languages).map(lng => checkboxMenuItem({ @@ -177,7 +177,7 @@ const LanguageButton: React.FC = () => { const ThemeButton: React.FC = () => { - const { t } = useTranslation() + const { t } = useTranslation(); const { scheme, isAuto, update } = useColorScheme(); const currentPref = isAuto ? "auto" : scheme; @@ -200,8 +200,8 @@ const ThemeButton: React.FC = () => { label={t("theme.appearance")} /> - ) -} + ); +}; type HeaderButtonProps = JSX.IntrinsicElements["button"] & { Icon: IconType; @@ -210,7 +210,7 @@ type HeaderButtonProps = JSX.IntrinsicElements["button"] & { const HeaderButton = React.forwardRef( ({ Icon, label, ...rest }, ref) => { - const theme = useTheme() + const theme = useTheme(); const themeSelectorButtonStyle = css({ display: "flex", @@ -237,14 +237,14 @@ const HeaderButton = React.forwardRef( backgroundColor: theme.header_button_hover_bg, color: `${theme.header_text}` } - }) + }); const iconStyle = css({ display: "flex", alignItems: "center", fontSize: 22, - }) + }); return ( ( }, }}>{label} - ) - }) + ); + }); export default Header; diff --git a/src/main/KeyboardControls.tsx b/src/main/KeyboardControls.tsx index f861df1be..1b52339f9 100644 --- a/src/main/KeyboardControls.tsx +++ b/src/main/KeyboardControls.tsx @@ -3,13 +3,13 @@ import { ParseKeys } from "i18next"; import React from "react"; -import { useTranslation, Trans} from "react-i18next"; +import { useTranslation, Trans } from "react-i18next"; import { flexGapReplacementStyle } from "../cssStyles"; import { getGroupName, KEYMAP, rewriteKeys } from "../globalKeys"; import { useTheme } from "../themes"; -import { titleStyle, titleStyleBold } from '../cssStyles' +import { titleStyle, titleStyleBold } from '../cssStyles'; -const Group: React.FC<{name: ParseKeys, entries: { [key: string]: string[][] }}> = ({name, entries}) => { +const Group: React.FC<{ name: ParseKeys, entries: { [key: string]: string[][]; }; }> = ({ name, entries }) => { const { t } = useTranslation(); const theme = useTheme(); @@ -29,7 +29,7 @@ const Group: React.FC<{name: ParseKeys, entries: { [key: string]: string[][] }}> const headingStyle = css({ color: `${theme.text}`, - }) + }); return (
@@ -38,10 +38,10 @@ const Group: React.FC<{name: ParseKeys, entries: { [key: string]: string[][] }}> )}
- ) -} + ); +}; -const Entry: React.FC<{name: string, sequences: string[][] }> = ({name, sequences}) => { +const Entry: React.FC<{ name: string, sequences: string[][]; }> = ({ name, sequences }) => { const { t } = useTranslation(); const theme = useTheme(); @@ -61,13 +61,13 @@ const Entry: React.FC<{name: string, sequences: string[][] }> = ({name, sequence textOverflow: 'ellipsis', wordWrap: 'break-word', color: `${theme.text}`, - }) + }); const sequenceStyle = css({ display: 'flex', flexDirection: 'row', ...(flexGapReplacementStyle(10, true)) - }) + }); const singleKeyStyle = css({ borderRadius: '4px', @@ -78,13 +78,13 @@ const Entry: React.FC<{name: string, sequences: string[][] }> = ({name, sequence boxShadow: `${theme.singleKey_boxShadow}`, padding: '10px', color: `${theme.text}`, - }) + }); const orStyle = css({ alignSelf: 'center', fontSize: '20px', fontWeight: 'bold', - }) + }); return (
@@ -101,14 +101,14 @@ const Entry: React.FC<{name: string, sequences: string[][] }> = ({name, sequence
))}
- ) -} + ); +}; const KeyboardControls: React.FC = () => { const { t } = useTranslation(); - const theme = useTheme() + const theme = useTheme(); const groupsStyle = css({ display: 'flex', @@ -116,33 +116,33 @@ const KeyboardControls: React.FC = () => { flexWrap: 'wrap', justifyContent: 'center', ...(flexGapReplacementStyle(30, true)), - }) + }); const render = () => { if (KEYMAP && Object.keys(KEYMAP).length > 0) { const groups: JSX.Element[] = []; Object.entries(KEYMAP).forEach(([groupName, group], index) => { - const entries : { [groupName: string]: string[][] } = {} + const entries: { [groupName: string]: string[][]; } = {}; Object.entries(group).forEach(([, action]) => { - const sequences = action.key.split(",").map(item => item.trim()) + const sequences = action.key.split(",").map(item => item.trim()); entries[action.name] = Object.entries(sequences).map(([, sequence]) => { - return sequence.split("+").map(item => rewriteKeys(item.trim())) - }) - }) - groups.push() - }) + return sequence.split("+").map(item => rewriteKeys(item.trim())); + }); + }); + groups.push(); + }); return (
{groups}
- ) + ); } // No groups fallback - return
{t("keyboardControls.genericError")}
- } + return
{t("keyboardControls.genericError")}
; + }; const keyboardControlsStyle = css({ display: 'flex', diff --git a/src/main/Landing.tsx b/src/main/Landing.tsx index 03fbef58b..9daff633e 100644 --- a/src/main/Landing.tsx +++ b/src/main/Landing.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { css } from '@emotion/react' +import { css } from '@emotion/react'; import { useTranslation } from 'react-i18next'; @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next'; * This page is to be displayed when the application has run into a critical error * from which it cannot recover. */ -const Landing : React.FC = () => { +const Landing: React.FC = () => { const { t } = useTranslation(); @@ -30,7 +30,7 @@ const Landing : React.FC = () => { userSelect: 'all', color: '#e83e8c', } - }) + }); return (
@@ -53,6 +53,6 @@ const Landing : React.FC = () => {
); -} +}; -export default Landing +export default Landing; diff --git a/src/main/Lock.tsx b/src/main/Lock.tsx index f74419c18..12f7aaf43 100644 --- a/src/main/Lock.tsx +++ b/src/main/Lock.tsx @@ -4,24 +4,24 @@ import { LuLock } from "react-icons/lu"; import { useAppDispatch, useAppSelector } from "../redux/store"; import { settings } from "../config"; import { setLock, video } from "../redux/videoSlice"; -import { selectIsEnd } from '../redux/endSlice' +import { selectIsEnd } from '../redux/endSlice'; import { setError } from "../redux/errorSlice"; import { client } from "../util/client"; import { useInterval } from "../util/utilityFunctions"; import { useBeforeunload } from 'react-beforeunload'; const Lock: React.FC = () => { - const endpoint = `${settings.opencast.url}/editor/${settings.id}/lock` + const endpoint = `${settings.opencast.url}/editor/${settings.id}/lock`; const dispatch = useAppDispatch(); - const lockingActive = useAppSelector((state: { videoState: { lockingActive: video["lockingActive"] } }) => + const lockingActive = useAppSelector((state: { videoState: { lockingActive: video["lockingActive"]; }; }) => state.videoState.lockingActive); - const lockRefresh = useAppSelector((state: { videoState: { lockRefresh: video["lockRefresh"] } }) => + const lockRefresh = useAppSelector((state: { videoState: { lockRefresh: video["lockRefresh"]; }; }) => state.videoState.lockRefresh); - const lockState = useAppSelector((state: { videoState: { lockState: video["lockState"] } }) => + const lockState = useAppSelector((state: { videoState: { lockState: video["lockState"]; }; }) => state.videoState.lockState); - const lock = useAppSelector((state: { videoState: { lock: video["lock"] } }) => state.videoState.lock); - const isEnd = useAppSelector(selectIsEnd) + const lock = useAppSelector((state: { videoState: { lock: video["lock"]; }; }) => state.videoState.lock); + const isEnd = useAppSelector(selectIsEnd); function requestLock() { const form = `user=${lock.user}&uuid=${lock.uuid}`; @@ -30,54 +30,54 @@ const Lock: React.FC = () => { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' } }) - .then(() => dispatch(setLock(true))) - .catch((error: string) => { - dispatch(setLock(false)) - dispatch(setError({ - error: true, - errorDetails: error, - errorIcon: LuLock, - errorTitle: 'Video editing locked', - errorMessage: 'This video is currently being edited by another user' - })) - }) + .then(() => dispatch(setLock(true))) + .catch((error: string) => { + dispatch(setLock(false)); + dispatch(setError({ + error: true, + errorDetails: error, + errorIcon: LuLock, + errorTitle: 'Video editing locked', + errorMessage: 'This video is currently being edited by another user' + })); + }); } function releaseLock() { if (lockingActive && lockState) { client.delete(endpoint + '/' + lock.uuid) - .then(() => { - console.info('Lock released') - dispatch(setLock(false)); - }) + .then(() => { + console.info('Lock released'); + dispatch(setLock(false)); + }); } } // Request lock useEffect(() => { if (lockingActive) { - requestLock() + requestLock(); } - }, [lockingActive]) + }, [lockingActive]); // Refresh lock useInterval(async () => { - requestLock() + requestLock(); }, lockingActive ? lockRefresh : null); // Release lock on leaving page useBeforeunload((_event: { preventDefault: () => void; }) => { - releaseLock() - }) + releaseLock(); + }); // Release lock on discard useEffect(() => { if (isEnd) { - releaseLock() + releaseLock(); } - }, [isEnd]) + }, [isEnd]); - return (<>) -} + return (<>); +}; export default Lock; diff --git a/src/main/MainContent.tsx b/src/main/MainContent.tsx index 134cdd01a..2eeab9116 100644 --- a/src/main/MainContent.tsx +++ b/src/main/MainContent.tsx @@ -3,22 +3,22 @@ import React from "react"; import Metadata from './Metadata'; import TrackSelection from './TrackSelection'; import Subtitle from "./Subtitle"; -import Finish from "./Finish" +import Finish from "./Finish"; import KeyboardControls from "./KeyboardControls"; import { LuWrench } from "react-icons/lu"; -import { css } from '@emotion/react' +import { css } from '@emotion/react'; import { useAppSelector } from "../redux/store"; -import { selectMainMenuState } from '../redux/mainMenuSlice' +import { selectMainMenuState } from '../redux/mainMenuSlice'; -import { MainMenuStateNames } from '../types' +import { MainMenuStateNames } from '../types'; import { flexGapReplacementStyle } from "../cssStyles"; import { useBeforeunload } from 'react-beforeunload'; import { selectHasChanges as videoSelectHasChanges } from "../redux/videoSlice"; -import { selectHasChanges as metadataSelectHasChanges} from "../redux/metadataSlice"; +import { selectHasChanges as metadataSelectHasChanges } from "../redux/metadataSlice"; import { selectHasChanges as selectSubtitleHasChanges } from "../redux/subtitleSlice"; import { useTheme } from "../themes"; import Thumbnail from "./Thumbnail"; @@ -30,11 +30,11 @@ import Cutting from "./Cutting"; */ const MainContent: React.FC = () => { - const mainMenuState = useAppSelector(selectMainMenuState) - const videoChanged = useAppSelector(videoSelectHasChanges) - const metadataChanged = useAppSelector(metadataSelectHasChanges) - const subtitleChanged = useAppSelector(selectSubtitleHasChanges) - const theme = useTheme() + const mainMenuState = useAppSelector(selectMainMenuState); + const videoChanged = useAppSelector(videoSelectHasChanges); + const metadataChanged = useAppSelector(metadataSelectHasChanges); + const subtitleChanged = useAppSelector(selectSubtitleHasChanges); + const theme = useTheme(); // Display warning when leaving the page if there are unsaved changes useBeforeunload((event: { preventDefault: () => void; }) => { @@ -51,44 +51,44 @@ const MainContent: React.FC = () => { ...(flexGapReplacementStyle(20, false)), background: `${theme.background}`, overflow: 'auto', - }) + }); const cuttingStyle = css({ flexDirection: 'column', - }) + }); const metadataStyle = css({ - }) + }); const trackSelectStyle = css({ flexDirection: 'column', alignContent: 'space-around', - }) + }); const subtitleSelectStyle = css({ flexDirection: 'column', justifyContent: 'space-around', - }) + }); const thumbnailSelectStyle = css({ flexDirection: 'column', alignContent: 'space-around', - }) + }); const finishStyle = css({ flexDirection: 'column', justifyContent: 'space-around', - }) + }); const keyboardControlsStyle = css({ flexDirection: 'column', - }) + }); const defaultStyle = css({ flexDirection: 'column', alignItems: 'center', padding: '20px', - }) + }); const render = () => { if (mainMenuState === MainMenuStateNames.cutting) { @@ -96,50 +96,50 @@ const MainContent: React.FC = () => {
- ) + ); } else if (mainMenuState === MainMenuStateNames.metadata) { return (
- ) + ); } else if (mainMenuState === MainMenuStateNames.trackSelection) { return (
- ) + ); } else if (mainMenuState === MainMenuStateNames.subtitles) { return (
- ) + ); } else if (mainMenuState === MainMenuStateNames.thumbnail) { return (
- ) + ); } else if (mainMenuState === MainMenuStateNames.finish) { return (
- ) + ); } else if (mainMenuState === MainMenuStateNames.keyboardControls) { return (
- ) + ); } else {
- + Placeholder -
+ ; } - } + }; return ( <>{render()} diff --git a/src/main/MainMenu.tsx b/src/main/MainMenu.tsx index 0cb91cf49..e50f97b81 100644 --- a/src/main/MainMenu.tsx +++ b/src/main/MainMenu.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { css, SerializedStyles } from '@emotion/react' +import { css, SerializedStyles } from '@emotion/react'; import { IconType } from "react-icons"; import { LuScissors, LuFilm, LuFileText, LuCheckSquare } from "react-icons/lu"; @@ -8,12 +8,12 @@ import { LuImage } from "react-icons/lu"; import { ReactComponent as SubtitleIcon } from '../img/subtitle.svg'; import { useAppDispatch, useAppSelector } from "../redux/store"; -import { setState, selectMainMenuState, mainMenu } from '../redux/mainMenuSlice' -import { setPageNumber } from '../redux/finishSlice' +import { setState, selectMainMenuState, mainMenu } from '../redux/mainMenuSlice'; +import { setPageNumber } from '../redux/finishSlice'; -import { MainMenuStateNames } from '../types' -import { settings } from '../config' -import { basicButtonStyle, flexGapReplacementStyle } from '../cssStyles' +import { MainMenuStateNames } from '../types'; +import { settings } from '../config'; +import { basicButtonStyle, flexGapReplacementStyle } from '../cssStyles'; import { setIsPlaying } from "../redux/videoSlice"; import { useTranslation } from 'react-i18next'; @@ -110,24 +110,24 @@ export const MainMenuButton: React.FC = ({ }) => { const dispatch = useAppDispatch(); - const activeState = useAppSelector(selectMainMenuState) + const activeState = useAppSelector(selectMainMenuState); const theme = useTheme(); const onMenuItemClicked = () => { dispatch(setState(stateName)); // Reset multi-page content to their first page if (stateName === MainMenuStateNames.finish) { - dispatch(setPageNumber(0)) + dispatch(setPageNumber(0)); } if (stateName === MainMenuStateNames.subtitles) { - dispatch(setIsDisplayEditView(false)) + dispatch(setIsDisplayEditView(false)); } // Halt ongoing events - dispatch(setIsPlaying(false)) + dispatch(setIsPlaying(false)); // Reset states - dispatch(resetPostRequestState()) - dispatch(metadataResetPostRequestState()) - } + dispatch(resetPostRequestState()); + dispatch(metadataResetPostRequestState()); + }; const mainMenuButtonStyle = css({ width: '100%', @@ -151,15 +151,17 @@ export const MainMenuButton: React.FC = ({ role="menuitem" tabIndex={0} aria-label={ariaLabelText} onClick={onMenuItemClicked} - onKeyDown={(event: React.KeyboardEvent) => { if (event.key === "Enter") { - onMenuItemClicked() - } }} + onKeyDown={(event: React.KeyboardEvent) => { + if (event.key === "Enter") { + onMenuItemClicked(); + } + }} > + }} /> {bottomText &&
{bottomText}
} ); diff --git a/src/main/Metadata.tsx b/src/main/Metadata.tsx index 594a2966c..67f1e66db 100644 --- a/src/main/Metadata.tsx +++ b/src/main/Metadata.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; -import { css } from '@emotion/react' -import { calendarStyle, errorBoxStyle, selectFieldStyle, titleStyle, titleStyleBold } from '../cssStyles' +import { css } from '@emotion/react'; +import { calendarStyle, errorBoxStyle, selectFieldStyle, titleStyle, titleStyleBold } from '../cssStyles'; import { useAppDispatch, useAppSelector } from "../redux/store"; import { @@ -16,16 +16,16 @@ import { selectPostError, selectPostStatus, setFieldReadonly -} from '../redux/metadataSlice' +} from '../redux/metadataSlice'; -import { Form, Field, FieldInputProps } from 'react-final-form' -import Select from 'react-select' +import { Form, Field, FieldInputProps } from 'react-final-form'; +import Select from 'react-select'; import CreatableSelect from 'react-select/creatable'; import { useTranslation } from 'react-i18next'; -import { DateTime as LuxonDateTime} from "luxon"; +import { DateTime as LuxonDateTime } from "luxon"; -import { configureFieldsAttributes, settings } from '../config' +import { configureFieldsAttributes, settings } from '../config'; import { useTheme } from "../themes"; import { ThemeProvider } from "@mui/material/styles"; import { cloneDeep } from "lodash"; @@ -56,39 +56,40 @@ const Metadata: React.FC = () => { // Try to fetch URL from external API useEffect(() => { if (getStatus === 'idle') { - dispatch(fetchMetadata()) + dispatch(fetchMetadata()); } - }, [getStatus, dispatch]) + }, [getStatus, dispatch]); // Overwrite readonly property of fields based on config settings useEffect(() => { if (getStatus === 'success') { for (let catalogIndex = 0; catalogIndex < catalogs.length; catalogIndex++) { if (settings.metadata.configureFields) { - const configureFields = settings.metadata.configureFields - const catalog = catalogs[catalogIndex] + const configureFields = settings.metadata.configureFields; + const catalog = catalogs[catalogIndex]; if (catalog.title in configureFields) { if (Object.keys(configureFields[catalog.title]).length > 0) { - const configureFieldsCatalog = configureFields[catalog.title] + const configureFieldsCatalog = configureFields[catalog.title]; for (let fieldIndex = 0; fieldIndex < catalog.fields.length; fieldIndex++) { if (catalog.fields[fieldIndex].id in configureFieldsCatalog) { if ("readonly" in configureFieldsCatalog[catalog.fields[fieldIndex].id]) { - dispatch(setFieldReadonly({catalogIndex: catalogIndex, fieldIndex: fieldIndex, + dispatch(setFieldReadonly({ + catalogIndex: catalogIndex, fieldIndex: fieldIndex, value: configureFieldsCatalog[catalog.fields[fieldIndex].id].readonly - })) + })); } } } } else { - return undefined + return undefined; } } } } } - }, [getStatus, catalogs, dispatch]) + }, [getStatus, catalogs, dispatch]); /** * CSS @@ -100,7 +101,7 @@ const Metadata: React.FC = () => { marginRight: 'auto', minWidth: '50%', display: 'grid', - }) + }); const catalogStyle = css({ background: `${theme.menu_background}`, @@ -109,14 +110,14 @@ const Metadata: React.FC = () => { marginTop: '24px', boxSizing: "border-box", padding: '10px', - }) + }); const fieldStyle = css({ display: 'flex', flexFlow: 'column nowrap', lineHeight: '2em', margin: '10px', - }) + }); const fieldLabelStyle = css({ width: '110px', @@ -124,14 +125,14 @@ const Metadata: React.FC = () => { fontWeight: 'bold', color: `${theme.text}`, lineHeight: '32px', - }) + }); const fieldTypeStyle = (isReadOnly: boolean) => { return css({ fontSize: '1em', borderRadius: '5px', boxShadow: isReadOnly ? '0 0 0px rgba(0, 0, 0, 0.3)' : '0 0 1px rgba(0, 0, 0, 0.3)', - ...(isReadOnly && {color: `${theme.text}`}), + ...(isReadOnly && { color: `${theme.text}` }), color: `${theme.text}`, outline: isReadOnly ? '0px solid transparent' : `${theme.element_outline}`, "&:hover": { @@ -141,7 +142,7 @@ const Metadata: React.FC = () => { borderColor: isReadOnly ? undefined : theme.metadata_highlight, }, }); - } + }; const inputFieldTypeStyle = (isReadOnly: boolean) => { return ( @@ -153,16 +154,16 @@ const Metadata: React.FC = () => { resize: 'vertical', }) ); - } + }; const validateStyle = (isError: boolean) => { return css({ lineHeight: '32px', marginLeft: '10px', - ...(isError && {color: `${theme.error}`}), + ...(isError && { color: `${theme.error}` }), fontWeight: 'bold', }); - } + }; // const buttonContainerStyle = css({ // display: 'flex', @@ -219,47 +220,47 @@ const Metadata: React.FC = () => { // If the value is hid inside an array, we need to extract it if (Array.isArray(input)) { input.forEach((subArray: any) => { - output.push(helperHandleArrays(library, subArray, output)) - }) + output.push(helperHandleArrays(library, subArray, output)); + }); } // Find react-select equivalent for inital value - return library?.find(el => el.submitValue === input) - } + return library?.find(el => el.submitValue === input); + }; /** * Returns a data structure to initialize the form fields with * @param catalogs */ const getInitialValues = (catalogs: Catalog[]) => { - const initValues : { [n: string]: any } = {}; + const initValues: { [n: string]: any; } = {}; catalogs.forEach((catalog: Catalog, catalogIndex: number) => { - initValues["catalog" + catalogIndex] = {} + initValues["catalog" + catalogIndex] = {}; catalog.fields.forEach((field: MetadataField) => { - initValues["catalog" + catalogIndex][field.id] = field.value + initValues["catalog" + catalogIndex][field.id] = field.value; // Handle initial values for select fields differently // Since react-select creates different values if (field.collection) { - const library = generateReactSelectLibrary(field) - let searchValue : any = field.value + const library = generateReactSelectLibrary(field); + let searchValue: any = field.value; if (Array.isArray(searchValue)) { const result: any[] = []; - helperHandleArrays(library, field.value, result) - searchValue = result + helperHandleArrays(library, field.value, result); + searchValue = result; } else { - searchValue = library?.find(el => el.submitValue === searchValue) + searchValue = library?.find(el => el.submitValue === searchValue); } - initValues["catalog" + catalogIndex][field.id] = searchValue + initValues["catalog" + catalogIndex][field.id] = searchValue; } - }) - }) + }); + }); - return initValues - } + return initValues; + }; /** * Form Callbacks - Validation @@ -269,16 +270,16 @@ const Metadata: React.FC = () => { * Validator for required fields * @param value */ - const required = (value: any) => (value ? undefined : t("metadata.validation.required")) + const required = (value: any) => (value ? undefined : t("metadata.validation.required")); /** * Validator for the duration field * @param value */ const duration = (value: any) => { - const re = /^[0-9][0-9]:[0-9][0-9]:[0-9][0-9]$/ - return re.test(value) ? undefined : t("metadata.validation.duration-format") - } + const re = /^[0-9][0-9]:[0-9][0-9]:[0-9][0-9]$/; + return re.test(value) ? undefined : t("metadata.validation.duration-format"); + }; /** * Validator for the date time fields @@ -287,10 +288,10 @@ const Metadata: React.FC = () => { const dateTimeValidator = (date: any) => { // Empty field is valid value in Opencast if (!date) { - return undefined + return undefined; } - let dt = undefined + let dt = undefined; if (Object.prototype.toString.call(date) === '[object Date]') { dt = LuxonDateTime.fromJSDate(date); } @@ -299,10 +300,10 @@ const Metadata: React.FC = () => { } if (dt) { - return dt.isValid ? undefined : t("metadata.validation.datetime") + return dt.isValid ? undefined : t("metadata.validation.datetime"); } - return t("metadata.validation.datetime") - } + return t("metadata.validation.datetime"); + }; // // Function that combines multiple validation functions. Needs to be made typescript conform // const composeValidators = (...validators) => value => @@ -315,15 +316,15 @@ const Metadata: React.FC = () => { */ const getValidators = (field: MetadataField) => { if (field.required) { - return required + return required; } else if (field.id === "duration") { - return duration + return duration; } else if (field.type === "date" || field.type === "time") { - return dateTimeValidator + return dateTimeValidator; } else { - return undefined + return undefined; } - } + }; /** * Form Callbacks - Submitting @@ -345,19 +346,20 @@ const Metadata: React.FC = () => { fieldId.indexOf(".") + 1, fieldId.length ); - const catalogIndex = parseInt(catalogIndexString) + const catalogIndex = parseInt(catalogIndexString); // Find the corresponding field index in the redux catalog for (let fieldIndex = 0; fieldIndex < catalogs[catalogIndex].fields.length; fieldIndex++) { if (catalogs[catalogIndex].fields[fieldIndex].id === fieldName) { // Update the field in the redux catalog - dispatch(setFieldValue({catalogIndex: catalogIndex, fieldIndex: fieldIndex, + dispatch(setFieldValue({ + catalogIndex: catalogIndex, fieldIndex: fieldIndex, value: parseValue(catalogs[catalogIndex].fields[fieldIndex], value) - })) - break + })); + break; } } - } + }; /** * Executes given blur callback while also sending the value of the current field to redux @@ -366,8 +368,8 @@ const Metadata: React.FC = () => { */ const blurWithSubmit = (e: any, input: any) => { input.onBlur(e); - submitSingleField(input.value, input.name) - } + submitSingleField(input.value, input.name); + }; /** * Helper function for onSubmit @@ -376,45 +378,45 @@ const Metadata: React.FC = () => { * @param value */ const parseValue = (field: MetadataField | null, value: any) => { - let returnValue : any = value + let returnValue: any = value; // Parse values out react-multi-select and put them in an array if (Array.isArray(value)) { - returnValue = [] - value.forEach((subValue : any) => { - returnValue.push(parseValue(null, subValue)) // Pass field as null to avoid each value into an array later on - }) + returnValue = []; + value.forEach((subValue: any) => { + returnValue.push(parseValue(null, subValue)); // Pass field as null to avoid each value into an array later on + }); } // If the value is hidden an object due to react-select, extract it if (typeof value === 'object' && value !== null && Object.prototype.hasOwnProperty.call(value, "submitValue")) { - returnValue = value.submitValue + returnValue = value.submitValue; } else if (typeof value === 'object' && value !== null && value.__isNew__) { - returnValue = value.value + returnValue = value.value; } // For these fields, the value needs to be inside an array if (field && !Array.isArray(value) && (field.id === "creator" || field.id === "contributor")) { - returnValue = [returnValue] + returnValue = [returnValue]; } // For these fields, the value needs to be inside an array if (field && (field.type === "date" || field.type === "time") && - Object.prototype.toString.call(returnValue) === '[object Date]') { + Object.prototype.toString.call(returnValue) === '[object Date]') { // If invalid date if ((isNaN(returnValue.getTime()))) { // Do nothing } else { - returnValue = returnValue.toJSON() + returnValue = returnValue.toJSON(); } } else if (field && (field.type === "date" || field.type === "time") && typeof returnValue === "string") { if (returnValue !== "") { // Empty string is allowed - returnValue = new Date(returnValue).toJSON() + returnValue = new Date(returnValue).toJSON(); } } - return returnValue - } + return returnValue; + }; /** * Callback for when the form is submitted @@ -424,7 +426,7 @@ const Metadata: React.FC = () => { const onSubmit = (values: { [x: string]: { [x: string]: any; }; }) => { // For each submitted value, get the catalog it belongs to Object.keys(values).forEach((formCatalogName: string) => { - const catalogIndex = parseInt(formCatalogName.replace("catalog", "")) + const catalogIndex = parseInt(formCatalogName.replace("catalog", "")); // For each field in the submitted values Object.keys(values[formCatalogName]).forEach((formFieldName: any) => { @@ -432,18 +434,19 @@ const Metadata: React.FC = () => { for (let fieldIndex = 0; fieldIndex < catalogs[catalogIndex].fields.length; fieldIndex++) { if (catalogs[catalogIndex].fields[fieldIndex].id === formFieldName) { // Update the field in the redux catalog - dispatch(setFieldValue({catalogIndex: catalogIndex, fieldIndex: fieldIndex, + dispatch(setFieldValue({ + catalogIndex: catalogIndex, fieldIndex: fieldIndex, value: parseValue(catalogs[catalogIndex].fields[fieldIndex], values[formCatalogName][formFieldName]) - })) - break + })); + break; } } - }) + }); // Send updated values to Opencast - dispatch(postMetadata()) - }) - } + dispatch(postMetadata()); + }); + }; /** * Form - Rendering @@ -457,7 +460,8 @@ const Metadata: React.FC = () => { const generateReactSelectLibrary = (field: MetadataField) => { if (field.collection) { // For whatever reason react-select uses 'value' as their key, which is not at all confusing - const library: [{value: any, label: any, submitValue: any}] = [{value: "", label: "No value", submitValue: ""}] + const library: [{ value: any, label: any, submitValue: any; }] = + [{ value: "", label: "No value", submitValue: "" }]; Object.entries(field.collection).forEach(([key, value]) => { // // Parse License // let [err, result] = safeJsonParse(key) @@ -466,18 +470,18 @@ const Metadata: React.FC = () => { // } // Parse Label - let descLabel = null + let descLabel = null; if (i18n.exists(`metadata.${field.id}`)) { - descLabel = t(`metadata.${field.id}.${key.replaceAll(".", "-")}` as ParseKeys) + descLabel = t(`metadata.${field.id}.${key.replaceAll(".", "-")}` as ParseKeys); if (field.id === "license") { - descLabel = t(`metadata.${field.id}.${JSON.parse(key).label.replaceAll(".", "-")}` as ParseKeys) + descLabel = t(`metadata.${field.id}.${JSON.parse(key).label.replaceAll(".", "-")}` as ParseKeys); } } // Change label for series if (field.id === "isPartOf") { - descLabel = key + descLabel = key; } // Add to library @@ -485,13 +489,13 @@ const Metadata: React.FC = () => { value: key, label: descLabel ? descLabel : value, submitValue: value - }) - }) - return library + }); + }); + return library; } else { - return null + return null; } - } + }; /** * Generates different form components based on the field @@ -499,12 +503,12 @@ const Metadata: React.FC = () => { * @param input */ const generateComponent = (field: MetadataField, input: any) => { - input.id = input.name + input.id = input.name; if (field.collection) { if (Array.isArray(field.value)) { return ( { blurWithSubmit(e, input) }} + onBlur={e => { blurWithSubmit(e, input); }} isMulti isClearable={!field.readOnly} // The component does not support readOnly, so we have to work around isSearchable={!field.readOnly} // by setting other settings @@ -518,7 +522,7 @@ const Metadata: React.FC = () => { } else { return (