mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-04 08:49:59 -04:00
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:
parent
4e18ce38f3
commit
762a0a850e
1051 changed files with 0 additions and 35 deletions
|
@ -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>
|
||||
`;
|
|
@ -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])
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
166
frontend/src/components/editor-page/splitter/splitter.tsx
Normal file
166
frontend/src/components/editor-page/splitter/splitter.tsx
Normal 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>
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue