270 lines
10 KiB
TypeScript

import { ColumnComponent, EventCallBackMethods, RowComponent, Tabulator } from "tabulator-tables";
import contextMenu, { MenuItem } from "../../../menus/context_menu.js";
import FNote from "../../../entities/fnote.js";
import { t } from "../../../services/i18n.js";
import { TableData } from "./rows.js";
import link_context_menu from "../../../menus/link_context_menu.js";
import froca from "../../../services/froca.js";
import branches from "../../../services/branches.js";
import Component from "../../../components/component.js";
import { RefObject } from "preact";
export function useContextMenu(parentNote: FNote, parentComponent: Component | null | undefined, tabulator: RefObject<Tabulator>): Partial<EventCallBackMethods> {
const events: Partial<EventCallBackMethods> = {};
if (!tabulator || !parentComponent) return events;
events["rowContext"] = (e, row) => tabulator.current && showRowContextMenu(parentComponent, e as MouseEvent, row, parentNote, tabulator.current);
events["headerContext"] = (e, col) => tabulator.current && showColumnContextMenu(parentComponent, e as MouseEvent, col, parentNote, tabulator.current);
events["renderComplete"] = () => {
const headerRow = tabulator.current?.element.querySelector(".tabulator-header-contents");
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(parentComponent: Component, e: MouseEvent, column: ColumnComponent, parentNote: FNote, tabulator: Tabulator) {
const { title, field } = column.getDefinition();
const sorters = tabulator.getSorters();
const sorter = sorters.find(sorter => sorter.field === field);
const isUserDefinedColumn = (!!field && (field?.startsWith("labels.") || field?.startsWith("relations.")));
contextMenu.show({
items: [
{
title: t("table_view.sort-column-by", { title }),
enabled: !!field,
uiIcon: "bx bx-sort-alt-2",
items: [
{
title: t("table_view.sort-column-ascending"),
checked: (sorter?.dir === "asc"),
uiIcon: "bx bx-empty",
handler: () => tabulator.setSort([
{
column: field!,
dir: "asc",
}
])
},
{
title: t("table_view.sort-column-descending"),
checked: (sorter?.dir === "desc"),
uiIcon: "bx bx-empty",
handler: () => tabulator.setSort([
{
column: field!,
dir: "desc"
}
])
}
]
},
{
title: t("table_view.sort-column-clear"),
enabled: sorters.length > 0,
uiIcon: "bx bx-x-circle",
handler: () => tabulator.clearSort()
},
{
title: "----"
},
{
title: t("table_view.hide-column", { title }),
uiIcon: "bx bx-hide",
handler: () => column.hide()
},
{
title: t("table_view.show-hide-columns"),
uiIcon: "bx bx-columns",
items: buildColumnItems(tabulator)
},
{ title: "----" },
{
title: t("table_view.add-column-to-the-left"),
uiIcon: "bx bx-horizontal-left",
enabled: !column.getDefinition().frozen,
items: buildInsertSubmenu(parentComponent, column, "before"),
handler: () => parentComponent?.triggerCommand("addNewTableColumn", {
referenceColumn: column
})
},
{
title: t("table_view.add-column-to-the-right"),
uiIcon: "bx bx-horizontal-right",
items: buildInsertSubmenu(parentComponent, column, "after"),
handler: () => parentComponent?.triggerCommand("addNewTableColumn", {
referenceColumn: column,
direction: "after"
})
},
{ title: "----" },
{
title: t("table_view.edit-column"),
uiIcon: "bx bxs-edit-alt",
enabled: isUserDefinedColumn,
handler: () => parentComponent?.triggerCommand("addNewTableColumn", {
referenceColumn: column,
columnToEdit: column
})
},
{
title: t("table_view.delete-column"),
uiIcon: "bx bx-trash",
enabled: isUserDefinedColumn,
handler: () => parentComponent?.triggerCommand("deleteTableColumn", {
columnToDelete: column
})
}
],
selectMenuItemHandler() {},
x: e.pageX,
y: e.pageY
});
e.preventDefault();
}
/**
* 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.
*/
function showHeaderContextMenu(parentComponent: Component, e: MouseEvent, tabulator: Tabulator) {
contextMenu.show({
items: [
{
title: t("table_view.show-hide-columns"),
uiIcon: "bx bx-columns",
items: buildColumnItems(tabulator)
},
{ title: "----" },
{
title: t("table_view.new-column"),
uiIcon: "bx bx-empty",
enabled: false
},
...buildInsertSubmenu(parentComponent)
],
selectMenuItemHandler() {},
x: e.pageX,
y: e.pageY
});
e.preventDefault();
}
export function showRowContextMenu(parentComponent: Component, e: MouseEvent, row: RowComponent, parentNote: FNote, tabulator: Tabulator) {
const rowData = row.getData() as TableData;
let parentNoteId: string = parentNote.noteId;
if (tabulator.options.dataTree) {
const parentRow = row.getTreeParent();
if (parentRow) {
parentNoteId = parentRow.getData().noteId as string;
}
}
contextMenu.show({
items: [
...link_context_menu.getItems(),
{ title: "----" },
{
title: t("table_view.row-insert-above"),
uiIcon: "bx bx-horizontal-left bx-rotate-90",
handler: () => parentComponent?.triggerCommand("addNewRow", {
parentNotePath: parentNoteId,
customOpts: {
target: "before",
targetBranchId: rowData.branchId,
}
})
},
{
title: t("table_view.row-insert-child"),
uiIcon: "bx bx-subdirectory-right",
handler: async () => {
const branchId = row.getData().branchId;
const note = await froca.getBranch(branchId)?.getNote();
parentComponent?.triggerCommand("addNewRow", {
parentNotePath: note?.noteId,
customOpts: {
target: "after",
targetBranchId: branchId,
}
});
}
},
{
title: t("table_view.row-insert-below"),
uiIcon: "bx bx-horizontal-left bx-rotate-270",
handler: () => parentComponent?.triggerCommand("addNewRow", {
parentNotePath: parentNoteId,
customOpts: {
target: "after",
targetBranchId: rowData.branchId,
}
})
},
{ title: "----" },
{
title: t("table_context_menu.delete_row"),
uiIcon: "bx bx-trash",
handler: () => branches.deleteNotes([ rowData.branchId ], false, false)
}
],
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, rowData.noteId),
x: e.pageX,
y: e.pageY
});
e.preventDefault();
}
function buildColumnItems(tabulator: Tabulator) {
const items: MenuItem<unknown>[] = [];
for (const column of tabulator.getColumns()) {
const { title } = column.getDefinition();
items.push({
title,
checked: column.isVisible(),
uiIcon: "bx bx-empty",
handler: () => column.toggle()
});
}
return items;
}
function buildInsertSubmenu(parentComponent: Component, referenceColumn?: ColumnComponent, direction?: "before" | "after"): MenuItem<unknown>[] {
return [
{
title: t("table_view.new-column-label"),
uiIcon: "bx bx-hash",
handler: () => {
parentComponent?.triggerCommand("addNewTableColumn", {
referenceColumn,
type: "label",
direction
});
}
},
{
title: t("table_view.new-column-relation"),
uiIcon: "bx bx-transfer",
handler: () => {
parentComponent?.triggerCommand("addNewTableColumn", {
referenceColumn,
type: "relation",
direction
});
}
}
]
}