fix: Move content into to frontend directory

Doing this BEFORE the merge prevents a lot of merge conflicts.

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-11-11 11:16:18 +01:00
parent 4e18ce38f3
commit 762a0a850e
No known key found for this signature in database
GPG key ID: B97799103358209B
1051 changed files with 0 additions and 35 deletions

View file

@ -0,0 +1,716 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Splitter resize can change size with mouse 1`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(50% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle "
>
<div
class="buttons"
>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 50%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with mouse 2`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(50% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle "
>
<div
class="buttons"
>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 50%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with mouse 3`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(50% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle "
>
<div
class="buttons"
>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 50%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with mouse 4`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(50% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle "
>
<div
class="buttons"
>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 50%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with touch 1`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(50% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle "
>
<div
class="buttons"
>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 50%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with touch 2`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(50% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle "
>
<div
class="buttons"
>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 50%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with touch 3`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(50% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle "
>
<div
class="buttons"
>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 50%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;
exports[`Splitter resize can change size with touch 4`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(50% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle "
>
<div
class="buttons"
>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 50%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;
exports[`Splitter resize can react to shortcuts 1`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(0% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle shift-right"
>
<div
class="buttons"
>
<button
class="btn btn-secondary"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 0%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;
exports[`Splitter resize can react to shortcuts 2`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(100% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle shift-left"
>
<div
class="buttons"
>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-secondary"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 100%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;
exports[`Splitter resize can react to shortcuts 3`] = `
<div>
<div
class="flex-fill flex-row d-flex "
>
<div
class="left"
style="width: calc(50% - 5px);"
>
<div
class="inner"
>
left
</div>
</div>
<div
class="split-divider"
data-testid="splitter-divider"
>
<div
class="bg-light middle "
>
<div
class="buttons"
>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-left "
/>
</button>
<span
class="grabber"
>
<i
class="fa fa-arrows-h "
/>
</span>
<button
class="btn btn-light"
type="button"
>
<i
class="fa fa-arrow-right "
/>
</button>
</div>
</div>
</div>
<div
class="right"
style="width: calc(100% - 50%);"
>
<div
class="inner"
>
right
</div>
</div>
</div>
</div>
`;

View file

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useEffect } from 'react'
/**
* 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) => {
useEffect(() => {
const shortcutHandler = (event: KeyboardEvent): void => {
if (event.ctrlKey && event.altKey && event.key === 'b') {
setRelativeSplitValue(50)
event.preventDefault()
}
if (event.ctrlKey && event.altKey && event.key === 'v') {
setRelativeSplitValue(0)
event.preventDefault()
}
if (event.ctrlKey && event.altKey && (event.key === 'e' || event.key === '€')) {
setRelativeSplitValue(100)
event.preventDefault()
}
}
document.addEventListener('keydown', shortcutHandler, false)
return () => {
document.removeEventListener('keydown', shortcutHandler, false)
}
}, [setRelativeSplitValue])
}

View file

@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.split-divider {
width: 15px;
background: white;
z-index: 1;
box-shadow: 3px 0 6px #e7e7e7;
display: flex;
align-items: center;
justify-content: center;
:global(body.dark) & {
box-shadow: 3px 0 6px #7b7b7b;
}
}
.grabber {
cursor: col-resize;
}
.middle {
width: 35px;
height: 35px;
z-index: 100000;
position: absolute;
border-radius: 90px;
border: solid 1px #d5d5d5;
overflow: hidden;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
transition: width 150ms ease-in-out, height 150ms ease-in-out, border-radius 50ms ease-in-out, transform 50ms ease-in-out;
transform: translateX(0);
&.shift-right {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
transform: translateX(8px);
}
&.shift-left {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
transform: translateX(-8px);
}
&:hover, &.open {
width: 135px;
height: 50px;
border-radius: 90px;
}
:global(.btn) {
border-radius: 90px;
}
.buttons {
height: 40px;
width: 3*40px;
position: absolute;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
}

View file

@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react'
import styles from './split-divider.module.scss'
import { testId } from '../../../../utils/test-id'
import { Button } from 'react-bootstrap'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
export enum DividerButtonsShift {
SHIFT_TO_LEFT = 'shift-left',
SHIFT_TO_RIGHT = 'shift-right',
NO_SHIFT = ''
}
export interface SplitDividerProps {
onGrab: () => void
onLeftButtonClick: () => void
onRightButtonClick: () => void
forceOpen: boolean
focusLeft: boolean
focusRight: boolean
dividerButtonsShift: DividerButtonsShift
}
/**
* Renders the divider between the two editor panes.
* This divider supports both mouse and touch interactions.
*
* @param onGrab callback that is triggered if the splitter is grabbed.
* @param onLeftButtonClick callback that is triggered when the left arrow button is pressed
* @param onRightButtonClick callback that is triggered when the right arrow button is pressed
* @param dividerShift defines if the buttons should be shifted to the left or right side
* @param focusLeft defines if the left button should be focused
* @param focusRight defines if the right button should be focused
* @param forceOpen defines if the arrow buttons should always be visible
*/
export const SplitDivider: React.FC<SplitDividerProps> = ({
onGrab,
onLeftButtonClick,
onRightButtonClick,
dividerButtonsShift,
focusLeft,
focusRight,
forceOpen
}) => {
const shiftClass = dividerButtonsShift == '' ? '' : styles[dividerButtonsShift]
return (
<div className={`${styles['split-divider']}`} {...testId('splitter-divider')}>
<div className={`bg-light ${styles['middle']} ${forceOpen ? styles['open'] : ''} ${shiftClass}`}>
<div className={styles['buttons']}>
<Button variant={focusLeft ? 'secondary' : 'light'} onClick={onLeftButtonClick}>
<ForkAwesomeIcon icon={'arrow-left'} />
</Button>
<span onMouseDown={onGrab} onTouchStart={onGrab} className={styles['grabber']}>
<ForkAwesomeIcon icon={'arrows-h'} />
</span>
<Button variant={focusRight ? 'secondary' : 'light'} onClick={onRightButtonClick}>
<ForkAwesomeIcon icon={'arrow-right'} />
</Button>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.left, .right {
overflow: hidden;
}
.inner {
display: flex;
min-width: 20vw;
height: 100%;
flex-direction: column;
}
.move-overlay {
position: absolute;
height: 100%;
width: 100%;
z-index: 100000;
}

View file

@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { fireEvent, render, screen } from '@testing-library/react'
import { Splitter } from './splitter'
import { Mock } from 'ts-mockery'
describe('Splitter', () => {
describe('resize', () => {
beforeEach(() => {
Object.defineProperty(window.HTMLDivElement.prototype, 'clientWidth', { value: 1920 })
Object.defineProperty(window.HTMLDivElement.prototype, 'offsetLeft', { value: 0 })
})
it('can react to shortcuts', () => {
const view = render(<Splitter left={<>left</>} right={<>right</>} />)
fireEvent.keyDown(document, Mock.of<KeyboardEvent>({ ctrlKey: true, altKey: true, key: 'v' }))
expect(view.container).toMatchSnapshot()
fireEvent.keyDown(document, Mock.of<KeyboardEvent>({ ctrlKey: true, altKey: true, key: 'e' }))
expect(view.container).toMatchSnapshot()
fireEvent.keyDown(document, Mock.of<KeyboardEvent>({ ctrlKey: true, altKey: true, key: 'b' }))
expect(view.container).toMatchSnapshot()
})
it('can change size with mouse', async () => {
const view = render(<Splitter left={<>left</>} right={<>right</>} />)
expect(view.container).toMatchSnapshot()
const divider = await screen.findByTestId('splitter-divider')
fireEvent.mouseDown(divider, {})
fireEvent.mouseMove(window, Mock.of<MouseEvent>({ buttons: 1, clientX: 1920 }))
fireEvent.mouseUp(window)
expect(view.container).toMatchSnapshot()
fireEvent.mouseDown(divider, {})
fireEvent.mouseMove(window, Mock.of<MouseEvent>({ buttons: 1, clientX: 0 }))
fireEvent.mouseUp(window)
expect(view.container).toMatchSnapshot()
fireEvent.mouseMove(window, Mock.of<MouseEvent>({ buttons: 1, clientX: 1920 }))
expect(view.container).toMatchSnapshot()
})
it('can change size with touch', async () => {
const view = render(<Splitter left={<>left</>} right={<>right</>} />)
expect(view.container).toMatchSnapshot()
const divider = await screen.findByTestId('splitter-divider')
fireEvent.touchStart(divider, {})
fireEvent.touchMove(window, Mock.of<TouchEvent>({ touches: [{ clientX: 1920 }, { clientX: 200 }] }))
fireEvent.touchEnd(window)
expect(view.container).toMatchSnapshot()
fireEvent.touchStart(divider, {})
fireEvent.touchMove(window, Mock.of<TouchEvent>({ touches: [{ clientX: 0 }, { clientX: 100 }] }))
fireEvent.touchCancel(window)
expect(view.container).toMatchSnapshot()
fireEvent.touchMove(window, Mock.of<TouchEvent>({ touches: [{ clientX: 500 }, { clientX: 900 }] }))
expect(view.container).toMatchSnapshot()
})
})
})

View file

@ -0,0 +1,166 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ReactElement, TouchEvent, MouseEvent } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { DividerButtonsShift, SplitDivider } from './split-divider/split-divider'
import styles from './splitter.module.scss'
import { ShowIf } from '../../common/show-if/show-if'
import { useKeyboardShortcuts } from './hooks/use-keyboard-shortcuts'
export interface SplitterProps {
left?: ReactElement
right?: ReactElement
additionalContainerClassName?: string
}
/**
* Checks if the given {@link Event} is a {@link MouseEvent}.
*
* @param event the event to check
* @return {@link true} if the given event is a {@link MouseEvent}
*/
const isMouseEvent = (event: MouseEvent | TouchEvent): event is MouseEvent => {
return (event as MouseEvent).buttons !== undefined
}
const isLeftMouseButtonClicked = (mouseEvent: MouseEvent): boolean => {
return mouseEvent.buttons === 1
}
/**
* Extracts the absolute horizontal position of the mouse or first touch point from the event.
*
* @param moveEvent The mouse or touch event that contains a pointer position
* @return the extracted horizontal position or {@link undefined} if no position could be extracted
*/
const extractHorizontalPosition = (moveEvent: MouseEvent | TouchEvent): number | undefined => {
if (isMouseEvent(moveEvent)) {
return moveEvent.clientX
} else {
return moveEvent.touches[0]?.clientX
}
}
const SNAP_PERCENTAGE = 10
/**
* Creates a Left/Right splitter react component.
*
* @param additionalContainerClassName css classes that are added to the split container.
* @param left the react component that should be shown on the left side.
* @param right the react component that should be shown on the right side.
* @return the created component
*/
export const Splitter: React.FC<SplitterProps> = ({ additionalContainerClassName, left, right }) => {
const [relativeSplitValue, setRelativeSplitValue] = useState(50)
const [resizingInProgress, setResizingInProgress] = useState(false)
const adjustedRelativeSplitValue = useMemo(() => Math.min(100, Math.max(0, relativeSplitValue)), [relativeSplitValue])
const splitContainer = useRef<HTMLDivElement>(null)
/**
* Starts the splitter resizing.
*/
const onStartResizing = useCallback(() => {
setResizingInProgress(true)
}, [])
/**
* Stops the splitter resizing.
*/
const onStopResizing = useCallback(() => {
setResizingInProgress(false)
}, [])
useEffect(() => {
if (!resizingInProgress) {
setRelativeSplitValue((value) => {
if (value < SNAP_PERCENTAGE) {
return 0
}
if (value > 100 - SNAP_PERCENTAGE) {
return 100
}
return value
})
}
}, [resizingInProgress])
/**
* Recalculates the panel split based on the absolute mouse/touch position.
*
* @param moveEvent is a {@link MouseEvent} or {@link TouchEvent} that got triggered.
*/
const onMove = useCallback((moveEvent: MouseEvent | TouchEvent) => {
if (!splitContainer.current) {
return
}
if (isMouseEvent(moveEvent) && !isLeftMouseButtonClicked(moveEvent)) {
setResizingInProgress(false)
moveEvent.preventDefault()
return undefined
}
const horizontalPosition = extractHorizontalPosition(moveEvent)
if (horizontalPosition === undefined) {
return
}
const horizontalPositionInSplitContainer = horizontalPosition - splitContainer.current.offsetLeft
const newRelativeSize = horizontalPositionInSplitContainer / splitContainer.current.clientWidth
const number = newRelativeSize * 100
setRelativeSplitValue(number)
moveEvent.preventDefault()
}, [])
const onLeftButtonClick = useCallback(() => {
setRelativeSplitValue((value) => (value === 100 ? 50 : 0))
}, [])
const onRightButtonClick = useCallback(() => {
setRelativeSplitValue((value) => (value === 0 ? 50 : 100))
}, [])
const dividerButtonsShift = useMemo(() => {
if (relativeSplitValue === 0) {
return DividerButtonsShift.SHIFT_TO_RIGHT
} else if (relativeSplitValue === 100) {
return DividerButtonsShift.SHIFT_TO_LEFT
} else {
return DividerButtonsShift.NO_SHIFT
}
}, [relativeSplitValue])
useKeyboardShortcuts(setRelativeSplitValue)
return (
<div ref={splitContainer} className={`flex-fill flex-row d-flex ${additionalContainerClassName || ''}`}>
<ShowIf condition={resizingInProgress}>
<div
className={styles['move-overlay']}
onTouchMove={onMove}
onMouseMove={onMove}
onTouchCancel={onStopResizing}
onTouchEnd={onStopResizing}
onMouseUp={onStopResizing}></div>
</ShowIf>
<div className={styles['left']} style={{ width: `calc(${adjustedRelativeSplitValue}% - 5px)` }}>
<div className={styles['inner']}>{left}</div>
</div>
<SplitDivider
onGrab={onStartResizing}
onLeftButtonClick={onLeftButtonClick}
onRightButtonClick={onRightButtonClick}
forceOpen={resizingInProgress}
focusLeft={relativeSplitValue < SNAP_PERCENTAGE}
focusRight={relativeSplitValue > 100 - SNAP_PERCENTAGE}
dividerButtonsShift={dividerButtonsShift}
/>
<div className={styles['right']} style={{ width: `calc(100% - ${adjustedRelativeSplitValue}%)` }}>
<div className={styles['inner']}>{right}</div>
</div>
</div>
)
}