>(
() => new Set()
);
const [windowHeight, setWindowHeight] = React.useState(window.innerHeight);
+ const vscode = getVSCodeAPI();
+ const configuration = getVSCodeConfiguration();
const logger = new Logger(configuration.logging.react);
const windowRezise = () => {
setWindowHeight(window.innerHeight);
};
- window.addEventListener('contextmenu', e => {
- e.stopImmediatePropagation();
- }, true);
+ window.addEventListener(
+ 'contextmenu',
+ (e) => {
+ e.stopImmediatePropagation();
+ },
+ true
+ );
React.useEffect(() => {
window.addEventListener('resize', windowRezise);
@@ -79,15 +79,16 @@ function Indexes({ tableDetails, configuration, vscode }: IConfigProps) {
const message = event.data;
logger.log('indexes explorer data', message);
switch (message.command) {
- case 'data':
- setRows(message.data.indexes);
- setDataLoaded(true);
+ case 'data':
+ setRows(message.data.indexes);
+ setDataLoaded(true);
}
});
});
const refresh = () => {
const obj = {
+ id: '2',
action: CommandAction.RefreshTableData,
};
logger.log('Refresh Table Data', obj);
@@ -96,12 +97,10 @@ function Indexes({ tableDetails, configuration, vscode }: IConfigProps) {
return (
- {!dataLoaded ? (
-
{action === ProcessAction.Delete ? (
-
- Are You sure You want delete{' '}
- {selectedRows.size} record
- {selectedRows.size > 1 && 's'}?
-
+
+
+ Are You sure You want delete{' '}
+ {selectedRows.size} record
+ {selectedRows.size > 1 && 's'}?
+
+
+ Use delete trigger
+
+
) : action === ProcessAction.Read ? (
<>
@@ -242,20 +289,17 @@ const UpdatePopup: React.FC = ({
>
) : (
-
+
+
)}
@@ -266,11 +310,23 @@ const UpdatePopup: React.FC
= ({
>
{ProcessAction[action]}
- ) : null}
+ ) : (
+ }
+ onClick={() => {
+ setOpen(false);
+ updateRecord();
+ }}
+ >
+ UPDATE
+
+ )}
{
- setUseTriggers(defaultTrigger);
+ setUseWriteTriggers(defaultWriteTrigger);
+ setUseDeleteTriggers(defaultDeleteTrigger);
setOpen(false);
}}
>
diff --git a/src/view/app/Query/query.tsx b/src/view/app/Query/query.tsx
index b28131da..6d599b07 100644
--- a/src/view/app/Query/query.tsx
+++ b/src/view/app/Query/query.tsx
@@ -7,7 +7,12 @@ import {
useState,
} from 'react';
-import DataGrid, { SortColumn, SelectColumn, CopyEvent } from 'react-data-grid';
+import DataGrid, {
+ SortColumn,
+ SelectColumn,
+ CopyEvent,
+ DataGridHandle,
+} from 'react-data-grid';
import { IOETableData } from '@src/db/Oe';
import { CommandAction, ICommand, ProcessAction } from '../model';
@@ -21,6 +26,7 @@ import QueryFormHead from '@app/Components/Layout/Query/QueryFormHead';
import { IFilters } from '@app/common/types';
import { getVSCodeAPI, getVSCodeConfiguration } from '@utils/vscode';
import { green, red } from '@mui/material/colors';
+import { HighlightFieldsCommand } from '@src/common/commands/fieldsCommands';
const filterCSS: CSSProperties = {
inlineSize: '100%',
@@ -72,6 +78,7 @@ function QueryForm({
const [initialDataLoad, setInitialDataLoad] = useState(true);
const [recordColor, setRecordColor] = useState('red');
const [selectedRows, setSelectedRows] = useState>(new Set());
+ const queryGridRef = useRef(null);
const configuration = getVSCodeConfiguration();
const logger = new Logger(configuration.logging.react);
@@ -127,13 +134,34 @@ function QueryForm({
};
}, []);
+ const highlightColumn = (column: string) => {
+ // + 1 because columns start from index 1. Rows start from index 0.
+ const columnIdx: number = selectedColumns.indexOf(column) + 1;
+
+ if (!columnIdx || columnIdx < 0) {
+ return;
+ }
+
+ const cellHeight = getCellHeight();
+ const rowIdx = Math.floor(scrollHeight / cellHeight);
+
+ // scrollToColumn doesn't work, so a workaround is to use selectCell and rowIdx
+ queryGridRef.current?.selectCell({ idx: columnIdx, rowIdx: rowIdx });
+ };
+
const messageEvent = (event) => {
const message = event.data;
logger.log('got query data', message);
switch (message.command) {
+ case 'highlightColumn':
+ highlightColumn((message as HighlightFieldsCommand).column);
+ break;
case 'columns':
setSelectedColumns([...message.columns]);
break;
+ case 'refetch':
+ prepareQuery();
+ break;
case 'submit':
if (message.data.error) {
// should be displayed in UpdatePopup window
@@ -435,28 +463,33 @@ function QueryForm({
minTime: minTime,
},
};
+ if (!isLoading) {
+ setIsLoading(true);
+ }
logger.log('make query', command);
vscode.postMessage(command);
}
- function isAtBottom({ currentTarget }: UIEvent): boolean {
+ const isAtBottom = ({
+ currentTarget,
+ }: UIEvent): boolean => {
return (
currentTarget.scrollTop + 10 >=
currentTarget.scrollHeight - currentTarget.clientHeight
);
- }
+ };
- function isHorizontalScroll({
+ const isHorizontalScroll = ({
currentTarget,
- }: UIEvent): boolean {
+ }: UIEvent): boolean => {
return currentTarget.scrollTop === scrollHeight;
- }
+ };
- async function handleScroll(event: UIEvent) {
+ const handleScroll = (event: UIEvent) => {
+ setScrollHeight(event.currentTarget.scrollTop);
if (isLoading || !isAtBottom(event) || isHorizontalScroll(event)) {
return;
}
- setScrollHeight(event.currentTarget.scrollTop);
setIsLoading(true);
makeQuery(
loaded,
@@ -467,7 +500,7 @@ function QueryForm({
configuration.batchMaxTimeout,
configuration.batchMinTimeout
);
- }
+ };
function onSortClick(inputSortColumns: SortColumn[]) {
if (isLoading) {
@@ -510,7 +543,10 @@ function QueryForm({
const [action, setAction] = useState();
const [readRow, setReadRow] = useState([]);
- const readRecord = (row: string[]) => {
+ const readRecord = (row) => {
+ const selectedRowsSet = new Set();
+ selectedRowsSet.add(row.ROWID);
+ setSelectedRows(selectedRowsSet);
setAction(ProcessAction.Read);
setReadRow(row);
setOpen(true);
@@ -541,18 +577,22 @@ function QueryForm({
}
}
- const calculateHeight = () => {
- const rowCount = isFormatted ? formattedRows.length : rawRows.length;
- let minHeight;
+ const getCellHeight = () => {
if (configuration.gridTextSize === 'Large') {
- minHeight = 40;
+ return 40;
} else if (configuration.gridTextSize === 'Medium') {
- minHeight = 30;
+ return 30;
} else if (configuration.gridTextSize === 'Small') {
- minHeight = 20;
+ return 20;
}
+ return 30;
+ };
+
+ const calculateHeight = () => {
+ const rowCount = isFormatted ? formattedRows.length : rawRows.length;
+ const cellHeight = getCellHeight();
const startingHeight = 85;
- const calculatedHeight = startingHeight + rowCount * minHeight;
+ const calculatedHeight = startingHeight + rowCount * cellHeight;
return calculatedHeight;
};
@@ -603,6 +643,7 @@ function QueryForm({
/>
val.tableName === command.tableName
+ );
+
+ firstEditor?.panel?.reveal();
+ firstEditor?.highlightColumn(command.column);
+ }
+
public resolveWebviewView(
webviewView: vscode.WebviewView
): void | Thenable {
@@ -56,6 +70,11 @@ export class FieldsViewProvider extends PanelViewProvider {
);
this.notifyQueryEditors();
break;
+ case CommandAction.FieldsHighlightColumn:
+ this.highlightQueryEditorsColumn(
+ command as HighlightFieldsCommand
+ );
+ break;
}
});
}
diff --git a/src/webview/PanelViewProvider.ts b/src/webview/PanelViewProvider.ts
index 6aa1d768..c7cfb2a2 100644
--- a/src/webview/PanelViewProvider.ts
+++ b/src/webview/PanelViewProvider.ts
@@ -1,15 +1,17 @@
import path = require('path');
import * as vscode from 'vscode';
import { Constants } from '../common/Constants';
-import { CommandAction, ICommand, TableDetails } from '../view/app/model';
+import { CommandAction, ICommand } from '../view/app/model';
import { TableNode } from '../treeview/TableNode';
import { TablesListProvider } from '../treeview/TablesListProvider';
+import { FavoritesProvider } from '../treeview/FavoritesProvider';
export class PanelViewProvider implements vscode.WebviewViewProvider {
public static readonly viewType = `${Constants.globalExtensionKey}-panel`;
public _view?: vscode.WebviewView;
public tableNode?: TableNode;
public tableListProvider?: TablesListProvider;
+ public favoritesProvider?: FavoritesProvider;
public readonly configuration = vscode.workspace.getConfiguration(
Constants.globalExtensionKey
);
@@ -28,13 +30,9 @@ export class PanelViewProvider implements vscode.WebviewViewProvider {
),
],
};
- this._view.webview.html = this.getWebviewContent({
- fields: [],
- indexes: [],
- selectedColumns: [],
- });
+ this._view.webview.html = this.getWebviewContent();
- this._view.onDidChangeVisibility((ev) => {
+ this._view.onDidChangeVisibility(() => {
if (this._view?.visible) {
if (this.tableNode) {
this.tableListProvider?.displayData(this.tableNode);
@@ -53,7 +51,7 @@ export class PanelViewProvider implements vscode.WebviewViewProvider {
});
}
- private getWebviewContent(data: TableDetails): string {
+ private getWebviewContent(): string {
// Local path to main script run in the webview
const reactAppPathOnDisk = vscode.Uri.file(
path.join(
@@ -83,7 +81,6 @@ export class PanelViewProvider implements vscode.WebviewViewProvider {
diff --git a/src/webview/QueryEditor.ts b/src/webview/QueryEditor.ts
index a5e1ed34..5dacb329 100644
--- a/src/webview/QueryEditor.ts
+++ b/src/webview/QueryEditor.ts
@@ -1,17 +1,19 @@
import path = require('path');
import * as vscode from 'vscode';
-import { ICommand, CommandAction } from '../view/app/model';
+import { ICommand, CommandAction, IConfig } from '../view/app/model';
import { IOETableData } from '../db/Oe';
-import { TableNode } from '../treeview/TableNode';
+import { TableNode, TableNodeSourceEnum } from '../treeview/TableNode';
import { TablesListProvider } from '../treeview/TablesListProvider';
import { FieldsViewProvider } from './FieldsViewProvider';
import { DumpFileFormatter } from './DumpFileFormatter';
import { Logger } from '../common/Logger';
import { ProcessorFactory } from '../repo/processor/ProcessorFactory';
import { Constants } from '../common/Constants';
+import { queryEditorCache } from './queryEditor/queryEditorCache';
+import { FavoritesProvider } from '../treeview/FavoritesProvider';
export class QueryEditor {
- private readonly panel: vscode.WebviewPanel | undefined;
+ public readonly panel: vscode.WebviewPanel | undefined;
private readonly extensionPath: string;
private disposables: vscode.Disposable[] = [];
public tableName: string;
@@ -23,28 +25,43 @@ export class QueryEditor {
private logger = new Logger(this.configuration.get('logging.node')!);
constructor(
- private context: vscode.ExtensionContext,
- private tableNode: TableNode,
- private tableListProvider: TablesListProvider,
- private fieldProvider: FieldsViewProvider
+ private context: vscode.ExtensionContext,
+ private tableNode: TableNode,
+ private tableListProvider: TablesListProvider,
+ private favoritesProvider: FavoritesProvider,
+ private fieldProvider: FieldsViewProvider
) {
this.extensionPath = context.asAbsolutePath('');
this.tableName = tableNode.tableName;
this.fieldsProvider = fieldProvider;
- if (tableListProvider.config) {
- this.readOnly = tableListProvider.config?.isReadOnly;
+ let config: IConfig | undefined;
+ switch (this.tableNode.source) {
+ case TableNodeSourceEnum.Tables:
+ config = this.tableListProvider.config;
+ break;
+ case TableNodeSourceEnum.Favorites:
+ config = this.favoritesProvider.config;
+ break;
+ default:
+ return;
+ }
+
+ if (config) {
+ this.readOnly = config?.isReadOnly;
}
this.panel = vscode.window.createWebviewPanel(
'queryOETable', // Identifies the type of the webview. Used internally
- `${this.tableListProvider.config?.label}.${this.tableNode.tableName}`, // Title of the panel displayed to the user
+ `${config?.label}.${this.tableNode.tableName}`, // Title of the panel displayed to the user
vscode.ViewColumn.One, // Editor column to show the new webview panel in.
{
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [
- vscode.Uri.file(path.join(context.asAbsolutePath(''), 'out')),
+ vscode.Uri.file(
+ path.join(context.asAbsolutePath(''), 'out')
+ ),
],
}
);
@@ -79,121 +96,129 @@ export class QueryEditor {
(command: ICommand) => {
this.logger.log('command:', command);
switch (command.action) {
- case CommandAction.Query:
- if (this.tableListProvider.config) {
- ProcessorFactory.getProcessorInstance()
- .getTableData(
- this.tableListProvider.config,
- this.tableNode.tableName,
- command.params
- )
- .then((oe) => {
- if (this.panel) {
- const obj = {
- id: command.id,
- command: 'data',
- columns: tableNode.cache?.selectedColumns,
- data: oe,
- };
- this.logger.log('data:', obj);
- this.panel?.webview.postMessage(obj);
- }
- });
- }
- break;
- case CommandAction.CRUD:
- if (this.tableListProvider.config) {
- ProcessorFactory.getProcessorInstance()
- .getTableData(
- this.tableListProvider.config,
- this.tableNode.tableName,
- command.params
- )
- .then((oe) => {
- if (this.panel) {
- const obj = {
- id: command.id,
- command: 'crud',
- data: oe,
- };
- this.logger.log('data:', obj);
- this.panel?.webview.postMessage(obj);
- }
- });
- }
- break;
- case CommandAction.Submit:
- if (this.tableListProvider.config) {
- ProcessorFactory.getProcessorInstance()
- .submitTableData(
- this.tableListProvider.config,
- this.tableNode.tableName,
- command.params
- )
- .then((oe) => {
- if (this.panel) {
- const obj = {
- id: command.id,
- command: 'submit',
- data: oe,
- };
- this.logger.log('data:', obj);
- if (
- obj.data.description !== null &&
- obj.data.description !== undefined
- ) {
- if (obj.data.description === '') {
- vscode.window.showErrorMessage(
- 'Database Error: Trigger canceled action'
- );
+ case CommandAction.Query:
+ if (config) {
+ ProcessorFactory.getProcessorInstance()
+ .getTableData(
+ config,
+ this.tableNode.tableName,
+ command.params
+ )
+ .then((oe) => {
+ if (this.panel) {
+ const obj = {
+ id: command.id,
+ command: 'data',
+ columns:
+ tableNode.cache
+ ?.selectedColumns,
+ data: oe,
+ };
+ this.logger.log('data:', obj);
+ this.panel?.webview.postMessage(obj);
+ }
+ });
+ }
+ break;
+ case CommandAction.CRUD:
+ if (config) {
+ ProcessorFactory.getProcessorInstance()
+ .getTableData(
+ config,
+ this.tableNode.tableName,
+ command.params
+ )
+ .then((oe) => {
+ if (this.panel) {
+ const obj = {
+ id: command.id,
+ command: 'crud',
+ data: oe,
+ };
+ this.logger.log('data:', obj);
+ this.panel?.webview.postMessage(obj);
+ }
+ });
+ }
+ break;
+ case CommandAction.Submit:
+ if (config) {
+ ProcessorFactory.getProcessorInstance()
+ .submitTableData(
+ config,
+ this.tableNode.tableName,
+ command.params
+ )
+ .then((oe) => {
+ if (this.panel) {
+ const obj = {
+ id: command.id,
+ command: 'submit',
+ data: oe,
+ };
+ this.logger.log('data:', obj);
+ if (
+ obj.data.description !== null &&
+ obj.data.description !== undefined
+ ) {
+ if (obj.data.description === '') {
+ vscode.window.showErrorMessage(
+ 'Database Error: Trigger canceled action'
+ );
+ } else {
+ vscode.window.showErrorMessage(
+ 'Database Error: ' +
+ obj.data.description
+ );
+ }
} else {
- vscode.window.showErrorMessage(
- 'Database Error: ' + obj.data.description
+ vscode.window.showInformationMessage(
+ 'Action was successful'
);
}
- } else {
- vscode.window.showInformationMessage(
- 'Action was successful'
- );
+ this.panel?.webview.postMessage(obj);
}
- this.panel?.webview.postMessage(obj);
- }
- });
- }
- break;
- case CommandAction.Export:
- if (this.tableListProvider.config) {
- ProcessorFactory.getProcessorInstance()
- .getTableData(
- this.tableListProvider.config,
- this.tableNode.tableName,
- command.params
- )
- .then((oe) => {
- if (this.panel) {
- let exportData = oe;
- if (command.params?.exportType === 'dumpFile') {
- const dumpFileFormatter = new DumpFileFormatter();
- dumpFileFormatter.formatDumpFile(
- oe,
- this.tableNode.tableName,
- this.tableListProvider.config!.label
- );
- exportData = dumpFileFormatter.getDumpFile();
+ });
+ }
+ break;
+ case CommandAction.Export:
+ if (config) {
+ ProcessorFactory.getProcessorInstance()
+ .getTableData(
+ config,
+ this.tableNode.tableName,
+ command.params
+ )
+ .then((oe) => {
+ if (this.panel) {
+ let exportData = oe;
+ if (
+ command.params?.exportType ===
+ 'dumpFile'
+ ) {
+ const dumpFileFormatter =
+ new DumpFileFormatter();
+ dumpFileFormatter.formatDumpFile(
+ oe,
+ this.tableNode.tableName,
+ config!.label
+ );
+ exportData =
+ dumpFileFormatter.getDumpFile();
+ }
+ const obj = {
+ id: command.id,
+ command: 'export',
+ tableName: this.tableNode.tableName,
+ data: exportData,
+ format: command.params!.exportType,
+ };
+ this.logger.log('data:', obj);
+ this.panel?.webview.postMessage(obj);
}
- const obj = {
- id: command.id,
- command: 'export',
- tableName: this.tableNode.tableName,
- data: exportData,
- format: command.params!.exportType,
- };
- this.logger.log('data:', obj);
- this.panel?.webview.postMessage(obj);
- }
- });
- }
- break;
+ });
+ }
+ break;
}
},
undefined,
@@ -204,6 +229,9 @@ export class QueryEditor {
this.panel.onDidDispose(
() => {
+ queryEditorCache.removeQueryEditor(
+ this.tableNode?.getFullName(true)
+ );
// When the panel is closed, cancel any future updates to the webview content
this.fieldsProvider.removeQueryEditor(this);
},
@@ -212,6 +240,14 @@ export class QueryEditor {
);
}
+ public refetchData = (): void => {
+ const obj = {
+ command: 'refetch',
+ };
+ this.logger.log('refetch:', obj);
+ this.panel?.webview.postMessage(obj);
+ };
+
public updateFields() {
const obj = {
command: 'columns',
@@ -221,17 +257,33 @@ export class QueryEditor {
this.panel?.webview.postMessage(obj);
}
+ /**
+ * Creates a request to the frontend to highlight a column
+ * @param {string} column column name to highlight for the table
+ */
+ public highlightColumn(column: string) {
+ const obj = {
+ command: 'highlightColumn',
+ column: column,
+ };
+ this.logger.log('highlighColumn:', obj);
+ this.panel?.webview.postMessage(obj);
+ }
+
private getWebviewContent(tableData: IOETableData): string {
- // Local path to main script run in the webview
+ // Local path to main script run in the webview
const reactAppPathOnDisk = vscode.Uri.file(
path.join(
vscode.Uri.file(
- this.context.asAbsolutePath(path.join('out/view/app', 'query.js'))
+ this.context.asAbsolutePath(
+ path.join('out/view/app', 'query.js')
+ )
).fsPath
)
);
- const reactAppUri = this.panel?.webview.asWebviewUri(reactAppPathOnDisk);
+ const reactAppUri =
+ this.panel?.webview.asWebviewUri(reactAppPathOnDisk);
const cspSource = this.panel?.webview.cspSource;
return `
diff --git a/src/webview/queryEditor/queryEditorCache.ts b/src/webview/queryEditor/queryEditorCache.ts
new file mode 100644
index 00000000..145f2805
--- /dev/null
+++ b/src/webview/queryEditor/queryEditorCache.ts
@@ -0,0 +1,39 @@
+import { QueryEditor } from '../QueryEditor';
+
+const cache = new Map();
+
+/**
+ * Gets the QueryEditor instance associated with a given table name.
+ * @param tableName The name of the table for which to retrieve the QueryEditor.
+ * @returns The QueryEditor instance if found; undefined otherwise.
+ */
+export const getQueryEditor = (tableName: string): QueryEditor | undefined => {
+ return cache.get(tableName);
+};
+
+/**
+ * Sets or updates the QueryEditor instance associated with a given table name.
+ * If a QueryEditor for the table already exists, it will be replaced.
+ * @param tableName The name of the table for which to set the QueryEditor.
+ * @param queryEditor The QueryEditor instance to associate with the table name.
+ */
+export const setQueryEditor = (
+ tableName: string,
+ queryEditor: QueryEditor
+): void => {
+ cache.set(tableName, queryEditor);
+};
+
+/**
+ * Removes the QueryEditor instance associated with a given table name from the cache.
+ * @param tableName The name of the table for which to remove the QueryEditor.
+ */
+export const removeQueryEditor = (tableName: string): void => {
+ cache.delete(tableName);
+};
+
+export const queryEditorCache = {
+ getQueryEditor,
+ setQueryEditor,
+ removeQueryEditor,
+};