added table overlay to table toolbar button. (#763)

Co-authored by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
Co-authored by: Erik Michelson <github@erik.michelson.eu>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2020-11-29 13:17:40 +01:00 committed by GitHub
parent 5077c95cba
commit a24ef18dd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 332 additions and 17 deletions

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2020 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const createNumberRangeArray = (length: number) : number[] => {
return Array.from(Array(length).keys())
}

View file

@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2020 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useCallback, useEffect, useState } from 'react'
import { Button, Form, ModalFooter } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
import { CommonModal } from '../../../../common/modals/common-modal'
import { TableSize } from './table-picker'
export interface CustomTableSizeModalProps {
showModal: boolean
onDismiss: () => void
onTablePicked: (row: number, cols: number) => void
}
export const CustomTableSizeModal: React.FC<CustomTableSizeModalProps> = ({ showModal, onDismiss, onTablePicked }) => {
const { t } = useTranslation()
const [tableSize, setTableSize] = useState<TableSize>({
rows: 0,
columns: 0
})
useEffect(() => {
setTableSize({
rows: 0,
columns: 0
})
}, [showModal])
const onClick = useCallback(() => {
onTablePicked(tableSize.rows, tableSize.columns)
onDismiss()
}, [onDismiss, tableSize, onTablePicked])
return (
<CommonModal
show={showModal}
onHide={() => onDismiss()}
titleI18nKey={'editor.editorToolbar.table.customSize'}
closeButton={true}
icon={'table'}>
<div className={'col-lg-10 d-flex flex-row p-3 align-items-center'}>
<Form.Control
type={'number'}
min={1}
placeholder={t('editor.editorToolbar.table.cols')}
isInvalid={tableSize.columns <= 0}
onChange={(event) => {
const value = Number.parseInt(event.currentTarget.value)
setTableSize(old => ({
rows: old.rows,
columns: isNaN(value) ? 0 : value
}))
}}
/>
<ForkAwesomeIcon icon='times' className='mx-2' fixedWidth={true}/>
<Form.Control
type={'number'}
min={1}
placeholder={t('editor.editorToolbar.table.rows')}
isInvalid={tableSize.rows <= 0}
onChange={(event) => {
const value = Number.parseInt(event.currentTarget.value)
setTableSize(old => ({
rows: isNaN(value) ? 0 : value,
columns: old.columns
}))
}}/>
</div>
<ModalFooter>
<Button onClick={onClick} disabled={tableSize.rows <= 0 || tableSize.columns <= 0}>
{t('editor.editorToolbar.table.create')}
</Button>
</ModalFooter>
</CommonModal>
)
}

View file

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2020 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import CodeMirror from 'codemirror'
import React, { Fragment, useState } from 'react'
import { Button } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
import { addTable } from '../utils/toolbarButtonUtils'
import { TablePicker } from './table-picker'
export interface TablePickerButtonProps {
editor: CodeMirror.Editor
}
export const TablePickerButton: React.FC<TablePickerButtonProps> = ({ editor }) => {
const { t } = useTranslation()
const [showTablePicker, setShowTablePicker] = useState(false)
return (
<Fragment>
<TablePicker
show={showTablePicker}
onDismiss={() => setShowTablePicker(false)}
onTablePicked={(rows, cols) => {
setShowTablePicker(false)
addTable(editor, rows, cols)
}}
/>
<Button variant='light' onClick={() => setShowTablePicker(old => !old)} title={t('editor.editorToolbar.table.title')}>
<ForkAwesomeIcon icon="table"/>
</Button>
</Fragment>
)
}

View file

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2020 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.table-picker-container {
z-index: 1111;
@import "../../../../../style/variables.light";
.table-cell {
border-top: 1px solid $dark;
border-left: 1px solid $dark;
}
.table-container {
border-bottom: 1px solid $dark;
border-right: 1px solid $dark;
display: grid;
grid-template-columns: repeat(10, 15px [col-start]);
grid-template-rows: repeat(8, 15px [row-start]);
}
body.dark {
@import "../../../../../style/variables.dark";
.table-cell {
border-top: 1px solid $dark;
border-left: 1px solid $dark;
}
.table-container {
border-bottom: 1px solid $dark;
border-right: 1px solid $dark;
display: grid;
grid-template-columns: repeat(10, 15px [col-start]);
grid-template-rows: repeat(8, 15px [row-start]);
}
}
}

View file

@ -0,0 +1,85 @@
/*
* SPDX-FileCopyrightText: 2020 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { Button } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { useClickAway } from 'react-use'
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
import { createNumberRangeArray } from '../../../../common/number-range/number-range'
import { CustomTableSizeModal } from './custom-table-size-modal'
import './table-picker.scss'
export interface TablePickerProps {
show: boolean
onDismiss: () => void
onTablePicked: (row: number, cols: number) => void
}
export type TableSize = {
rows: number,
columns: number
}
export const TablePicker: React.FC<TablePickerProps> = ({ show, onDismiss, onTablePicked }) => {
const { t } = useTranslation()
const containerRef = useRef<HTMLDivElement>(null)
const [tableSize, setTableSize] = useState<TableSize>()
const [showDialog, setShowDialog] = useState(false)
useClickAway(containerRef, () => {
onDismiss()
})
useEffect(() => {
setTableSize(undefined)
}, [show])
const onClick = useCallback(() => {
if (tableSize) {
onTablePicked(tableSize.rows, tableSize.columns)
}
}, [onTablePicked, tableSize])
return (
<div className={`position-absolute table-picker-container p-2 ${!show ? 'd-none' : ''} bg-light`} ref={containerRef} role="grid">
<p className={'lead'}>
{ tableSize
? t('editor.editorToolbar.table.size', { cols: tableSize?.columns, rows: tableSize.rows })
: t('editor.editorToolbar.table.title')
}
</p>
<div className={'table-container'}>
{createNumberRangeArray(8).map((row: number) => (
createNumberRangeArray(10).map((col: number) => (
<div
className={`table-cell ${tableSize && row < tableSize.rows && col < tableSize.columns ? 'bg-primary' : ''}`}
onMouseEnter={() => {
setTableSize({
rows: row + 1,
columns: col + 1
})
}}
title={t('editor.editorToolbar.table.size', { cols: col + 1, rows: row + 1 })}
onClick={onClick}
/>
)
)
))}
</div>
<div className="d-flex justify-content-center mt-2">
<Button className={'text-center'} onClick={() => setShowDialog(true)}>
<ForkAwesomeIcon icon="table"/>&nbsp;{t('editor.editorToolbar.table.customSize')}
</Button>
<CustomTableSizeModal
showModal={showDialog}
onDismiss={() => setShowDialog(false)}
onTablePicked={onTablePicked}
/>
</div>
</div>
)
}

View file

@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { EditorPreferences } from './editor-preferences/editor-preferences'
import { EmojiPickerButton } from './emoji-picker/emoji-picker-button'
import { TablePickerButton } from './table-picker/table-picker-button'
import './tool-bar.scss'
import {
addCodeFences,
@ -23,7 +24,6 @@ import {
addList,
addOrderedList,
addQuotes,
addTable,
addTaskList,
makeSelectionBold,
makeSelectionItalic,
@ -102,9 +102,7 @@ export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
</Button>
</ButtonGroup>
<ButtonGroup className={'mx-1 flex-wrap'}>
<Button variant='light' onClick={() => addTable(editor)} title={t('editor.editorToolbar.table')}>
<ForkAwesomeIcon icon="table"/>
</Button>
<TablePickerButton editor={editor}/>
<Button variant='light' onClick={() => addLine(editor)} title={t('editor.editorToolbar.line')}>
<ForkAwesomeIcon icon="minus"/>
</Button>

View file

@ -6,6 +6,7 @@
import { Editor } from 'codemirror'
import { EmojiClickEventDetail } from 'emoji-picker-element/shared'
import { createNumberRangeArray } from '../../../../common/number-range/number-range'
import { getEmojiShortCode } from './emojiUtils'
export const makeSelectionBold = (editor: Editor): void => wrapTextWith(editor, '**')
@ -29,7 +30,15 @@ export const addImage = (editor: Editor): void => addLink(editor, '!')
export const addLine = (editor: Editor): void => changeLines(editor, line => `${line}\n----`)
export const addCollapsableBlock = (editor: Editor): void => changeLines(editor, line => `${line}\n<details>\n <summary>Toggle label</summary>\n Toggled content\n</details>`)
export const addComment = (editor: Editor): void => changeLines(editor, line => `${line}\n> []`)
export const addTable = (editor: Editor): void => changeLines(editor, line => `${line}\n| # 1 | # 2 | # 3 |\n| ---- | ---- | ---- |\n| Text | Text | Text |`)
export const addTable = (editor: Editor, rows: number, columns: number): void => {
const rowArray = createNumberRangeArray(rows)
const colArray = createNumberRangeArray(columns).map(col => col + 1)
const head = '| # ' + colArray.join(' | # ') + ' |'
const divider = '| ' + colArray.map(() => '----').join(' | ') + ' |'
const body = rowArray.map(() => '| ' + colArray.map(() => 'Text').join(' | ') + ' |').join('\n')
const table = `${head}\n${divider}\n${body}`
changeLines(editor, line => `${line}\n${table}`)
}
export const addEmoji = (emoji: EmojiClickEventDetail, editor: Editor): void => {
const shortCode = getEmojiShortCode(emoji)