export function SpreadsheetWrapper()

in frontend/apps/quantgrid/src/app/components/SpreadsheetWrapper/SpreadsheetWrapper.tsx [54:644]


export function SpreadsheetWrapper() {
  const { viewGridData } = useContext(ViewportContext);
  const { onSpreadsheetMount, gridApi } = useContext(SpreadsheetContext);
  const { inputList } = useContext(InputsContext);
  const { undo } = useContext(UndoRedoContext);

  const {
    zoom,
    theme,
    formulaBarMode,
    setEditMode,
    isPointClickMode,
    switchPointClickMode,
    canvasSpreadsheetMode,
  } = useContext(AppContext);

  const {
    functions,
    projectName,
    sheetName,
    parsedSheets,
    updateSelectedCell,
    openStatusModal,
    sheetContent,
    projectSheets,
    getCurrentProjectViewport,
    updateIsAIPendingChanges,
    updateIsAIPendingBanner,
    openSheet,
  } = useContext(ProjectContext);

  const gridApiRef = useContext(CanvasSpreadsheetContext);
  const firstViewportChange = useRef(true);

  const [tableStructure, setTableStructure] = useState<GridTable[]>([]);

  const [data, setData] = useState<GridData>({});

  const viewportRef = useRef<CachedViewport>({
    startRow: 1,
    endRow: 1,
  });

  const { publish } = useEventBus<EventBusMessages>();
  const { expandDimTable, showRowReference } = useRequestDimTable();
  const { submitCellEditor } = useSubmitCellEditor();
  const { columnSizes } = useColumnSizes(viewportRef.current);
  const { handlePointClickSelectValue } = usePointClickSelectValue();
  const { promoteRow } = usePromoteRowManualEditDSL();
  const { addOverride, editOverride, removeOverride } =
    useOverridesManualEditDSL();
  const { changeFieldSort } = useApplySortManualEditDSL();
  const { applySuggestion } = useApplySuggestions();
  const { applyListFilter, applyNumberFilter } = useApplyFilterManualEditDSL();
  const {
    addChart,
    addDimension,
    addKey,
    chartResize,
    convertToChart,
    convertToTable,
    deleteField,
    deleteSelectedFieldOrTable,
    editExpression,
    editExpressionWithOverrideRemove,
    moveTable,
    addField,
    removeNote,
    removeDimension,
    moveTableTo,
    removeKey,
    renameField,
    renameTable,
    updateNote,
    swapFields,
    onIncreaseFieldColumnSize,
    onDecreaseFieldColumnSize,
    onChangeFieldColumnSize,
    onToggleTableHeaderVisibility,
    onToggleTableFieldsVisibility,
    onFlipTable,
    onCloneTable,
    onRemoveRow,
  } = useManualEditDSL();
  const {
    editTotalExpression,
    addTotalExpression,
    removeTotalByType,
    removeTotalByIndex,
    toggleTotalByType,
  } = useTotalManualEditDSL();
  const { arrangeTable } = useManualArrangeTableDSL();
  const { deleteTable } = useManualDeleteTableDSL();
  const { createDerivedTable, createManualTable } = useManualCreateEntityDSL();
  const { addTableRow, addTableRowToEnd } = useManualAddTableRowDSL();
  const { pasteCells } = useManualPasteCellsDSL();
  const { getMoreChartKeys, selectChartKey, chartData, charts } = useCharts();
  const { openInEditor } = useOpenInEditor();
  const { onCreateTableAction } = useCreateTableAction();
  const { getSelectedCell } = useSelectedCell();
  const { systemMessageContent } = useSelectionSystemMessage();
  const isCalculateRequested = useRef(false);

  const onEditExpression = useCallback(
    (tableName: string, fieldName: string, expression: string) => {
      editExpression(tableName, fieldName, expression);

      if (canvasSpreadsheetMode) {
        gridApiRef?.current?.hideCellEditor();
      } else {
        gridApi?.hideCellEditor();
      }
    },
    [canvasSpreadsheetMode, editExpression, gridApi, gridApiRef]
  );

  const onCloseTable = useCallback(
    (tableName: string) => {
      deleteTable(tableName);
    },
    [deleteTable]
  );

  const onRenameField = useCallback(
    (tableName: string, oldName: string, newName: string) => {
      renameField(tableName, oldName, newName);

      if (canvasSpreadsheetMode) {
        gridApiRef?.current?.hideCellEditor();
      } else {
        gridApi?.hideCellEditor();
      }
    },
    [canvasSpreadsheetMode, gridApi, gridApiRef, renameField]
  );

  const onRenameTable = useCallback(
    (oldName: string, newName: string) => {
      renameTable(oldName, newName);

      if (canvasSpreadsheetMode) {
        gridApiRef?.current?.hideCellEditor();
      } else {
        gridApi?.hideCellEditor();
      }
    },
    [canvasSpreadsheetMode, gridApi, gridApiRef, renameTable]
  );

  const onCellEditorUpdateValue = useCallback(
    (value: string, cancelEdit: boolean, dimFieldName?: string) => {
      publish({
        topic: 'CellEditorUpdateValue',
        payload: { value, cancelEdit, dimFieldName },
      });
    },
    [publish]
  );

  const onCellEditorChangeEditMode = useCallback(
    (editMode: GridCellEditorMode) => {
      setEditMode(editMode);
    },
    [setEditMode]
  );

  const onCellEditorMessage = useCallback(
    (message: string) => {
      openStatusModal(message);
    },
    [openStatusModal]
  );

  const onSelectionChange = useCallback(
    (selection: SelectedCell | null) => {
      updateSelectedCell(selection);
    },
    [updateSelectedCell]
  );

  const onCanvasSelectionChange = useCallback(
    (selectionEdges: SelectionEdges | null) => {
      updateSelectedCell(getSelectedCell(selectionEdges, data));
    },
    [data, getSelectedCell, updateSelectedCell]
  );

  const onStartPointClick = useCallback(() => {
    switchPointClickMode(true, 'cell-editor');
  }, [switchPointClickMode]);

  const onStopPointClick = useCallback(() => {
    switchPointClickMode(false);
  }, [switchPointClickMode]);

  const onPointClickSelectValue = useCallback(
    (pointClickSelection: GridSelection | null) => {
      handlePointClickSelectValue(null, pointClickSelection);
    },
    [handlePointClickSelectValue]
  );

  const onUndo = useCallback(() => {
    undo();
  }, [undo]);

  const onMount = useCallback(
    (gridApi: Grid, gridService: GridService) => {
      onSpreadsheetMount(gridApi, gridService);
    },
    [onSpreadsheetMount]
  );

  const onScroll = useCallback(
    (
      startCol: number,
      endCol: number,
      startRow: number,
      endRow: number,
      forceRequest?: boolean
    ) => {
      if (!projectName || !sheetName || !sheetContent || !projectSheets) return;

      viewportRef.current = { startRow, endRow };

      const viewportRequest = viewGridData.buildViewportsToRequest({
        startCol,
        endCol,
        startRow,
        endRow,
      });

      if (viewportRequest.length === 0 && !forceRequest) return;

      isCalculateRequested.current = true;
      getCurrentProjectViewport(viewportRequest);
    },
    [
      projectName,
      sheetName,
      sheetContent,
      projectSheets,
      viewGridData,
      getCurrentProjectViewport,
    ]
  );

  const onGetFieldFilterList = useCallback(
    (tableName: string, fieldName: string) => {
      return viewGridData.getFieldFilterList(tableName, fieldName);
    },
    [viewGridData]
  );

  useEffect(() => {
    if (!sheetName || !projectName) return;

    if (canvasSpreadsheetMode) {
      gridApiRef?.current?.clearSelection();
    } else {
      gridApi?.clearSelection();
      gridApi?.clearViewportScroll();
    }
  }, [sheetName, projectName, gridApi, canvasSpreadsheetMode, gridApiRef]);

  const triggerOnScroll = useCallback(
    (forceRequest: boolean) => {
      let gridViewport: SelectionEdges | undefined;

      if (canvasSpreadsheetMode && gridApiRef?.current) {
        gridViewport = gridApiRef.current.getViewportEdges();
      } else if (gridApi) {
        const [startRow, endRow] = gridApi.rowEdges;
        const [startCol, endCol] = gridApi.colEdges;

        gridViewport = { startRow, endRow, startCol, endCol };
      }
      if (!gridViewport) return;

      const { startRow, endRow, startCol, endCol } = gridViewport;
      const normalizedEndRow =
        endRow && endRow < 10 ? chunkSize : endRow || chunkSize;

      const normalizedEndCol = endCol < 10 ? 100 : endCol;

      onScroll(
        startCol,
        normalizedEndCol,
        startRow,
        normalizedEndRow,
        forceRequest
      );
    },
    [canvasSpreadsheetMode, gridApi, gridApiRef, onScroll]
  );

  useEffect(() => {
    if (
      !projectName ||
      !sheetName ||
      !sheetContent ||
      !gridApi ||
      canvasSpreadsheetMode
    )
      return;

    triggerOnScroll(!isCalculateRequested.current);

    isCalculateRequested.current = true;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    gridApi,
    viewGridData,
    projectSheets,
    sheetContent,
    parsedSheets,
    inputList,
    triggerOnScroll,
  ]);

  useEffect(() => {
    if (!gridApiRef?.current) return;

    const gridApi = gridApiRef.current;

    const onViewportChange = debounce((deltaX: number, deltaY: number) => {
      triggerOnScroll(firstViewportChange.current);

      if (firstViewportChange.current) {
        firstViewportChange.current = false;
      }
    }, 100);

    const unsubscribe = gridApi.gridViewportSubscription(onViewportChange);

    onViewportChange(0, 0);

    return () => {
      unsubscribe?.();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [gridApiRef?.current, onScroll, triggerOnScroll]);

  useEffect(() => {
    if (!projectName) return;

    const handleDataUpdate = () => {
      setData(viewGridData.toGridData());
      setTableStructure(viewGridData.getGridTableStructure());
    };

    // init data update call, if component had unmounted
    handleDataUpdate();

    const dataUpdateSubscription =
      viewGridData.shouldUpdate$.subscribe(handleDataUpdate);

    return () => {
      dataUpdateSubscription.unsubscribe();
    };
  }, [projectName, setData, viewGridData]);

  useEffect(() => {
    if (!projectName) return;

    const handleDataUpdate = () => {
      let selection: SelectionEdges | null = null;

      if (canvasSpreadsheetMode && gridApiRef?.current) {
        selection = gridApiRef.current.getSelection();
      } else if (!canvasSpreadsheetMode && gridApi) {
        selection = gridApi.getSelection();
      }

      if (!selection) return;

      updateSelectedCell(getSelectedCell(selection, viewGridData.toGridData()));
    };

    const dataUpdateSubscription =
      viewGridData.shouldUpdate$.subscribe(handleDataUpdate);

    return () => {
      dataUpdateSubscription.unsubscribe();
    };
  }, [
    canvasSpreadsheetMode,
    data,
    getSelectedCell,
    gridApi,
    gridApiRef,
    projectName,
    setData,
    updateSelectedCell,
    viewGridData,
  ]);

  useEffect(() => {
    if (!projectName) return;

    const handleDynamicFieldsRequest = () => {
      triggerOnScroll(false);
    };

    const subscription = viewGridData.tableDynamicFieldsRequest$.subscribe(
      handleDynamicFieldsRequest
    );

    return () => {
      subscription.unsubscribe();
    };
  }, [gridApi, onScroll, projectName, setData, triggerOnScroll, viewGridData]);

  if (canvasSpreadsheetMode) {
    return (
      <CanvasSpreadsheet
        chartData={chartData}
        charts={charts}
        columnSizes={columnSizes}
        currentSheetName={sheetName}
        data={data}
        formulaBarMode={formulaBarMode}
        functions={functions}
        inputFiles={inputList}
        isPointClickMode={isPointClickMode}
        parsedSheets={parsedSheets}
        ref={gridApiRef}
        sheetContent={sheetContent || ''}
        systemMessageContent={systemMessageContent}
        tableStructure={tableStructure}
        theme={theme}
        zoom={zoom}
        onAddChart={addChart}
        onAddDimension={addDimension}
        onAddField={addField}
        onAddKey={addKey}
        onAddOverride={addOverride}
        onAddTableRow={addTableRow}
        onAddTotalExpression={addTotalExpression}
        onAIPendingBanner={updateIsAIPendingBanner}
        onAIPendingChanges={updateIsAIPendingChanges}
        onApplyListFilter={applyListFilter}
        onApplyNumericFilter={applyNumberFilter}
        onApplySuggestion={applySuggestion}
        onArrangeTable={arrangeTable}
        onCellEditorChangeEditMode={onCellEditorChangeEditMode}
        onCellEditorMessage={onCellEditorMessage}
        onCellEditorSubmit={submitCellEditor}
        onCellEditorUpdateValue={onCellEditorUpdateValue}
        onChangeFieldColumnSize={onChangeFieldColumnSize}
        onChartResize={chartResize}
        onCloneTable={onCloneTable}
        onCloseTable={onCloseTable}
        onConvertToChart={convertToChart}
        onConvertToTable={convertToTable}
        onCreateDerivedTable={createDerivedTable}
        onCreateManualTable={createManualTable}
        onDecreaseFieldColumnSize={onDecreaseFieldColumnSize}
        onDelete={deleteSelectedFieldOrTable}
        onDeleteField={deleteField}
        onDeleteTable={deleteTable}
        onDNDTable={moveTableTo}
        onEditExpression={onEditExpression}
        onEditExpressionWithOverrideRemove={editExpressionWithOverrideRemove}
        onEditOverride={editOverride}
        onEditTotalExpression={editTotalExpression}
        onExpandDimTable={expandDimTable}
        onFlipTable={onFlipTable}
        onGetFieldFilterList={onGetFieldFilterList}
        onGetMoreChartKeys={getMoreChartKeys}
        onIncreaseFieldColumnSize={onIncreaseFieldColumnSize}
        onMoveTable={moveTable}
        onOpenInEditor={openInEditor}
        onOpenSheet={openSheet}
        onPaste={pasteCells}
        onPointClickSelectValue={onPointClickSelectValue}
        onRemoveDimension={removeDimension}
        onRemoveKey={removeKey}
        onRemoveNote={removeNote}
        onRemoveOverride={removeOverride}
        onRemoveTotalByIndex={removeTotalByIndex}
        onRemoveTotalByType={removeTotalByType}
        onRenameField={onRenameField}
        onRenameTable={onRenameTable}
        onScroll={onScroll}
        onSelectChartKey={selectChartKey}
        onSelectionChange={onCanvasSelectionChange}
        onShowRowReference={showRowReference}
        onSortChange={changeFieldSort}
        onStartPointClick={onStartPointClick}
        onStopPointClick={onStopPointClick}
        onSwapFields={swapFields}
        onToggleTableFieldsVisibility={onToggleTableFieldsVisibility}
        onToggleTableHeaderVisibility={onToggleTableHeaderVisibility}
        onToggleTotalByType={toggleTotalByType}
        onUndo={onUndo}
        onUpdateNote={updateNote}
      />
    );
  }

  return (
    <Spreadsheet
      chartData={chartData}
      charts={charts}
      columnSizes={columnSizes}
      currentSheetName={sheetName}
      data={data}
      formulaBarMode={formulaBarMode}
      functions={functions}
      inputFiles={inputList}
      isPointClickMode={isPointClickMode}
      parsedSheets={parsedSheets}
      sheetContent={sheetContent || ''}
      systemMessageContent={systemMessageContent}
      tableStructure={tableStructure}
      theme={theme}
      zoom={zoom}
      onAddChart={addChart}
      onAddDimension={addDimension}
      onAddField={addField}
      onAddKey={addKey}
      onAddOverride={addOverride}
      onAddTableRow={addTableRow}
      onAddTableRowToEnd={addTableRowToEnd}
      onAddTotalExpression={addTotalExpression}
      onAIPendingBanner={updateIsAIPendingBanner}
      onAIPendingChanges={updateIsAIPendingChanges}
      onApplyListFilter={applyListFilter}
      onApplyNumericFilter={applyNumberFilter}
      onApplySuggestion={applySuggestion}
      onArrangeTable={arrangeTable}
      onCellEditorChangeEditMode={onCellEditorChangeEditMode}
      onCellEditorMessage={onCellEditorMessage}
      onCellEditorSubmit={submitCellEditor}
      onCellEditorUpdateValue={onCellEditorUpdateValue}
      onChangeFieldColumnSize={onChangeFieldColumnSize}
      onChartResize={chartResize}
      onCloneTable={onCloneTable}
      onCloseTable={onCloseTable}
      onConvertToChart={convertToChart}
      onConvertToTable={convertToTable}
      onCreateDerivedTable={createDerivedTable}
      onCreateManualTable={createManualTable}
      onCreateTableAction={onCreateTableAction}
      onDecreaseFieldColumnSize={onDecreaseFieldColumnSize}
      onDelete={deleteSelectedFieldOrTable}
      onDeleteField={deleteField}
      onDeleteTable={deleteTable}
      onDNDTable={moveTableTo}
      onEditExpression={onEditExpression}
      onEditExpressionWithOverrideRemove={editExpressionWithOverrideRemove}
      onEditOverride={editOverride}
      onEditTotalExpression={editTotalExpression}
      onExpandDimTable={expandDimTable}
      onFlipTable={onFlipTable}
      onGetFieldFilterList={onGetFieldFilterList}
      onGetMoreChartKeys={getMoreChartKeys}
      onIncreaseFieldColumnSize={onIncreaseFieldColumnSize}
      onMount={onMount}
      onMoveTable={moveTable}
      onOpenInEditor={openInEditor}
      onOpenSheet={openSheet}
      onPaste={pasteCells}
      onPointClickSelectValue={onPointClickSelectValue}
      onPromoteRow={promoteRow}
      onRemoveDimension={removeDimension}
      onRemoveKey={removeKey}
      onRemoveNote={removeNote}
      onRemoveOverride={removeOverride}
      onRemoveOverrideRow={onRemoveRow}
      onRemoveTotalByIndex={removeTotalByIndex}
      onRemoveTotalByType={removeTotalByType}
      onRenameField={onRenameField}
      onRenameTable={onRenameTable}
      onScroll={onScroll}
      onSelectChartKey={selectChartKey}
      onSelectionChange={onSelectionChange}
      onShowRowReference={showRowReference}
      onSortChange={changeFieldSort}
      onStartPointClick={onStartPointClick}
      onStopPointClick={onStopPointClick}
      onSwapFields={swapFields}
      onToggleTableFieldsVisibility={onToggleTableFieldsVisibility}
      onToggleTableHeaderVisibility={onToggleTableHeaderVisibility}
      onToggleTotalByType={toggleTotalByType}
      onUndo={onUndo}
      onUpdateNote={updateNote}
    />
  );
}