mirror of
https://github.com/overleaf/overleaf.git
synced 2025-02-10 06:51:07 +00:00
Merge pull request #14317 from overleaf/mj-table-gen-update
[visual] in-place table generator updates GitOrigin-RevId: 410411fd9158e6c8c6fe6a5300556800732a252a
This commit is contained in:
parent
33ac9e18f9
commit
2d15ce8d05
8 changed files with 85 additions and 24 deletions
|
@ -118,7 +118,7 @@ export const Cell: FC<{
|
|||
if (renderDiv.current && !editing) {
|
||||
const tree = parser.parse(toDisplay)
|
||||
const node = tree.topNode
|
||||
|
||||
renderDiv.current.innerText = ''
|
||||
typesetNodeIntoElement(
|
||||
node,
|
||||
renderDiv.current,
|
||||
|
|
|
@ -166,6 +166,16 @@ export class TableSelection {
|
|||
maxY === totalRows - 1
|
||||
)
|
||||
}
|
||||
|
||||
width() {
|
||||
const { minX, maxX } = this.normalized()
|
||||
return maxX - minX + 1
|
||||
}
|
||||
|
||||
height() {
|
||||
const { minY, maxY } = this.normalized()
|
||||
return maxY - minY + 1
|
||||
}
|
||||
}
|
||||
|
||||
const SelectionContext = createContext<
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FC, createContext, useContext } from 'react'
|
||||
import { Positions, TableData } from '../tabular'
|
||||
import { Positions, TableData, TableRenderingError } from '../tabular'
|
||||
import {
|
||||
CellPosition,
|
||||
CellSeparator,
|
||||
|
@ -41,7 +41,7 @@ export const TableProvider: FC<{
|
|||
// TODO: Validate better that the table matches the column definition
|
||||
for (const row of tableData.table.rows) {
|
||||
if (row.cells.length !== tableData.table.columns.length) {
|
||||
throw new Error("Table doesn't match column definition")
|
||||
return <TableRenderingError view={view} codePosition={tabularNode.from} />
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -173,8 +173,15 @@ export const removeRowOrColumns = (
|
|||
const numberOfRows = positions.rowPositions.length
|
||||
|
||||
if (selection.spansEntireTable(numberOfColumns, numberOfRows)) {
|
||||
return emptyTable(view, columnSpecification, positions)
|
||||
emptyTable(view, columnSpecification, positions)
|
||||
return new TableSelection({ cell: 0, row: 0 })
|
||||
}
|
||||
const removedRows =
|
||||
Number(selection.isRowSelected(startRow, numberOfColumns)) *
|
||||
selection.height()
|
||||
const removedColumns =
|
||||
Number(selection.isColumnSelected(startCell, numberOfRows)) *
|
||||
selection.width()
|
||||
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
if (selection.isRowSelected(row, numberOfColumns)) {
|
||||
|
@ -221,6 +228,13 @@ export const removeRowOrColumns = (
|
|||
insert: newSpecification,
|
||||
})
|
||||
view.dispatch({ changes })
|
||||
const updatedNumberOfRows = numberOfRows - removedRows
|
||||
const updatedNumberOfColumns = numberOfColumns - removedColumns
|
||||
// Clamp selection to new table size
|
||||
return new TableSelection({
|
||||
cell: Math.max(0, Math.min(updatedNumberOfColumns - 1, startCell)),
|
||||
row: Math.max(0, Math.min(updatedNumberOfRows - 1, startRow)),
|
||||
})
|
||||
}
|
||||
|
||||
const emptyTable = (
|
||||
|
@ -262,6 +276,13 @@ export const insertRow = (
|
|||
const numberOfColumns = positions.cells[maxY].length
|
||||
const insert = `\n${' &'.repeat(numberOfColumns - 1)}\\\\`
|
||||
view.dispatch({ changes: { from, to: from, insert } })
|
||||
if (!below) {
|
||||
return selection
|
||||
}
|
||||
return new TableSelection(
|
||||
{ cell: 0, row: maxY + 1 },
|
||||
{ cell: numberOfColumns - 1, row: maxY + 1 }
|
||||
)
|
||||
}
|
||||
|
||||
export const insertColumn = (
|
||||
|
@ -300,6 +321,13 @@ export const insertColumn = (
|
|||
insert: generateColumnSpecification(columnSpecification),
|
||||
})
|
||||
view.dispatch({ changes })
|
||||
if (!after) {
|
||||
return selection
|
||||
}
|
||||
return new TableSelection(
|
||||
{ cell: maxX + 1, row: 0 },
|
||||
{ cell: maxX + 1, row: positions.rowPositions.length - 1 }
|
||||
)
|
||||
}
|
||||
|
||||
export const removeNodes = (
|
||||
|
|
|
@ -36,7 +36,6 @@ export const ToolbarButton = memo<{
|
|||
if (command) {
|
||||
event.preventDefault()
|
||||
command(view)
|
||||
view.focus()
|
||||
}
|
||||
},
|
||||
[command, view]
|
||||
|
|
|
@ -23,10 +23,9 @@ export const ToolbarDropdown: FC<{
|
|||
disabled,
|
||||
disabledTooltip,
|
||||
}) => {
|
||||
const { open, onToggle } = useDropdown()
|
||||
const { open, onToggle, ref } = useDropdown()
|
||||
const toggleButtonRef = useRef<HTMLButtonElement | null>(null)
|
||||
const { ref: tabularRef } = useTabularContext()
|
||||
|
||||
const button = (
|
||||
<button
|
||||
ref={toggleButtonRef}
|
||||
|
@ -34,8 +33,13 @@ export const ToolbarDropdown: FC<{
|
|||
id={id}
|
||||
aria-haspopup="true"
|
||||
className={btnClassName}
|
||||
onMouseDown={event => event.preventDefault()}
|
||||
onClick={() => onToggle(!open)}
|
||||
onMouseDown={event => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={() => {
|
||||
onToggle(!open)
|
||||
}}
|
||||
aria-label={tooltip}
|
||||
disabled={disabled}
|
||||
aria-disabled={disabled}
|
||||
|
@ -44,26 +48,30 @@ export const ToolbarDropdown: FC<{
|
|||
<MaterialIcon type={icon} />
|
||||
</button>
|
||||
)
|
||||
const overlay = open && tabularRef.current && (
|
||||
const overlay = tabularRef.current && (
|
||||
<Overlay
|
||||
show
|
||||
onHide={() => onToggle(false)}
|
||||
animation={false}
|
||||
container={tabularRef.current}
|
||||
containerPadding={0}
|
||||
placement="bottom"
|
||||
rootClose
|
||||
show={open}
|
||||
target={toggleButtonRef.current ?? undefined}
|
||||
placement="bottom"
|
||||
container={tabularRef.current}
|
||||
animation
|
||||
containerPadding={0}
|
||||
onHide={() => onToggle(false)}
|
||||
>
|
||||
<Popover
|
||||
id={`${id}-popover`}
|
||||
ref={ref}
|
||||
className="table-generator-toolbar-dropdown-popover"
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions,
|
||||
jsx-a11y/click-events-have-key-events */}
|
||||
<div
|
||||
className="table-generator-toolbar-dropdown-menu"
|
||||
id={`${id}-menu`}
|
||||
role="menu"
|
||||
aria-labelledby={id}
|
||||
onClick={() => {
|
||||
onToggle(false)
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -83,6 +91,7 @@ export const ToolbarDropdown: FC<{
|
|||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
hidden={open}
|
||||
id={id}
|
||||
description={disabled && disabledTooltip ? disabledTooltip : tooltip}
|
||||
overlayProps={{ placement: 'bottom' }}
|
||||
|
|
|
@ -19,7 +19,7 @@ import { useCodeMirrorViewContext } from '../../codemirror-editor'
|
|||
import { useTableContext } from '../contexts/table-context'
|
||||
|
||||
export const Toolbar = memo(function Toolbar() {
|
||||
const { selection } = useSelectionContext()
|
||||
const { selection, setSelection } = useSelectionContext()
|
||||
const view = useCodeMirrorViewContext()
|
||||
const { positions, rowSeparators, cellSeparators, tableEnvironment } =
|
||||
useTableContext()
|
||||
|
@ -159,7 +159,9 @@ export const Toolbar = memo(function Toolbar() {
|
|||
) && !selection.isAnyColumnSelected(positions.rowPositions.length)
|
||||
}
|
||||
command={() =>
|
||||
removeRowOrColumns(view, selection, positions, cellSeparators)
|
||||
setSelection(
|
||||
removeRowOrColumns(view, selection, positions, cellSeparators)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ToolbarDropdown
|
||||
|
@ -174,7 +176,7 @@ export const Toolbar = memo(function Toolbar() {
|
|||
role="menuitem"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
insertColumn(view, selection, positions, false)
|
||||
setSelection(insertColumn(view, selection, positions, false))
|
||||
}}
|
||||
>
|
||||
<span className="table-generator-button-label">
|
||||
|
@ -186,7 +188,7 @@ export const Toolbar = memo(function Toolbar() {
|
|||
role="menuitem"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
insertColumn(view, selection, positions, true)
|
||||
setSelection(insertColumn(view, selection, positions, true))
|
||||
}}
|
||||
>
|
||||
<span className="table-generator-button-label">
|
||||
|
@ -199,7 +201,7 @@ export const Toolbar = memo(function Toolbar() {
|
|||
role="menuitem"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
insertRow(view, selection, positions, false)
|
||||
setSelection(insertRow(view, selection, positions, false))
|
||||
}}
|
||||
>
|
||||
<span className="table-generator-button-label">
|
||||
|
@ -211,7 +213,7 @@ export const Toolbar = memo(function Toolbar() {
|
|||
role="menuitem"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
insertRow(view, selection, positions, true)
|
||||
setSelection(insertRow(view, selection, positions, true))
|
||||
}}
|
||||
>
|
||||
<span className="table-generator-button-label">
|
||||
|
|
|
@ -38,6 +38,19 @@ export class TabularWidget extends WidgetType {
|
|||
)
|
||||
}
|
||||
|
||||
updateDOM(dom: HTMLElement, view: EditorView): boolean {
|
||||
this.element = dom
|
||||
ReactDOM.render(
|
||||
<Tabular
|
||||
view={view}
|
||||
tabularNode={this.tabularNode}
|
||||
tableNode={this.tableNode}
|
||||
/>,
|
||||
this.element
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
destroy() {
|
||||
console.debug('destroying tabular widget')
|
||||
if (this.element) {
|
||||
|
|
Loading…
Reference in a new issue