frontend/apps/quantgrid/src/app/components/MainMenu/MainMenuItems.tsx (510 lines of code) (raw):

import Icon from '@ant-design/icons'; import { EditIcon, FileIcon, FilesMetadata, FormulasContextMenuKeyData, FunctionInfo, getDropdownDivider, getDropdownItem, getDropdownMenuKey, getTableBySizeDropdownItem, GridCell, InsertIcon, MenuItem, QuestionIcon, Shortcut, shortcutApi, ViewIcon, } from '@frontend/common'; import { ParsedSheets } from '@frontend/parser'; import { PanelName } from '../../common'; export const togglePanelKeys: Record<string, PanelName> = { ToggleProjectTree: PanelName.ProjectTree, ToggleInputs: PanelName.Inputs, ToggleCodeEditor: PanelName.CodeEditor, ToggleErrorPanel: PanelName.Errors, ToggleHistoryPanel: PanelName.UndoRedo, }; export const fileMenuKeys = { createProject: 'CreateProject', deleteProject: 'DeleteProject', closeProject: 'CloseProject', shareProject: 'Share project', createWorksheet: 'CreateWorksheet', clearProjectHistory: 'Clear project history', }; export const editMenuKeys = { undo: 'Undo', redo: 'Redo', search: 'Search', deleteWorksheet: 'DeleteWorksheet', renameWorksheet: 'RenameWorksheet', renameProject: 'RenameProject', }; export const viewMenuKeys = { toggleChat: 'ToggleChat', ToggleChatPlacement: 'ToggleChatPlacement', resetSheetColumns: 'Reset spreadsheet columns', }; export const insertMenuKeys = { table: 'Table', newField: 'NewField', insertLeft: 'InsertLeft', insertRight: 'InsertRight', newRow: 'NewRow', newRowAbove: 'NewRowAbove', newRowBelow: 'NewRowBelow', }; export const helpMenuKeys = { shortcuts: 'Shortcuts', toggleGrid: 'Toggle grid', }; const createTableMenuKeys = { byRowRange: 'byRowRange', bySize: 'bySize', copy: 'copy', derived: 'derived', input: 'input', filter: 'filter', sortTable: 'sortTable', uniqueValues: 'uniqueValues', }; export const getCreateTableChildren = ( functions: FunctionInfo[], parsedSheets: ParsedSheets, inputFiles: FilesMetadata[] | null, onCreateTable: (cols: number, rows: number) => void ) => { const inputs = [...(inputFiles ?? [])].sort((a, b) => a.name < b.name ? -1 : 1 ); const rangeFunction = functions.find((func) => func.name === 'RANGE'); const filterFunction = functions.find((func) => func.name === 'FILTER'); const sortByFunction = functions.find((func) => func.name === 'SORTBY'); const uniqueByFunction = functions.find((func) => func.name === 'UNIQUEBY'); let tableNames: string[] = []; for (const sheet of Object.values(parsedSheets)) { tableNames = tableNames.concat( [...sheet.tables.map((table) => table.tableName)].sort() ); } return [ getDropdownItem({ label: 'By row range', key: getDropdownMenuKey<FormulasContextMenuKeyData>( [ 'CreateTable', createTableMenuKeys.byRowRange, rangeFunction?.name, ].join('-'), { insertFormula: rangeFunction?.name + '()', } ), }), getDropdownItem({ label: 'By size', key: createTableMenuKeys.bySize, children: [ getTableBySizeDropdownItem({ key: getDropdownMenuKey<FormulasContextMenuKeyData>( ['Action', createTableMenuKeys.bySize].join('-'), { type: 'size', } ), onCreateTable, }), ], }), getDropdownItem({ label: 'Copy', key: 'Copy', disabled: !tableNames?.length, children: tableNames?.map((name) => getDropdownItem({ label: name, key: getDropdownMenuKey<FormulasContextMenuKeyData>( ['Action', 'Copy', name].join('-'), { tableName: name, type: 'copy', } ), }) ), }), getDropdownItem({ label: 'Derived', key: getDropdownMenuKey(createTableMenuKeys.derived), disabled: !tableNames?.length, children: tableNames?.map((name) => getDropdownItem({ label: name, key: getDropdownMenuKey<FormulasContextMenuKeyData>( ['Action', 'Derived', name].join('-'), { tableName: name, type: 'derived', } ), }) ), }), getDropdownItem({ label: 'Input', key: getDropdownMenuKey(createTableMenuKeys.input), disabled: !inputs?.length, children: inputs?.map(({ name, url }) => getDropdownItem({ label: name, key: getDropdownMenuKey<FormulasContextMenuKeyData>( ['CreateTable', 'Derived', name].join('-'), { insertFormula: `INPUT("${url ?? name}")`, } ), }) ), }), filterFunction ? getDropdownItem({ label: 'Filter', key: getDropdownMenuKey(createTableMenuKeys.filter), disabled: !tableNames?.length, children: tableNames?.map((name) => getDropdownItem({ label: name, key: getDropdownMenuKey<FormulasContextMenuKeyData>( ['CreateTable', 'Filter', name].join('-'), { insertFormula: filterFunction.name + `(${name},)`, } ), }) ), }) : undefined, sortByFunction ? getDropdownItem({ label: 'Sort table', key: getDropdownMenuKey(createTableMenuKeys.sortTable), disabled: !tableNames?.length, children: tableNames?.map((name) => getDropdownItem({ label: name, key: getDropdownMenuKey<FormulasContextMenuKeyData>( ['CreateTable', 'Filter', name].join('-'), { insertFormula: sortByFunction.name + `(${name},)`, } ), }) ), }) : undefined, uniqueByFunction ? getDropdownItem({ label: 'Unique values', key: getDropdownMenuKey(createTableMenuKeys.uniqueValues), disabled: !tableNames?.length, children: tableNames?.map((name) => getDropdownItem({ label: name, key: getDropdownMenuKey<FormulasContextMenuKeyData>( ['CreateTable', 'Filter', name].join('-'), { insertFormula: uniqueByFunction.name + `(${name},)`, } ), }) ), }) : undefined, ]; }; export function getMenuItems({ selectedCell, functions, parsedSheets, inputFiles, isYourProject, isAIPendingChanges, onCreateTable, }: { selectedCell: GridCell | null | undefined; functions: FunctionInfo[]; parsedSheets: ParsedSheets; inputFiles: FilesMetadata[] | null; isYourProject: boolean; isAIPendingChanges: boolean; onCreateTable: (cols: number, rows: number) => void; }) { return [ { label: 'File', key: 'FileMenu', icon: ( <Icon className="stroke-textSecondary" component={() => <FileIcon />} /> ), children: [ getDropdownItem({ label: 'Create project', key: fileMenuKeys.createProject, shortcut: shortcutApi.getLabel(Shortcut.NewProject), }), getDropdownItem({ label: 'Create worksheet', key: fileMenuKeys.createWorksheet, }), getDropdownDivider(), getDropdownItem({ label: 'Clear project history', key: fileMenuKeys.clearProjectHistory, }), getDropdownDivider(), getDropdownItem({ label: 'Delete project', key: fileMenuKeys.deleteProject, disabled: !isYourProject, tooltip: !isYourProject ? 'You are not allowed to delete projects which are not yours' : undefined, }), getDropdownDivider(), getDropdownItem({ label: 'Share project', key: fileMenuKeys.shareProject, disabled: !isYourProject, tooltip: !isYourProject ? 'You are not allowed to share projects which are not yours' : undefined, }), getDropdownDivider(), getDropdownItem({ label: 'Back to projects', key: fileMenuKeys.closeProject, }), ], }, { label: 'Edit', key: 'EditMenu', icon: ( <Icon className="w-[18px] text-textSecondary stroke-textSecondary" component={() => <EditIcon />} /> ), children: [ getDropdownItem({ label: 'Undo', key: editMenuKeys.undo, shortcut: shortcutApi.getLabel(Shortcut.UndoAction), disabled: isAIPendingChanges, tooltip: isAIPendingChanges ? 'Please accept or discard pending AI change before continue working with project' : undefined, }), getDropdownItem({ label: 'Redo', key: editMenuKeys.redo, shortcut: shortcutApi.getLabel(Shortcut.RedoAction), disabled: isAIPendingChanges, tooltip: isAIPendingChanges ? 'Please accept or discard pending AI change before continue working with project' : undefined, }), getDropdownDivider(), getDropdownItem({ label: 'Search', key: editMenuKeys.search, shortcut: shortcutApi.getLabel(Shortcut.SearchWindow), }), getDropdownDivider(), getDropdownItem({ label: 'Rename Project', key: editMenuKeys.renameProject, disabled: !isYourProject, tooltip: !isYourProject ? 'You are not allowed to rename projects which are not yours' : undefined, }), getDropdownItem({ label: 'Rename Worksheet', key: editMenuKeys.renameWorksheet, }), getDropdownItem({ label: 'Delete Worksheet', key: editMenuKeys.deleteWorksheet, }), ], }, { label: 'View', key: 'ViewMenu', icon: ( <Icon className="stroke-textSecondary" component={() => <ViewIcon />} /> ), children: [ getDropdownItem({ label: 'Panels', key: 'PanelMenu', children: [ getDropdownItem({ label: 'Toggle Project Tree', key: 'ToggleProjectTree', shortcut: shortcutApi.getLabel(Shortcut.ToggleProjects), }), getDropdownItem({ label: 'Toggle Code Editor', key: 'ToggleCodeEditor', shortcut: shortcutApi.getLabel(Shortcut.ToggleCodeEditor), }), getDropdownItem({ label: 'Toggle Inputs', key: 'ToggleInputs', shortcut: shortcutApi.getLabel(Shortcut.ToggleInputs), }), getDropdownItem({ label: 'Toggle Error Panel', key: 'ToggleErrorPanel', shortcut: shortcutApi.getLabel(Shortcut.ToggleErrors), }), getDropdownItem({ label: 'Toggle History Panel', key: 'ToggleHistoryPanel', shortcut: shortcutApi.getLabel(Shortcut.ToggleHistory), }), getDropdownItem({ label: 'Toggle Chat', key: 'ToggleChat', shortcut: shortcutApi.getLabel(Shortcut.ToggleChat), }), ], }), getDropdownItem({ label: 'Reset spreadsheet columns', key: viewMenuKeys.resetSheetColumns, }), getDropdownItem({ label: 'Toggle Chat Placement', key: 'ToggleChatPlacement', }), ], }, { label: 'Insert', key: 'InsertMenu', icon: ( <Icon className="w-4 stroke-textSecondary" component={() => <InsertIcon />} /> ), children: [ getDropdownItem({ label: 'Table', key: insertMenuKeys.table, children: getCreateTableChildren( functions, parsedSheets, inputFiles, onCreateTable ) as MenuItem[], }), getDropdownDivider(), getDropdownItem({ label: 'New field', key: insertMenuKeys.newField, disabled: !selectedCell?.table?.tableName, tooltip: !selectedCell?.table?.tableName ? 'No table selected' : undefined, }), getDropdownItem({ label: selectedCell?.table?.isTableHorizontal ? 'Field above' : 'Field to the left', key: insertMenuKeys.insertLeft, disabled: !selectedCell?.field?.fieldName, tooltip: !selectedCell?.field ? 'No field cell selected' : undefined, }), getDropdownItem({ label: selectedCell?.table?.isTableHorizontal ? 'Field below' : 'Field to the right', key: insertMenuKeys.insertRight, disabled: !selectedCell?.field?.fieldName, tooltip: !selectedCell?.field ? 'No field cell selected' : undefined, }), getDropdownDivider(), getDropdownItem({ label: 'New row', key: insertMenuKeys.newRow, disabled: !selectedCell?.table || (!!selectedCell && !selectedCell?.table?.isManual), tooltip: !selectedCell?.table ? 'No table selected' : selectedCell && !selectedCell?.table?.isManual ? 'Only available for manual table' : undefined, }), getDropdownItem({ label: selectedCell?.table?.isTableHorizontal ? 'Row to the left' : 'Row above', key: insertMenuKeys.newRowAbove, disabled: selectedCell?.isFieldHeader || selectedCell?.isTableHeader || (!!selectedCell && !selectedCell?.table?.isManual), tooltip: selectedCell?.isFieldHeader || selectedCell?.isTableHeader ? 'No table cell selected' : selectedCell && !selectedCell?.table?.isManual ? 'Only available for manual table' : undefined, }), getDropdownItem({ label: selectedCell?.table?.isTableHorizontal ? 'Row to the right' : 'Row below', key: insertMenuKeys.newRowBelow, disabled: selectedCell?.isFieldHeader || selectedCell?.isTableHeader || (!!selectedCell && !selectedCell?.table?.isManual), tooltip: selectedCell?.isFieldHeader || selectedCell?.isTableHeader ? 'No table cell selected' : selectedCell && !selectedCell?.table?.isManual ? 'Only available for manual table' : undefined, }), ], }, { label: 'Help', key: 'HelpMenu', icon: ( <Icon className="stroke-textSecondary" component={() => <QuestionIcon />} /> ), children: [ getDropdownItem({ label: 'Keyboard Shortcuts', key: helpMenuKeys.shortcuts, }), getDropdownItem({ label: 'Toggle grid', key: helpMenuKeys.toggleGrid, }), ], }, ]; }