mirror of
https://github.com/zadam/trilium.git
synced 2025-11-01 20:19:05 +01:00
chore(react/collections/table): set up context menu partially
This commit is contained in:
parent
9d877ec97a
commit
76e903a782
@ -1,31 +1,35 @@
|
|||||||
import { ColumnComponent, RowComponent, Tabulator } from "tabulator-tables";
|
import { ColumnComponent, EventCallBackMethods, RowComponent, Tabulator } from "tabulator-tables";
|
||||||
import contextMenu, { MenuItem } from "../../../menus/context_menu.js";
|
import contextMenu, { MenuItem } from "../../../menus/context_menu.js";
|
||||||
import { TableData } from "./rows.js";
|
import FNote from "../../../entities/fnote.js";
|
||||||
import branches from "../../../services/branches.js";
|
|
||||||
import { t } from "../../../services/i18n.js";
|
import { t } from "../../../services/i18n.js";
|
||||||
|
import { TableData } from "./rows.js";
|
||||||
import link_context_menu from "../../../menus/link_context_menu.js";
|
import link_context_menu from "../../../menus/link_context_menu.js";
|
||||||
import type FNote from "../../../entities/fnote.js";
|
|
||||||
import froca from "../../../services/froca.js";
|
import froca from "../../../services/froca.js";
|
||||||
import type Component from "../../../components/component.js";
|
import branches from "../../../services/branches.js";
|
||||||
|
import Component from "../../../components/component.js";
|
||||||
|
import { RefObject } from "preact";
|
||||||
|
|
||||||
export function setupContextMenu(tabulator: Tabulator, parentNote: FNote) {
|
export function useContextMenu(parentNote: FNote, parentComponent: Component | null | undefined, tabulator: RefObject<Tabulator>): Partial<EventCallBackMethods> {
|
||||||
tabulator.on("rowContext", (e, row) => showRowContextMenu(e, row, parentNote, tabulator));
|
const events: Partial<EventCallBackMethods> = {};
|
||||||
tabulator.on("headerContext", (e, col) => showColumnContextMenu(e, col, parentNote, tabulator));
|
if (!tabulator || !parentComponent) return events;
|
||||||
tabulator.on("renderComplete", () => {
|
|
||||||
const headerRow = tabulator.element.querySelector(".tabulator-header-contents");
|
|
||||||
headerRow?.addEventListener("contextmenu", (e) => showHeaderContextMenu(e, tabulator));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pressing the expand button prevents bubbling and the context menu remains menu when it shouldn't.
|
events["rowContext"] = (e, row) => tabulator.current && showRowContextMenu(parentComponent, e as MouseEvent, row, parentNote, tabulator.current);
|
||||||
if (tabulator.options.dataTree) {
|
events["headerContext"] = (e, col) => tabulator.current && showColumnContextMenu(parentComponent, e as MouseEvent, col, parentNote, tabulator.current);
|
||||||
const dismissContextMenu = () => contextMenu.hide();
|
events["renderComplete"] = () => {
|
||||||
tabulator.on("dataTreeRowExpanded", dismissContextMenu);
|
const headerRow = tabulator.current?.element.querySelector(".tabulator-header-contents");
|
||||||
tabulator.on("dataTreeRowCollapsed", dismissContextMenu);
|
headerRow?.addEventListener("contextmenu", (e) => showHeaderContextMenu(parentComponent, e as MouseEvent, tabulator.current!));
|
||||||
}
|
}
|
||||||
|
// Pressing the expand button prevents bubbling and the context menu remains menu when it shouldn't.
|
||||||
|
if (tabulator.current?.options.dataTree) {
|
||||||
|
const dismissContextMenu = () => contextMenu.hide();
|
||||||
|
events["dataTreeRowExpanded"] = dismissContextMenu;
|
||||||
|
events["dataTreeRowCollapsed"] = dismissContextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showColumnContextMenu(_e: UIEvent, column: ColumnComponent, parentNote: FNote, tabulator: Tabulator) {
|
function showColumnContextMenu(parentComponent: Component, e: MouseEvent, column: ColumnComponent, parentNote: FNote, tabulator: Tabulator) {
|
||||||
const e = _e as MouseEvent;
|
|
||||||
const { title, field } = column.getDefinition();
|
const { title, field } = column.getDefinition();
|
||||||
|
|
||||||
const sorters = tabulator.getSorters();
|
const sorters = tabulator.getSorters();
|
||||||
@ -87,16 +91,16 @@ function showColumnContextMenu(_e: UIEvent, column: ColumnComponent, parentNote:
|
|||||||
title: t("table_view.add-column-to-the-left"),
|
title: t("table_view.add-column-to-the-left"),
|
||||||
uiIcon: "bx bx-horizontal-left",
|
uiIcon: "bx bx-horizontal-left",
|
||||||
enabled: !column.getDefinition().frozen,
|
enabled: !column.getDefinition().frozen,
|
||||||
items: buildInsertSubmenu(e, column, "before"),
|
items: buildInsertSubmenu(parentComponent, column, "before"),
|
||||||
handler: () => getParentComponent(e)?.triggerCommand("addNewTableColumn", {
|
handler: () => parentComponent?.triggerCommand("addNewTableColumn", {
|
||||||
referenceColumn: column
|
referenceColumn: column
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("table_view.add-column-to-the-right"),
|
title: t("table_view.add-column-to-the-right"),
|
||||||
uiIcon: "bx bx-horizontal-right",
|
uiIcon: "bx bx-horizontal-right",
|
||||||
items: buildInsertSubmenu(e, column, "after"),
|
items: buildInsertSubmenu(parentComponent, column, "after"),
|
||||||
handler: () => getParentComponent(e)?.triggerCommand("addNewTableColumn", {
|
handler: () => parentComponent?.triggerCommand("addNewTableColumn", {
|
||||||
referenceColumn: column,
|
referenceColumn: column,
|
||||||
direction: "after"
|
direction: "after"
|
||||||
})
|
})
|
||||||
@ -106,7 +110,7 @@ function showColumnContextMenu(_e: UIEvent, column: ColumnComponent, parentNote:
|
|||||||
title: t("table_view.edit-column"),
|
title: t("table_view.edit-column"),
|
||||||
uiIcon: "bx bxs-edit-alt",
|
uiIcon: "bx bxs-edit-alt",
|
||||||
enabled: isUserDefinedColumn,
|
enabled: isUserDefinedColumn,
|
||||||
handler: () => getParentComponent(e)?.triggerCommand("addNewTableColumn", {
|
handler: () => parentComponent?.triggerCommand("addNewTableColumn", {
|
||||||
referenceColumn: column,
|
referenceColumn: column,
|
||||||
columnToEdit: column
|
columnToEdit: column
|
||||||
})
|
})
|
||||||
@ -115,7 +119,7 @@ function showColumnContextMenu(_e: UIEvent, column: ColumnComponent, parentNote:
|
|||||||
title: t("table_view.delete-column"),
|
title: t("table_view.delete-column"),
|
||||||
uiIcon: "bx bx-trash",
|
uiIcon: "bx bx-trash",
|
||||||
enabled: isUserDefinedColumn,
|
enabled: isUserDefinedColumn,
|
||||||
handler: () => getParentComponent(e)?.triggerCommand("deleteTableColumn", {
|
handler: () => parentComponent?.triggerCommand("deleteTableColumn", {
|
||||||
columnToDelete: column
|
columnToDelete: column
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -131,8 +135,7 @@ function showColumnContextMenu(_e: UIEvent, column: ColumnComponent, parentNote:
|
|||||||
* Shows a context menu which has options dedicated to the header area (the part where the columns are, but in the empty space).
|
* Shows a context menu which has options dedicated to the header area (the part where the columns are, but in the empty space).
|
||||||
* Provides generic options such as toggling columns.
|
* Provides generic options such as toggling columns.
|
||||||
*/
|
*/
|
||||||
function showHeaderContextMenu(_e: Event, tabulator: Tabulator) {
|
function showHeaderContextMenu(parentComponent: Component, e: MouseEvent, tabulator: Tabulator) {
|
||||||
const e = _e as MouseEvent;
|
|
||||||
contextMenu.show({
|
contextMenu.show({
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@ -146,7 +149,7 @@ function showHeaderContextMenu(_e: Event, tabulator: Tabulator) {
|
|||||||
uiIcon: "bx bx-empty",
|
uiIcon: "bx bx-empty",
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
...buildInsertSubmenu(e)
|
...buildInsertSubmenu(parentComponent)
|
||||||
],
|
],
|
||||||
selectMenuItemHandler() {},
|
selectMenuItemHandler() {},
|
||||||
x: e.pageX,
|
x: e.pageX,
|
||||||
@ -155,8 +158,7 @@ function showHeaderContextMenu(_e: Event, tabulator: Tabulator) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showRowContextMenu(_e: UIEvent, row: RowComponent, parentNote: FNote, tabulator: Tabulator) {
|
export function showRowContextMenu(parentComponent: Component, e: MouseEvent, row: RowComponent, parentNote: FNote, tabulator: Tabulator) {
|
||||||
const e = _e as MouseEvent;
|
|
||||||
const rowData = row.getData() as TableData;
|
const rowData = row.getData() as TableData;
|
||||||
|
|
||||||
let parentNoteId: string = parentNote.noteId;
|
let parentNoteId: string = parentNote.noteId;
|
||||||
@ -175,7 +177,7 @@ export function showRowContextMenu(_e: UIEvent, row: RowComponent, parentNote: F
|
|||||||
{
|
{
|
||||||
title: t("table_view.row-insert-above"),
|
title: t("table_view.row-insert-above"),
|
||||||
uiIcon: "bx bx-horizontal-left bx-rotate-90",
|
uiIcon: "bx bx-horizontal-left bx-rotate-90",
|
||||||
handler: () => getParentComponent(e)?.triggerCommand("addNewRow", {
|
handler: () => parentComponent?.triggerCommand("addNewRow", {
|
||||||
parentNotePath: parentNoteId,
|
parentNotePath: parentNoteId,
|
||||||
customOpts: {
|
customOpts: {
|
||||||
target: "before",
|
target: "before",
|
||||||
@ -189,7 +191,7 @@ export function showRowContextMenu(_e: UIEvent, row: RowComponent, parentNote: F
|
|||||||
handler: async () => {
|
handler: async () => {
|
||||||
const branchId = row.getData().branchId;
|
const branchId = row.getData().branchId;
|
||||||
const note = await froca.getBranch(branchId)?.getNote();
|
const note = await froca.getBranch(branchId)?.getNote();
|
||||||
getParentComponent(e)?.triggerCommand("addNewRow", {
|
parentComponent?.triggerCommand("addNewRow", {
|
||||||
parentNotePath: note?.noteId,
|
parentNotePath: note?.noteId,
|
||||||
customOpts: {
|
customOpts: {
|
||||||
target: "after",
|
target: "after",
|
||||||
@ -201,7 +203,7 @@ export function showRowContextMenu(_e: UIEvent, row: RowComponent, parentNote: F
|
|||||||
{
|
{
|
||||||
title: t("table_view.row-insert-below"),
|
title: t("table_view.row-insert-below"),
|
||||||
uiIcon: "bx bx-horizontal-left bx-rotate-270",
|
uiIcon: "bx bx-horizontal-left bx-rotate-270",
|
||||||
handler: () => getParentComponent(e)?.triggerCommand("addNewRow", {
|
handler: () => parentComponent?.triggerCommand("addNewRow", {
|
||||||
parentNotePath: parentNoteId,
|
parentNotePath: parentNoteId,
|
||||||
customOpts: {
|
customOpts: {
|
||||||
target: "after",
|
target: "after",
|
||||||
@ -223,16 +225,6 @@ export function showRowContextMenu(_e: UIEvent, row: RowComponent, parentNote: F
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getParentComponent(e: MouseEvent) {
|
|
||||||
if (!e.target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $(e.target)
|
|
||||||
.closest(".component")
|
|
||||||
.prop("component") as Component;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildColumnItems(tabulator: Tabulator) {
|
function buildColumnItems(tabulator: Tabulator) {
|
||||||
const items: MenuItem<unknown>[] = [];
|
const items: MenuItem<unknown>[] = [];
|
||||||
for (const column of tabulator.getColumns()) {
|
for (const column of tabulator.getColumns()) {
|
||||||
@ -249,13 +241,13 @@ function buildColumnItems(tabulator: Tabulator) {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildInsertSubmenu(e: MouseEvent, referenceColumn?: ColumnComponent, direction?: "before" | "after"): MenuItem<unknown>[] {
|
function buildInsertSubmenu(parentComponent: Component, referenceColumn?: ColumnComponent, direction?: "before" | "after"): MenuItem<unknown>[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: t("table_view.new-column-label"),
|
title: t("table_view.new-column-label"),
|
||||||
uiIcon: "bx bx-hash",
|
uiIcon: "bx bx-hash",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
getParentComponent(e)?.triggerCommand("addNewTableColumn", {
|
parentComponent?.triggerCommand("addNewTableColumn", {
|
||||||
referenceColumn,
|
referenceColumn,
|
||||||
type: "label",
|
type: "label",
|
||||||
direction
|
direction
|
||||||
@ -266,7 +258,7 @@ function buildInsertSubmenu(e: MouseEvent, referenceColumn?: ColumnComponent, di
|
|||||||
title: t("table_view.new-column-relation"),
|
title: t("table_view.new-column-relation"),
|
||||||
uiIcon: "bx bx-transfer",
|
uiIcon: "bx bx-transfer",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
getParentComponent(e)?.triggerCommand("addNewTableColumn", {
|
parentComponent?.triggerCommand("addNewTableColumn", {
|
||||||
referenceColumn,
|
referenceColumn,
|
||||||
type: "relation",
|
type: "relation",
|
||||||
direction
|
direction
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "preact/hooks";
|
import { useContext, useEffect, useRef, useState } from "preact/hooks";
|
||||||
import { ViewModeProps } from "../interface";
|
import { ViewModeProps } from "../interface";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { buildColumnDefinitions } from "./columns";
|
import { buildColumnDefinitions } from "./columns";
|
||||||
@ -6,8 +6,9 @@ import getAttributeDefinitionInformation, { buildRowDefinitions, TableData } fro
|
|||||||
import { useNoteLabelInt } from "../../react/hooks";
|
import { useNoteLabelInt } from "../../react/hooks";
|
||||||
import { canReorderRows } from "../../view_widgets/table_view/dragging";
|
import { canReorderRows } from "../../view_widgets/table_view/dragging";
|
||||||
import Tabulator from "./tabulator";
|
import Tabulator from "./tabulator";
|
||||||
import {SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, ColumnDefinition, DataTreeModule} from 'tabulator-tables';
|
import { Tabulator as VanillaTabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, ColumnDefinition, DataTreeModule} from 'tabulator-tables';
|
||||||
|
import { useContextMenu } from "./context_menu";
|
||||||
|
import { ParentComponent } from "../../react/react_utils";
|
||||||
interface TableConfig {
|
interface TableConfig {
|
||||||
tableData?: {
|
tableData?: {
|
||||||
columns?: ColumnDefinition[];
|
columns?: ColumnDefinition[];
|
||||||
@ -18,6 +19,8 @@ export default function TableView({ note, viewConfig }: ViewModeProps<TableConfi
|
|||||||
const [ maxDepth ] = useNoteLabelInt(note, "maxNestingDepth") ?? -1;
|
const [ maxDepth ] = useNoteLabelInt(note, "maxNestingDepth") ?? -1;
|
||||||
const [ columnDefs, setColumnDefs ] = useState<ColumnDefinition[]>();
|
const [ columnDefs, setColumnDefs ] = useState<ColumnDefinition[]>();
|
||||||
const [ rowData, setRowData ] = useState<TableData[]>();
|
const [ rowData, setRowData ] = useState<TableData[]>();
|
||||||
|
const tabulatorRef = useRef<VanillaTabulator>(null);
|
||||||
|
const parentComponent = useContext(ParentComponent);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const info = getAttributeDefinitionInformation(note);
|
const info = getAttributeDefinitionInformation(note);
|
||||||
@ -34,14 +37,18 @@ export default function TableView({ note, viewConfig }: ViewModeProps<TableConfi
|
|||||||
});
|
});
|
||||||
}, [ note ]);
|
}, [ note ]);
|
||||||
|
|
||||||
|
const contextMenuEvents = useContextMenu(note, parentComponent, tabulatorRef);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-view">
|
<div className="table-view">
|
||||||
{columnDefs && (
|
{columnDefs && (
|
||||||
<Tabulator
|
<Tabulator
|
||||||
|
tabulatorRef={tabulatorRef}
|
||||||
className="table-view-container"
|
className="table-view-container"
|
||||||
columns={columnDefs}
|
columns={columnDefs}
|
||||||
data={rowData}
|
data={rowData}
|
||||||
modules={[ SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule ]}
|
modules={[ SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule ]}
|
||||||
|
{...contextMenuEvents}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,27 +1,29 @@
|
|||||||
import { useEffect, useRef } from "preact/hooks";
|
import { useEffect, useLayoutEffect, useRef } from "preact/hooks";
|
||||||
import { ColumnDefinition, Module, Tabulator as VanillaTabulator } from "tabulator-tables";
|
import { ColumnDefinition, EventCallBackMethods, Module, Tabulator as VanillaTabulator } from "tabulator-tables";
|
||||||
import "tabulator-tables/dist/css/tabulator.css";
|
import "tabulator-tables/dist/css/tabulator.css";
|
||||||
import "../../../../src/stylesheets/table.css";
|
import "../../../../src/stylesheets/table.css";
|
||||||
|
import { RefObject } from "preact";
|
||||||
|
|
||||||
interface TableProps<T> {
|
interface TableProps<T> extends Partial<EventCallBackMethods> {
|
||||||
|
tabulatorRef: RefObject<VanillaTabulator>;
|
||||||
className?: string;
|
className?: string;
|
||||||
columns: ColumnDefinition[];
|
columns: ColumnDefinition[];
|
||||||
data?: T[];
|
data?: T[];
|
||||||
modules?: (new (table: VanillaTabulator) => Module)[];
|
modules?: (new (table: VanillaTabulator) => Module)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Tabulator<T>({ className, columns, data, modules }: TableProps<T>) {
|
export default function Tabulator<T>({ className, columns, data, modules, tabulatorRef: externalTabulatorRef, ...events }: TableProps<T>) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const tabulatorRef = useRef<VanillaTabulator>(null);
|
const tabulatorRef = useRef<VanillaTabulator>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!modules) return;
|
if (!modules) return;
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
VanillaTabulator.registerModule(module);
|
VanillaTabulator.registerModule(module);
|
||||||
}
|
}
|
||||||
}, [modules]);
|
}, [modules]);
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
const tabulator = new VanillaTabulator(containerRef.current, {
|
const tabulator = new VanillaTabulator(containerRef.current, {
|
||||||
@ -30,10 +32,26 @@ export default function Tabulator<T>({ className, columns, data, modules }: Tabl
|
|||||||
});
|
});
|
||||||
|
|
||||||
tabulatorRef.current = tabulator;
|
tabulatorRef.current = tabulator;
|
||||||
|
externalTabulatorRef.current = tabulator;
|
||||||
|
|
||||||
return () => tabulator.destroy();
|
return () => tabulator.destroy();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const tabulator = tabulatorRef.current;
|
||||||
|
if (!tabulator) return;
|
||||||
|
|
||||||
|
for (const [ eventName, handler ] of Object.entries(events)) {
|
||||||
|
tabulator.on(eventName as keyof EventCallBackMethods, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
for (const [ eventName, handler ] of Object.entries(events)) {
|
||||||
|
tabulator.off(eventName as keyof EventCallBackMethods, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, Object.values(events));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className={className} />
|
<div ref={containerRef} className={className} />
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user