export function Component()

in desktop/plugins/public/databases/DatabasesPlugin.tsx [351:746]


export function Component() {
  const instance = usePlugin(plugin);
  const state = useValue(instance.state);
  const favorites = useValue(instance.favoritesState);

  const onViewModeChanged = useCallback(
    (evt: RadioChangeEvent) => {
      instance.updateViewMode({viewMode: evt.target.value ?? 'data'});
    },
    [instance],
  );

  const onDataClicked = useCallback(() => {
    instance.updateViewMode({viewMode: 'data'});
  }, [instance]);

  const onStructureClicked = useCallback(() => {
    instance.updateViewMode({viewMode: 'structure'});
  }, [instance]);

  const onSQLClicked = useCallback(() => {
    instance.updateViewMode({viewMode: 'SQL'});
  }, [instance]);

  const onTableInfoClicked = useCallback(() => {
    instance.updateViewMode({viewMode: 'tableInfo'});
  }, [instance]);

  const onQueryHistoryClicked = useCallback(() => {
    instance.updateViewMode({viewMode: 'queryHistory'});
  }, [instance]);

  const onRefreshClicked = useCallback(() => {
    instance.state.update((state) => {
      state.error = null;
    });
    instance.refresh();
  }, [instance]);

  const onFavoriteButtonClicked = useCallback(() => {
    if (state.query) {
      instance.addOrRemoveQueryToFavorites(state.query.value);
    }
  }, [instance, state.query]);

  const onDatabaseSelected = useCallback(
    (selected: string) => {
      const dbId =
        instance.state.get().databases.find((x) => x.name === selected)?.id ||
        0;
      instance.updateSelectedDatabase({
        database: dbId,
      });
    },
    [instance],
  );

  const onDatabaseTableSelected = useCallback(
    (selected: string) => {
      instance.updateSelectedDatabaseTable({
        table: selected,
      });
    },
    [instance],
  );

  const onNextPageClicked = useCallback(() => {
    instance.nextPage();
  }, [instance]);

  const onPreviousPageClicked = useCallback(() => {
    instance.previousPage();
  }, [instance]);

  const onExecuteClicked = useCallback(() => {
    const query = instance.state.get().query;
    if (query) {
      instance.execute({query: query.value});
    }
  }, [instance]);

  const onQueryTextareaKeyPress = useCallback(
    (event: KeyboardEvent) => {
      // Implement ctrl+enter as a shortcut for clicking 'Execute'.
      if (event.key === '\n' && event.ctrlKey) {
        event.preventDefault();
        event.stopPropagation();
        onExecuteClicked();
      }
    },
    [onExecuteClicked],
  );

  const onGoToRow = useCallback(
    (row: number, _count: number) => {
      instance.goToRow({row: row});
    },
    [instance],
  );

  const onQueryChanged = useCallback(
    (selected: any) => {
      instance.updateQuery({
        value: selected.target.value,
      });
    },
    [instance],
  );

  const onFavoriteQuerySelected = useCallback(
    (query: string) => {
      instance.updateQuery({
        value: query,
      });
    },
    [instance],
  );

  const pageHighlightedRowsChanged = useCallback(
    (rows: TableHighlightedRows) => {
      instance.pageHighlightedRowsChanged(rows);
    },
    [instance],
  );

  const queryHighlightedRowsChanged = useCallback(
    (rows: TableHighlightedRows) => {
      instance.queryHighlightedRowsChanged(rows);
    },
    [instance],
  );

  const sortOrderChanged = useCallback(
    (sortOrder: TableRowSortOrder) => {
      instance.sortByChanged({sortOrder});
    },
    [instance],
  );

  const onRowEdited = useCallback(
    (change: {[key: string]: string | null}) => {
      const {selectedDatabaseTable, currentStructure, viewMode, currentPage} =
        instance.state.get();
      const highlightedRowIdx = currentPage?.highlightedRows[0] ?? -1;
      const row =
        highlightedRowIdx >= 0
          ? currentPage?.rows[currentPage?.highlightedRows[0]]
          : undefined;
      const columns = currentPage?.columns;
      // currently only allow to edit data shown in Data tab
      if (
        viewMode !== 'data' ||
        selectedDatabaseTable === null ||
        currentStructure === null ||
        currentPage === null ||
        row === undefined ||
        columns === undefined ||
        // only trigger when there is change
        Object.keys(change).length <= 0
      ) {
        return;
      }
      // check if the table has primary key to use for query
      // This is assumed data are in the same format as in SqliteDatabaseDriver.java
      const primaryKeyIdx = currentStructure.columns.indexOf('primary_key');
      const nameKeyIdx = currentStructure.columns.indexOf('column_name');
      const typeIdx = currentStructure.columns.indexOf('data_type');
      const nullableIdx = currentStructure.columns.indexOf('nullable');
      if (primaryKeyIdx < 0 && nameKeyIdx < 0 && typeIdx < 0) {
        console.error(
          'primary_key, column_name, and/or data_type cannot be empty',
        );
        return;
      }
      const primaryColumnIndexes = currentStructure.rows
        .reduce((acc, row) => {
          const primary = row[primaryKeyIdx];
          if (primary.type === 'boolean' && primary.value) {
            const name = row[nameKeyIdx];
            return name.type === 'string' ? acc.concat(name.value) : acc;
          } else {
            return acc;
          }
        }, [] as Array<string>)
        .map((name) => columns.indexOf(name))
        .filter((idx) => idx >= 0);
      // stop if no primary key to distinguish unique query
      if (primaryColumnIndexes.length <= 0) {
        return;
      }

      const types = currentStructure.rows.reduce((acc, row) => {
        const nameValue = row[nameKeyIdx];
        const name = nameValue.type === 'string' ? nameValue.value : null;
        const typeValue = row[typeIdx];
        const type = typeValue.type === 'string' ? typeValue.value : null;
        const nullableValue =
          nullableIdx < 0 ? {type: 'null', value: null} : row[nullableIdx];
        const nullable = nullableValue.value !== false;
        if (name !== null && type !== null) {
          acc[name] = {type, nullable};
        }
        return acc;
      }, {} as {[key: string]: {type: string; nullable: boolean}});

      const changeValue = Object.entries(change).reduce(
        (acc, [key, value]: [string, string | null]) => {
          acc[key] = convertStringToValue(types, key, value);
          return acc;
        },
        {} as {[key: string]: Value},
      );
      instance.execute({
        query: constructUpdateQuery(
          selectedDatabaseTable,
          primaryColumnIndexes.reduce((acc, idx) => {
            acc[columns[idx]] = row[idx];
            return acc;
          }, {} as {[key: string]: Value}),
          changeValue,
        ),
      });
      instance.updatePage({
        ...produce(currentPage, (draft) =>
          Object.entries(changeValue).forEach(
            ([key, value]: [string, Value]) => {
              const columnIdx = draft.columns.indexOf(key);
              if (columnIdx >= 0) {
                draft.rows[highlightedRowIdx][columnIdx] = value;
              }
            },
          ),
        ),
      });
    },
    [instance],
  );

  const databaseOptions = useMemoize(
    (databases) =>
      databases.map((x) => (
        <Option key={x.name} value={x.name} label={x.name}>
          {x.name}
        </Option>
      )),
    [state.databases],
  );

  const selectedDatabaseName = useMemoize(
    (selectedDatabase: number, databases: DatabaseEntry[]) =>
      selectedDatabase && databases[state.selectedDatabase - 1]
        ? databases[selectedDatabase - 1].name
        : undefined,
    [state.selectedDatabase, state.databases],
  );

  const tableOptions = useMemoize(
    (selectedDatabase: number, databases: DatabaseEntry[]) =>
      selectedDatabase && databases[state.selectedDatabase - 1]
        ? databases[selectedDatabase - 1].tables.map((tableName) => (
            <Option key={tableName} value={tableName} label={tableName}>
              {tableName}
            </Option>
          ))
        : [],
    [state.selectedDatabase, state.databases],
  );

  const selectedTableName = useMemoize(
    (
      selectedDatabase: number,
      databases: DatabaseEntry[],
      selectedDatabaseTable: string | null,
    ) =>
      selectedDatabase && databases[selectedDatabase - 1]
        ? databases[selectedDatabase - 1].tables.find(
            (t) => t === selectedDatabaseTable,
          ) ?? databases[selectedDatabase - 1].tables[0]
        : undefined,
    [state.selectedDatabase, state.databases, state.selectedDatabaseTable],
  );

  return (
    <Layout.Container grow>
      <Toolbar position="top">
        <Radio.Group value={state.viewMode} onChange={onViewModeChanged}>
          <Radio.Button value="data" onClick={onDataClicked}>
            <TableOutlined style={{marginRight: 5}} />
            <Typography.Text>Data</Typography.Text>
          </Radio.Button>
          <Radio.Button onClick={onStructureClicked} value="structure">
            <SettingOutlined style={{marginRight: 5}} />
            <Typography.Text>Structure</Typography.Text>
          </Radio.Button>
          <Radio.Button onClick={onSQLClicked} value="SQL">
            <ConsoleSqlOutlined style={{marginRight: 5}} />
            <Typography.Text>SQL</Typography.Text>
          </Radio.Button>
          <Radio.Button onClick={onTableInfoClicked} value="tableInfo">
            <DatabaseOutlined style={{marginRight: 5}} />
            <Typography.Text>Table Info</Typography.Text>
          </Radio.Button>
          <Radio.Button onClick={onQueryHistoryClicked} value="queryHistory">
            <HistoryOutlined style={{marginRight: 5}} />
            <Typography.Text>Query History</Typography.Text>
          </Radio.Button>
        </Radio.Group>
      </Toolbar>
      {state.viewMode === 'data' ||
      state.viewMode === 'structure' ||
      state.viewMode === 'tableInfo' ? (
        <Toolbar position="top">
          <BoldSpan>Database</BoldSpan>
          <Select
            showSearch
            value={selectedDatabaseName}
            onChange={onDatabaseSelected}
            style={{flex: 1}}
            dropdownMatchSelectWidth={false}>
            {databaseOptions}
          </Select>
          <BoldSpan>Table</BoldSpan>
          <Select
            showSearch
            value={selectedTableName}
            onChange={onDatabaseTableSelected}
            style={{flex: 1}}
            dropdownMatchSelectWidth={false}>
            {tableOptions}
          </Select>
          <div />
          <Button onClick={onRefreshClicked} type="default">
            Refresh
          </Button>
        </Toolbar>
      ) : null}
      {state.viewMode === 'SQL' ? (
        <Layout.Container>
          <Toolbar position="top">
            <BoldSpan>Database</BoldSpan>
            <Select
              showSearch
              value={selectedDatabaseName}
              onChange={onDatabaseSelected}
              dropdownMatchSelectWidth={false}>
              {databaseOptions}
            </Select>
          </Toolbar>
          <Layout.Horizontal pad={theme.space.small} style={{paddingBottom: 0}}>
            <TextArea
              onChange={onQueryChanged}
              onKeyPress={onQueryTextareaKeyPress}
              placeholder="Type query here.."
              value={
                state.query !== null && typeof state.query !== 'undefined'
                  ? state.query.value
                  : undefined
              }
            />
          </Layout.Horizontal>
          <Toolbar position="top">
            <Layout.Right>
              <div />
              <Layout.Horizontal gap={theme.space.small}>
                <Button
                  icon={
                    state.query && favorites.includes(state.query.value) ? (
                      <StarFilled />
                    ) : (
                      <StarOutlined />
                    )
                  }
                  onClick={onFavoriteButtonClicked}
                />
                <Dropdown
                  overlay={
                    <FavoritesMenu
                      favorites={favorites}
                      onClick={onFavoriteQuerySelected}
                    />
                  }>
                  <Button onClick={() => {}}>
                    Choose from previous queries <DownOutlined />
                  </Button>
                </Dropdown>
                <Button
                  type="primary"
                  onClick={onExecuteClicked}
                  title={'Execute SQL [Ctrl+Return]'}>
                  Execute
                </Button>
              </Layout.Horizontal>
            </Layout.Right>
          </Toolbar>
        </Layout.Container>
      ) : null}