diff --git a/packages/tools/src/driverBase.ts b/packages/tools/src/driverBase.ts
index d76318b9d..3a304525e 100644
--- a/packages/tools/src/driverBase.ts
+++ b/packages/tools/src/driverBase.ts
@@ -161,4 +161,9 @@ export const driverBase = {
getCollectionExportQueryJson(collection: string, condition: any, sort: any) {
return null;
},
+
+ dataEditorTypesBehaviour: {
+ parseSqlNull: true,
+ parseHexAsBuffer: true,
+ },
};
diff --git a/packages/tools/src/stringTools.ts b/packages/tools/src/stringTools.ts
index 21be36385..723e7b110 100644
--- a/packages/tools/src/stringTools.ts
+++ b/packages/tools/src/stringTools.ts
@@ -1,6 +1,8 @@
import _isString from 'lodash/isString';
import _isArray from 'lodash/isArray';
+import _isNumber from 'lodash/isNumber';
import _isPlainObject from 'lodash/isPlainObject';
+import { DataEditorTypesBehaviour } from 'dbgate-types';
export function arrayToHexString(byteArray) {
return byteArray.reduce((output, elem) => output + ('0' + elem.toString(16)).slice(-2), '').toUpperCase();
@@ -15,36 +17,103 @@ export function hexStringToArray(inputString) {
return res;
}
-export function parseCellValue(value) {
+export function parseCellValue(value, editorTypes?: DataEditorTypesBehaviour) {
if (!_isString(value)) return value;
- if (value == '(NULL)') return null;
-
- const mHex = value.match(/^0x([0-9a-fA-F][0-9a-fA-F])+$/);
- if (mHex) {
- return {
- type: 'Buffer',
- data: hexStringToArray(value.substring(2)),
- };
+ if (editorTypes?.parseSqlNull) {
+ if (value == '(NULL)') return null;
}
- const mOid = value.match(/^ObjectId\("([0-9a-f]{24})"\)$/);
- if (mOid) {
- return { $oid: mOid[1] };
+ if (editorTypes?.parseHexAsBuffer) {
+ const mHex = value.match(/^0x([0-9a-fA-F][0-9a-fA-F])+$/);
+ if (mHex) {
+ return {
+ type: 'Buffer',
+ data: hexStringToArray(value.substring(2)),
+ };
+ }
+ }
+
+ if (editorTypes?.parseObjectIdAsDollar) {
+ const mOid = value.match(/^ObjectId\("([0-9a-f]{24})"\)$/);
+ if (mOid) {
+ return { $oid: mOid[1] };
+ }
+ }
+
+ if (editorTypes?.parseJsonNull) {
+ if (value == 'null') return null;
+ }
+
+ if (editorTypes?.parseJsonBoolean) {
+ if (value == 'true') return true;
+ if (value == 'false') return false;
+ }
+
+ if (editorTypes?.parseNumber) {
+ if (/^-?[0-9]+(?:\.[0-9]+)?$/.test(value)) {
+ return parseFloat(value);
+ }
+ }
+
+ if (editorTypes?.parseJsonArray || editorTypes?.parseJsonObject) {
+ const jsonValue = safeJsonParse(value);
+ if (_isPlainObject(jsonValue) && editorTypes?.parseJsonObject) return jsonValue;
+ if (_isArray(jsonValue) && editorTypes?.parseJsonArray) return jsonValue;
}
return value;
}
-export function stringifyCellValue(value) {
- if (value === null) return '(NULL)';
- if (value === undefined) return '(NoField)';
- if (value?.type == 'Buffer' && _isArray(value.data)) return '0x' + arrayToHexString(value.data);
- if (value?.$oid) return `ObjectId("${value?.$oid}")`;
- if (_isPlainObject(value) || _isArray(value)) return JSON.stringify(value);
+function parseObjectIdAsDollar(value) {
+ if (value?.$oid) return value;
+ if (_isString(value)) {
+ if (value.match(/^[0-9a-f]{24}$/)) return { $oid: value };
+ const mOid = value.match(/^ObjectId\("([0-9a-f]{24})"\)$/);
+ if (mOid) {
+ return { $oid: mOid[1] };
+ }
+ }
return value;
}
+export function stringifyCellValue(value, editorTypes?: DataEditorTypesBehaviour) {
+ if (editorTypes?.parseSqlNull) {
+ if (value === null) return '(NULL)';
+ }
+ if (value === undefined) return '(NoField)';
+ if (editorTypes?.parseJsonNull) {
+ if (value === null) return 'null';
+ }
+ if (editorTypes?.parseJsonBoolean) {
+ if (value === true) return 'true';
+ if (value === false) return 'false';
+ }
+ if (editorTypes?.parseHexAsBuffer) {
+ if (value?.type == 'Buffer' && _isArray(value.data)) return '0x' + arrayToHexString(value.data);
+ }
+ if (editorTypes?.parseObjectIdAsDollar) {
+ if (value?.$oid) return `ObjectId("${value?.$oid}")`;
+ }
+ if (editorTypes?.parseJsonArray) {
+ if (_isArray(value)) return JSON.stringify(value);
+ }
+ if (editorTypes?.parseJsonObject) {
+ if (_isPlainObject(value)) return JSON.stringify(value);
+ }
+ if (editorTypes?.parseNumber) {
+ if (_isNumber(value)) return value.toString();
+ }
+
+ if (_isString(value)) return value;
+
+ // fallback
+ if (_isNumber(value)) return value.toString();
+ if (value === null || value === undefined) return '';
+
+ return '';
+}
+
export function safeJsonParse(json, defaultValue?, logError = false) {
if (_isArray(json) || _isPlainObject(json)) {
return json;
@@ -127,3 +196,40 @@ export function parseSqlDefaultValue(value: string) {
}
return undefined;
}
+
+export function detectTypeIcon(value) {
+ if (value === null) return 'icon type-null';
+ if (value?.$oid) return 'icon type-objectid';
+ if (_isString(value)) return 'icon type-string';
+ if (_isNumber(value)) return 'icon type-number';
+ if (_isPlainObject(value)) return 'icon type-object';
+ if (_isArray(value)) return 'icon type-array';
+ if (value === true || value === false) return 'icon type-boolean';
+ return 'icon type-unknown';
+}
+
+export function getConvertValueMenu(value, onSetValue, editorTypes?: DataEditorTypesBehaviour) {
+ return [
+ editorTypes?.supportStringType && {
+ text: 'String',
+ onClick: () => onSetValue(stringifyCellValue(value, editorTypes)),
+ },
+ editorTypes?.supportNumberType && { text: 'Number', onClick: () => onSetValue(parseFloat(value)) },
+ editorTypes?.supportNullType && { text: 'Null', onClick: () => onSetValue(null) },
+ editorTypes?.supportBooleanType && {
+ text: 'Boolean',
+ onClick: () => onSetValue(value?.toString()?.toLowerCase() == 'true' || value == '1'),
+ },
+ editorTypes?.supportObjectIdType && { text: 'ObjectId', onClick: () => onSetValue(parseObjectIdAsDollar(value)) },
+ editorTypes?.supportJsonType && {
+ text: 'JSON',
+ onClick: () => {
+ const jsonValue = safeJsonParse(value);
+ if (jsonValue != null) {
+ console.log('**** ON SET VALUE', jsonValue);
+ onSetValue(jsonValue);
+ }
+ },
+ },
+ ];
+}
diff --git a/packages/types/engines.d.ts b/packages/types/engines.d.ts
index c828a4b9e..f6008c4a8 100644
--- a/packages/types/engines.d.ts
+++ b/packages/types/engines.d.ts
@@ -93,6 +93,26 @@ export interface CollectionSortDefinitionItem {
export type CollectionSortDefinition = CollectionSortDefinitionItem[];
+export interface DataEditorTypesBehaviour {
+ parseSqlNull?: boolean;
+ parseJsonNull?: boolean;
+ parseJsonBoolean?: boolean;
+ parseNumber?: boolean;
+ parseJsonArray?: boolean;
+ parseJsonObject?: boolean;
+ parseHexAsBuffer?: boolean;
+ parseObjectIdAsDollar?: boolean;
+
+ explicitDataType?: boolean;
+ supportNumberType?: boolean;
+ supportStringType?: boolean;
+ supportBooleanType?: boolean;
+ supportDateType?: boolean;
+ supportNullType?: boolean;
+ supportJsonType?: boolean;
+ supportObjectIdType?: boolean;
+}
+
export interface FilterBehaviourProvider {
getFilterBehaviour(dataType: string, standardFilterBehaviours: { [id: string]: FilterBehaviour }): FilterBehaviour;
}
@@ -105,6 +125,7 @@ export interface EngineDriver extends FilterBehaviourProvider {
editorMode?: string;
readOnlySessions: boolean;
supportedKeyTypes: SupportedDbKeyType[];
+ dataEditorTypesBehaviour: DataEditorTypesBehaviour;
supportsDatabaseUrl?: boolean;
supportsDatabaseDump?: boolean;
supportsServerSummary?: boolean;
diff --git a/packages/web/src/datagrid/DataGridCell.svelte b/packages/web/src/datagrid/DataGridCell.svelte
index f22b394c2..cb49efa79 100644
--- a/packages/web/src/datagrid/DataGridCell.svelte
+++ b/packages/web/src/datagrid/DataGridCell.svelte
@@ -1,13 +1,14 @@
{/if}
- {#if col.foreignKey && rowData && rowData[col.uniqueName] && !isCurrentCell}
+ {#if editorTypes?.explicitDataType}
+ {#if value !== undefined}
+ getConvertValueMenu(value, onSetValue, editorTypes)}
+ />
+ {/if}
+ {:else if col.foreignKey && rowData && rowData[col.uniqueName] && !isCurrentCell}
onSetFormView(rowData, col)} />
- {/if}
-
- {#if col.foreignKey && isCurrentCell && onDictionaryLookup}
+ {:else if col.foreignKey && isCurrentCell && onDictionaryLookup}
- {/if}
-
- {#if isJson}
+ {:else if isJson}
openJsonDocument(value, undefined, true)} />
- {/if}
-
- {#if jsonParsedValue && _.isPlainObject(jsonParsedValue)}
+ {:else if jsonParsedValue && _.isPlainObject(jsonParsedValue)}
openJsonDocument(jsonParsedValue, undefined, true)} />
- {/if}
-
- {#if _.isArray(jsonParsedValue || value)}
+ {:else if _.isArray(jsonParsedValue || value)}
{
diff --git a/packages/web/src/datagrid/DataGridCore.svelte b/packages/web/src/datagrid/DataGridCore.svelte
index ec51a266f..fc5031724 100644
--- a/packages/web/src/datagrid/DataGridCore.svelte
+++ b/packages/web/src/datagrid/DataGridCore.svelte
@@ -1557,7 +1557,7 @@
}
let colIndex = startCol;
for (const cell of rowData) {
- setCellValue([rowIndex, colIndex], parseCellValue(cell));
+ setCellValue([rowIndex, colIndex], parseCellValue(cell, display?.driver?.dataEditorTypesBehaviour));
colIndex += 1;
}
rowIndex += 1;
diff --git a/packages/web/src/datagrid/DataGridRow.svelte b/packages/web/src/datagrid/DataGridRow.svelte
index 9c6924a9d..f41f86a92 100644
--- a/packages/web/src/datagrid/DataGridRow.svelte
+++ b/packages/web/src/datagrid/DataGridRow.svelte
@@ -62,6 +62,7 @@
options="{col.options}"
canSelectMultipleOptions="{col.canSelectMultipleOptions}"
onSetValue={value => grider.setCellValue(rowIndex, col.uniqueName, value)}
+ {driver}
/>
{:else}
|
@@ -21,6 +22,7 @@
{onSetValue}
{options}
{canSelectMultipleOptions}
+ {driver}
/>
{:else}
{/if}
diff --git a/packages/web/src/datagrid/InplaceInput.svelte b/packages/web/src/datagrid/InplaceInput.svelte
index aec5d7b61..fdc8c22c4 100644
--- a/packages/web/src/datagrid/InplaceInput.svelte
+++ b/packages/web/src/datagrid/InplaceInput.svelte
@@ -14,6 +14,7 @@
export let onSetValue;
export let width;
export let cellValue;
+ export let driver;
let domEditor;
let showEditorButton = true;
@@ -22,6 +23,8 @@
const isChangedRef = createRef(!!inplaceEditorState.text);
+ $: editorTypes = driver?.dataEditorTypesBehaviour;
+
function handleKeyDown(event) {
showEditorButton = false;
@@ -32,7 +35,7 @@
break;
case keycodes.enter:
if (isChangedRef.get()) {
- onSetValue(parseCellValue(domEditor.value));
+ onSetValue(parseCellValue(domEditor.value, editorTypes));
isChangedRef.set(false);
}
domEditor.blur();
@@ -41,7 +44,7 @@
break;
case keycodes.tab:
if (isChangedRef.get()) {
- onSetValue(parseCellValue(domEditor.value));
+ onSetValue(parseCellValue(domEditor.value, editorTypes));
isChangedRef.set(false);
}
domEditor.blur();
@@ -51,7 +54,7 @@
case keycodes.s:
if (isCtrlOrCommandKey(event)) {
if (isChangedRef.get()) {
- onSetValue(parseCellValue(domEditor.value));
+ onSetValue(parseCellValue(domEditor.value, editorTypes));
isChangedRef.set(false);
}
event.preventDefault();
@@ -63,7 +66,7 @@
function handleBlur() {
if (isChangedRef.get()) {
- onSetValue(parseCellValue(domEditor.value));
+ onSetValue(parseCellValue(domEditor.value, editorTypes));
// grider.setCellValue(rowIndex, uniqueName, editor.value);
isChangedRef.set(false);
}
@@ -71,7 +74,7 @@
}
onMount(() => {
- domEditor.value = inplaceEditorState.text || stringifyCellValue(cellValue);
+ domEditor.value = inplaceEditorState.text || stringifyCellValue(cellValue, editorTypes);
domEditor.focus();
if (inplaceEditorState.selectAll) {
domEditor.select();
@@ -102,7 +105,7 @@
dispatchInsplaceEditor({ type: 'close' });
showModal(EditCellDataModal, {
- value: stringifyCellValue(cellValue),
+ value: stringifyCellValue(cellValue, editorTypes),
onSave: onSetValue,
});
}}
diff --git a/packages/web/src/datagrid/InplaceSelect.svelte b/packages/web/src/datagrid/InplaceSelect.svelte
index 7148c2837..a1d58a45f 100644
--- a/packages/web/src/datagrid/InplaceSelect.svelte
+++ b/packages/web/src/datagrid/InplaceSelect.svelte
@@ -11,6 +11,7 @@
export let cellValue;
export let options;
export let canSelectMultipleOptions;
+ export let driver;
let value;
let valueInit;
@@ -18,7 +19,7 @@
let isOptionsHidden = false;
onMount(() => {
- value = inplaceEditorState.text || stringifyCellValue(cellValue);
+ value = inplaceEditorState.text || stringifyCellValue(cellValue, driver?.dataEditorTypesBehaviour);
valueInit = value;
const optionsSelected = value.split(',');
diff --git a/packages/web/src/formview/FormView.svelte b/packages/web/src/formview/FormView.svelte
index e24d90216..10e27a633 100644
--- a/packages/web/src/formview/FormView.svelte
+++ b/packages/web/src/formview/FormView.svelte
@@ -273,7 +273,10 @@
export function copyToClipboard() {
const column = getCellColumn(currentCell);
if (!column) return;
- const text = currentCell[1] % 2 == 1 ? extractRowCopiedValue(rowData, column.uniqueName) : column.columnName;
+ const text =
+ currentCell[1] % 2 == 1
+ ? extractRowCopiedValue(rowData, column.uniqueName, display?.driver?.dataEditorTypesBehaviour)
+ : column.columnName;
copyTextToClipboard(text);
}
@@ -631,11 +634,12 @@
{#if rowData && $inplaceEditorState.cell && rowIndex == $inplaceEditorState.cell[0] && chunkIndex * 2 + 1 == $inplaceEditorState.cell[1]}
{
grider.setCellValue(0, col.uniqueName, value);
}}
@@ -644,6 +648,7 @@
handleLookup(col)}
+ onSetValue={value => {
+ grider.setCellValue(0, col.uniqueName, value);
+ }}
/>
{/if}
diff --git a/packages/web/src/formview/ShowFormDropDownButton.svelte b/packages/web/src/formview/ShowFormDropDownButton.svelte
new file mode 100644
index 000000000..d0977aaf3
--- /dev/null
+++ b/packages/web/src/formview/ShowFormDropDownButton.svelte
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte
index c64332de1..61be8eadd 100644
--- a/packages/web/src/icons/FontIcon.svelte
+++ b/packages/web/src/icons/FontIcon.svelte
@@ -186,6 +186,16 @@
'icon num-9-outline': 'mdi mdi-numeric-9-circle-outline',
'icon num-9-plus-outline': 'mdi mdi-numeric-9-plus-circle-outline',
+ 'icon type-string': 'mdi mdi-code-string',
+ 'icon type-object': 'mdi mdi-code-braces-box',
+ 'icon type-array': 'mdi mdi-code-array',
+ 'icon type-number': 'mdi mdi-pound-box',
+ 'icon type-boolean': 'mdi mdi-code-equal',
+ 'icon type-date': 'mdi mdi-alpha-d-box',
+ 'icon type-objectid': 'mdi mdi-alpha-i-box',
+ 'icon type-null': 'mdi mdi-code-equal',
+ 'icon type-unknown': 'mdi mdi-help-box',
+
'img ok': 'mdi mdi-check-circle color-icon-green',
'img ok-inv': 'mdi mdi-check-circle color-icon-inv-green',
'img alert': 'mdi mdi-alert-circle color-icon-blue',
diff --git a/packages/web/src/utility/clipboard.ts b/packages/web/src/utility/clipboard.ts
index f1885763d..516d69071 100644
--- a/packages/web/src/utility/clipboard.ts
+++ b/packages/web/src/utility/clipboard.ts
@@ -1,6 +1,7 @@
import _ from 'lodash';
import { arrayToHexString, stringifyCellValue } from 'dbgate-tools';
import yaml from 'js-yaml';
+import { DataEditorTypesBehaviour } from 'dbgate-types';
export function copyTextToClipboard(text) {
const oldFocus = document.activeElement;
@@ -71,13 +72,13 @@ export async function getClipboardText() {
return await navigator.clipboard.readText();
}
-export function extractRowCopiedValue(row, col) {
+export function extractRowCopiedValue(row, col, editorTypes?: DataEditorTypesBehaviour) {
let value = row[col];
if (value === undefined) value = _.get(row, col);
- return stringifyCellValue(value);
+ return stringifyCellValue(value, editorTypes);
}
-const clipboardHeadersFormatter = (delimiter) => (columns) => {
+const clipboardHeadersFormatter = delimiter => columns => {
return columns.join(delimiter);
};
diff --git a/plugins/dbgate-plugin-mongo/src/frontend/driver.js b/plugins/dbgate-plugin-mongo/src/frontend/driver.js
index b24d07d14..6eb0db144 100644
--- a/plugins/dbgate-plugin-mongo/src/frontend/driver.js
+++ b/plugins/dbgate-plugin-mongo/src/frontend/driver.js
@@ -122,6 +122,24 @@ const driver = {
sort: convertToMongoSort(sort) || {},
};
},
+
+ dataEditorTypesBehaviour: {
+ parseJsonNull: true,
+ parseJsonBoolean: true,
+ parseNumber: true,
+ parseJsonArray: true,
+ parseJsonObject: true,
+ parseObjectIdAsDollar: true,
+
+ explicitDataType: true,
+ supportNumberType: true,
+ supportStringType: true,
+ supportBooleanType: true,
+ supportDateType: true,
+ supportJsonType: true,
+ supportObjectIdType: true,
+ supportNullType: true,
+ },
};
module.exports = driver;
| |