diff --git a/frontend/src/app/(editor)/@appBar/n/[noteId]/editor-app-bar.tsx b/frontend/src/app/(editor)/@appBar/n/[noteId]/editor-app-bar.tsx index 973200561..843cde88d 100644 --- a/frontend/src/app/(editor)/@appBar/n/[noteId]/editor-app-bar.tsx +++ b/frontend/src/app/(editor)/@appBar/n/[noteId]/editor-app-bar.tsx @@ -10,6 +10,7 @@ import { NoteTitleElement } from '../../../../../components/layout/app-bar/app-b import { BaseAppBar } from '../../../../../components/layout/app-bar/base-app-bar' import { useApplicationState } from '../../../../../hooks/common/use-application-state' import React from 'react' +import { EditorModeExtendedAppBar } from './editor-mode-extended-app-bar' /** * Renders the EditorAppBar that extends the {@link BaseAppBar} with the note title or realtime connection alert. @@ -22,15 +23,15 @@ export const EditorAppBar: React.FC = () => { return } else if (isSynced) { return ( - + - + ) } else { return ( - + - + ) } } diff --git a/frontend/src/app/(editor)/@appBar/n/[noteId]/editor-mode-extended-app-bar.tsx b/frontend/src/app/(editor)/@appBar/n/[noteId]/editor-mode-extended-app-bar.tsx new file mode 100644 index 000000000..685d2a790 --- /dev/null +++ b/frontend/src/app/(editor)/@appBar/n/[noteId]/editor-mode-extended-app-bar.tsx @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import type { PropsWithChildren } from 'react' +import { useCallback } from 'react' +import React, { Fragment } from 'react' +import { BaseAppBar } from '../../../../../components/layout/app-bar/base-app-bar' +import { ButtonGroup } from 'react-bootstrap' +import { Eye as IconEye, FileText as IconFileText, WindowSplit as IconWindowSplit } from 'react-bootstrap-icons' +import { IconButton } from '../../../../../components/common/icon-button/icon-button' +import { setEditorSplitPosition } from '../../../../../redux/editor-config/methods' +import { useApplicationState } from '../../../../../hooks/common/use-application-state' +import { useTranslatedText } from '../../../../../hooks/common/use-translated-text' + +/** + * Extended AppBar for the editor mode that includes buttons to switch between the different editor modes + */ +export const EditorModeExtendedAppBar: React.FC = ({ children }) => { + const splitValue = useApplicationState((state) => state.editorConfig.splitPosition) + + const onClickEditorOnly = useCallback(() => { + setEditorSplitPosition(100) + }, []) + + const onClickBothViews = useCallback(() => { + setEditorSplitPosition(50) + }, []) + + const onClickViewOnly = useCallback(() => { + setEditorSplitPosition(0) + }, []) + + const titleEditorOnly = useTranslatedText('editor.viewMode.edit') + const titleBothViews = useTranslatedText('editor.viewMode.both') + const titleViewOnly = useTranslatedText('editor.viewMode.view') + + return ( + + + + 0 && splitValue < 100 ? 'secondary' : 'outline-secondary'} + /> + + + + }> + {children} + + ) +} diff --git a/frontend/src/components/editor-page/splitter/__snapshots__/splitter.spec.tsx.snap b/frontend/src/components/editor-page/splitter/__snapshots__/splitter.spec.tsx.snap index eb2c7ff78..e1697d629 100644 --- a/frontend/src/components/editor-page/splitter/__snapshots__/splitter.spec.tsx.snap +++ b/frontend/src/components/editor-page/splitter/__snapshots__/splitter.spec.tsx.snap @@ -59,183 +59,6 @@ exports[`Splitter resize can change size with mouse 1`] = ` `; -exports[`Splitter resize can change size with mouse 2`] = ` -
-
-
-
- left -
-
-
-
-
- - - BootstrapIconMock_ArrowLeftRight - - -
-
-
-
-
- right -
-
-
-
-`; - -exports[`Splitter resize can change size with mouse 3`] = ` -
-
-
-
- left -
-
-
-
-
- - - BootstrapIconMock_ArrowLeftRight - - -
-
-
-
-
- right -
-
-
-
-`; - -exports[`Splitter resize can change size with mouse 4`] = ` -
-
-
-
- left -
-
-
-
-
- - - BootstrapIconMock_ArrowLeftRight - - -
-
-
-
-
- right -
-
-
-
-`; - exports[`Splitter resize can change size with touch 1`] = `
`; - -exports[`Splitter resize can react to shortcuts 1`] = ` -
-
-
-
- left -
-
-
-
-
- - - BootstrapIconMock_ArrowLeftRight - - -
-
-
-
-
- right -
-
-
-
-`; - -exports[`Splitter resize can react to shortcuts 2`] = ` -
-
-
-
- left -
-
-
-
-
- - - BootstrapIconMock_ArrowLeftRight - - -
-
-
-
-
- right -
-
-
-
-`; - -exports[`Splitter resize can react to shortcuts 3`] = ` -
-
-
-
- left -
-
-
-
-
- - - BootstrapIconMock_ArrowLeftRight - - -
-
-
-
-
- right -
-
-
-
-`; diff --git a/frontend/src/components/editor-page/splitter/hooks/use-keyboard-shortcuts.ts b/frontend/src/components/editor-page/splitter/hooks/use-keyboard-shortcuts.ts index b6330ea57..8ecdfbab2 100644 --- a/frontend/src/components/editor-page/splitter/hooks/use-keyboard-shortcuts.ts +++ b/frontend/src/components/editor-page/splitter/hooks/use-keyboard-shortcuts.ts @@ -1,30 +1,29 @@ /* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import { useEffect } from 'react' +import { setEditorSplitPosition } from '../../../../redux/editor-config/methods' /** * Binds global keyboard shortcuts for setting the split value. - * - * @param setRelativeSplitValue A function that is used to set the split value */ -export const useKeyboardShortcuts = (setRelativeSplitValue: (value: number) => void) => { +export const useKeyboardShortcuts = () => { useEffect(() => { const shortcutHandler = (event: KeyboardEvent): void => { if (event.ctrlKey && event.altKey && event.key === 'b') { - setRelativeSplitValue(50) + setEditorSplitPosition(50) event.preventDefault() } if (event.ctrlKey && event.altKey && event.key === 'v') { - setRelativeSplitValue(0) + setEditorSplitPosition(0) event.preventDefault() } if (event.ctrlKey && event.altKey && (event.key === 'e' || event.key === '€')) { - setRelativeSplitValue(100) + setEditorSplitPosition(100) event.preventDefault() } } @@ -33,5 +32,5 @@ export const useKeyboardShortcuts = (setRelativeSplitValue: (value: number) => v return () => { document.removeEventListener('keydown', shortcutHandler, false) } - }, [setRelativeSplitValue]) + }, []) } diff --git a/frontend/src/components/editor-page/splitter/splitter.spec.tsx b/frontend/src/components/editor-page/splitter/splitter.spec.tsx index 1255e71e4..70639d114 100644 --- a/frontend/src/components/editor-page/splitter/splitter.spec.tsx +++ b/frontend/src/components/editor-page/splitter/splitter.spec.tsx @@ -1,11 +1,19 @@ /* - * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import { Splitter } from './splitter' import { fireEvent, render, screen } from '@testing-library/react' import { Mock } from 'ts-mockery' +import * as EditorConfigModule from '../../../redux/editor-config/methods' +import { mockAppState } from '../../../test-utils/mock-app-state' +import type { EditorConfig } from '../../../redux/editor-config/types' + +jest.mock('../../../hooks/common/use-application-state') +jest.mock('../../../redux/editor-config/methods') + +const setEditorSplitPosition = jest.spyOn(EditorConfigModule, 'setEditorSplitPosition').mockReturnValue() describe('Splitter', () => { describe('resize', () => { @@ -15,16 +23,22 @@ describe('Splitter', () => { }) it('can react to shortcuts', () => { - const view = render(left} right={<>right} />) + render(left} right={<>right} />) + fireEvent.keyDown(document, Mock.of({ ctrlKey: true, altKey: true, key: 'v' })) - expect(view.container).toMatchSnapshot() + expect(setEditorSplitPosition).toHaveBeenCalledWith(0) + fireEvent.keyDown(document, Mock.of({ ctrlKey: true, altKey: true, key: 'e' })) - expect(view.container).toMatchSnapshot() + expect(setEditorSplitPosition).toHaveBeenCalledWith(100) + fireEvent.keyDown(document, Mock.of({ ctrlKey: true, altKey: true, key: 'b' })) - expect(view.container).toMatchSnapshot() + expect(setEditorSplitPosition).toHaveBeenCalledWith(50) }) it('can change size with mouse', async () => { + mockAppState({ + editorConfig: { splitPosition: 50 } as EditorConfig + }) const view = render(left} right={<>right} />) expect(view.container).toMatchSnapshot() const divider = await screen.findByTestId('splitter-divider') @@ -32,15 +46,15 @@ describe('Splitter', () => { fireEvent.mouseDown(divider, {}) fireEvent.mouseMove(window, Mock.of({ buttons: 1, clientX: 1920 })) fireEvent.mouseUp(window) - expect(view.container).toMatchSnapshot() + expect(setEditorSplitPosition).toHaveBeenCalledWith(100) fireEvent.mouseDown(divider, {}) fireEvent.mouseMove(window, Mock.of({ buttons: 1, clientX: 0 })) fireEvent.mouseUp(window) - expect(view.container).toMatchSnapshot() + expect(setEditorSplitPosition).toHaveBeenCalledWith(0) fireEvent.mouseMove(window, Mock.of({ buttons: 1, clientX: 1920 })) - expect(view.container).toMatchSnapshot() + expect(setEditorSplitPosition).toHaveBeenCalledWith(100) }) it('can change size with touch', async () => { diff --git a/frontend/src/components/editor-page/splitter/splitter.tsx b/frontend/src/components/editor-page/splitter/splitter.tsx index 5dbda2173..f232ef2bb 100644 --- a/frontend/src/components/editor-page/splitter/splitter.tsx +++ b/frontend/src/components/editor-page/splitter/splitter.tsx @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -8,6 +8,8 @@ import { DividerButtonsShift, SplitDivider } from './split-divider/split-divider import styles from './splitter.module.scss' import type { MouseEvent, ReactElement, TouchEvent } from 'react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useApplicationState } from '../../../hooks/common/use-application-state' +import { setEditorSplitPosition } from '../../../redux/editor-config/methods' export interface SplitterProps { left?: ReactElement @@ -54,7 +56,7 @@ const SNAP_PERCENTAGE = 10 * @return the created component */ export const Splitter: React.FC = ({ additionalContainerClassName, left, right }) => { - const [relativeSplitValue, setRelativeSplitValue] = useState(50) + const relativeSplitValue = useApplicationState((state) => state.editorConfig.splitPosition) const [resizingInProgress, setResizingInProgress] = useState(false) const adjustedRelativeSplitValue = useMemo(() => Math.min(100, Math.max(0, relativeSplitValue)), [relativeSplitValue]) const splitContainer = useRef(null) @@ -73,19 +75,19 @@ export const Splitter: React.FC = ({ additionalContainerClassName setResizingInProgress(false) }, []) + /** + * Moves the splitter to the left or right side if the relative split value is close to the edges. + */ useEffect(() => { if (!resizingInProgress) { - setRelativeSplitValue((value) => { - if (value < SNAP_PERCENTAGE) { - return 0 - } - if (value > 100 - SNAP_PERCENTAGE) { - return 100 - } - return value - }) + if (relativeSplitValue < SNAP_PERCENTAGE && relativeSplitValue > 0) { + setEditorSplitPosition(0) + } + if (relativeSplitValue > 100 - SNAP_PERCENTAGE && relativeSplitValue < 100) { + setEditorSplitPosition(100) + } } - }, [resizingInProgress]) + }, [resizingInProgress, relativeSplitValue]) /** * Recalculates the panel split based on the absolute mouse/touch position. @@ -109,17 +111,17 @@ export const Splitter: React.FC = ({ additionalContainerClassName const horizontalPositionInSplitContainer = horizontalPosition - splitContainer.current.offsetLeft const newRelativeSize = horizontalPositionInSplitContainer / splitContainer.current.clientWidth const number = newRelativeSize * 100 - setRelativeSplitValue(number) + setEditorSplitPosition(number) moveEvent.preventDefault() }, []) const onLeftButtonClick = useCallback(() => { - setRelativeSplitValue((value) => (value === 100 ? 50 : 0)) - }, []) + setEditorSplitPosition(relativeSplitValue === 100 ? 50 : 0) + }, [relativeSplitValue]) const onRightButtonClick = useCallback(() => { - setRelativeSplitValue((value) => (value === 0 ? 50 : 100)) - }, []) + setEditorSplitPosition(relativeSplitValue === 0 ? 50 : 100) + }, [relativeSplitValue]) const dividerButtonsShift = useMemo(() => { if (relativeSplitValue === 0) { @@ -131,7 +133,7 @@ export const Splitter: React.FC = ({ additionalContainerClassName } }, [relativeSplitValue]) - useKeyboardShortcuts(setRelativeSplitValue) + useKeyboardShortcuts() return (
= ({ children }) => { +export const BaseAppBar: React.FC> = ({ children, additionalContentLeft }) => { return ( = ({ children }) => { {...cypressId('base-app-bar')}>