From daf9e9d18b512de8fa2f1c0d44101efc45b7ebeb Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 20 Feb 2021 19:15:11 +0100 Subject: [PATCH] remove web --- packages/web/.eslintrc.js | 28 - packages/web/package.json | 69 - packages/web/public/favicon.ico | Bin 186605 -> 0 bytes packages/web/public/index.html | 44 - packages/web/public/logo192.png | Bin 30847 -> 0 bytes packages/web/public/logo512.png | Bin 139905 -> 0 bytes packages/web/public/manifest.json | 25 - packages/web/public/robots.txt | 2 - packages/web/public/splash.css | 16 - packages/web/public/splash.html | 12 - packages/web/public/unknown.svg | 15 - packages/web/src/App.css | 38 - packages/web/src/App.js | 57 - packages/web/src/App.test.js | 11 - packages/web/src/DragAndDropFileTarget.js | 62 - packages/web/src/Screen.js | 147 --- packages/web/src/TabContent.js | 98 -- packages/web/src/TabsPanel.js | 300 ----- packages/web/src/appobj/AppObjectCore.js | 97 -- packages/web/src/appobj/AppObjectList.js | 164 --- .../web/src/appobj/ArchiveFileAppObject.js | 75 -- .../web/src/appobj/ArchiveFolderAppObject.js | 34 - packages/web/src/appobj/ClosedTabAppObject.js | 73 -- .../web/src/appobj/ConnectionAppObject.js | 119 -- packages/web/src/appobj/DatabaseAppObject.js | 90 -- .../web/src/appobj/DatabaseObjectAppObject.js | 325 ----- .../web/src/appobj/FavoriteFileAppObject.js | 104 -- packages/web/src/appobj/MacroAppObject.js | 15 - packages/web/src/appobj/SavedFileAppObject.js | 314 ----- packages/web/src/appobj/SubColumnParamList.js | 36 - packages/web/src/celldata/CellDataView.js | 99 -- packages/web/src/celldata/JsonCellDataView.js | 35 - packages/web/src/celldata/TextCellView.js | 22 - packages/web/src/charts/ChartEditor.js | 154 --- packages/web/src/charts/ChartToolbar.js | 15 - packages/web/src/charts/DataChart.js | 165 --- packages/web/src/charts/chartDataLoader.ts | 105 -- packages/web/src/datagrid/ChangeSetGrider.ts | 164 --- .../web/src/datagrid/ColumnHeaderControl.js | 134 -- packages/web/src/datagrid/ColumnLabel.js | 35 - packages/web/src/datagrid/ColumnManager.js | 94 -- .../web/src/datagrid/DataFilterControl.js | 501 -------- packages/web/src/datagrid/DataGrid.js | 83 -- .../web/src/datagrid/DataGridContextMenu.js | 68 - packages/web/src/datagrid/DataGridCore.js | 1124 ----------------- packages/web/src/datagrid/DataGridRow.js | 333 ----- packages/web/src/datagrid/DataGridToolbar.js | 32 - packages/web/src/datagrid/Grider.ts | 61 - packages/web/src/datagrid/InplaceEditor.js | 98 -- packages/web/src/datagrid/JslDataGridCore.js | 97 -- .../web/src/datagrid/LoadingDataGridCore.js | 141 --- packages/web/src/datagrid/ManagerStyles.js | 7 - packages/web/src/datagrid/ReferenceHeader.js | 46 - packages/web/src/datagrid/ReferenceManager.js | 114 -- packages/web/src/datagrid/RowsArrayGrider.ts | 20 - packages/web/src/datagrid/ScrollBars.js | 222 ---- packages/web/src/datagrid/SeriesSizes-old.js | 340 ----- packages/web/src/datagrid/SeriesSizes.ts | 341 ----- packages/web/src/datagrid/SqlDataGridCore.js | 177 --- packages/web/src/datagrid/TableDataGrid.js | 232 ---- packages/web/src/datagrid/gridutil.ts | 144 --- packages/web/src/datagrid/selection.ts | 69 - packages/web/src/datagrid/types.ts | 46 - packages/web/src/designer/Designer.js | 352 ------ .../src/designer/DesignerComponentCreator.ts | 91 -- .../web/src/designer/DesignerQueryDumper.ts | 215 ---- .../web/src/designer/DesignerReference.js | 177 --- packages/web/src/designer/DesignerTable.js | 413 ------ packages/web/src/designer/DomTableRef.ts | 39 - .../web/src/designer/QueryDesignColumns.js | 165 --- .../web/src/designer/QueryDesignToolbar.js | 29 - packages/web/src/designer/QueryDesigner.js | 7 - .../web/src/designer/cleanupDesignColumns.js | 5 - packages/web/src/designer/designerTools.ts | 144 --- packages/web/src/designer/types.ts | 44 - packages/web/src/formview/ChangeSetFormer.ts | 93 -- packages/web/src/formview/FormView.js | 583 --------- .../web/src/formview/FormViewContextMenu.js | 31 - packages/web/src/formview/FormViewFilters.js | 116 -- packages/web/src/formview/FormViewToolbar.js | 42 - packages/web/src/formview/Former.ts | 53 - packages/web/src/formview/SqlFormView.js | 294 ----- .../web/src/formview/openReferenceForm.js | 30 - .../src/freetable/FreeTableColumnEditor.js | 183 --- packages/web/src/freetable/FreeTableGrid.js | 92 -- .../web/src/freetable/FreeTableGridCore.js | 78 -- packages/web/src/freetable/FreeTableGrider.ts | 85 -- packages/web/src/freetable/MacroDetail.js | 121 -- packages/web/src/freetable/MacroManager.js | 41 - packages/web/src/freetable/MacroParameters.js | 17 - .../web/src/freetable/MacroPreviewGrider.ts | 40 - packages/web/src/freetable/macros.js | 273 ---- packages/web/src/freetable/useNewFreeTable.js | 14 - packages/web/src/icons.js | 123 -- .../src/impexp/ImportExportConfigurator.js | 580 --------- packages/web/src/impexp/PreviewDataGrid.js | 60 - packages/web/src/impexp/ScriptWriter.js | 49 - packages/web/src/impexp/createImpExpScript.js | 240 ---- packages/web/src/index.css | 52 - packages/web/src/index.js | 43 - .../web/src/markdown/MarkdownExtendedView.js | 34 - packages/web/src/markdown/MarkdownLink.js | 13 - packages/web/src/markdown/MarkdownToolbar.js | 12 - packages/web/src/markdown/OpenChartLink.js | 35 - packages/web/src/markdown/OpenSqlLink.js | 26 - packages/web/src/modals/AboutModal.js | 93 -- .../web/src/modals/ChangeDownloadUrlModal.js | 42 - packages/web/src/modals/ConfirmModal.js | 28 - packages/web/src/modals/ConfirmSqlModal.js | 46 - packages/web/src/modals/ConnectionModal.js | 349 ----- .../web/src/modals/CreateDatabaseModal.js | 30 - packages/web/src/modals/DropDownMenu.js | 106 -- packages/web/src/modals/ErrorMessageModal.js | 37 - packages/web/src/modals/FavoriteModal.js | 162 --- .../src/modals/FilterMultipleValuesModal.js | 68 - packages/web/src/modals/ImportExportModal.js | 249 ---- packages/web/src/modals/InputTextModal.js | 29 - packages/web/src/modals/InsertJoinModal.js | 191 --- packages/web/src/modals/ModalBase.js | 87 -- packages/web/src/modals/ModalContent.js | 27 - packages/web/src/modals/ModalFooter.js | 14 - packages/web/src/modals/ModalHeader.js | 33 - packages/web/src/modals/SaveArchiveModal.js | 40 - packages/web/src/modals/SaveFileModal.js | 86 -- packages/web/src/modals/SaveTabModal.js | 117 -- packages/web/src/modals/SetFilterModal.js | 132 -- packages/web/src/modals/showMenu.js | 40 - packages/web/src/modals/showModal.js | 40 - packages/web/src/modals/useModalState.js | 8 - packages/web/src/plugins/PluginsList.js | 87 -- packages/web/src/plugins/PluginsProvider.js | 62 - .../web/src/plugins/manifestExtractors.js | 21 - packages/web/src/query/MessagesView.js | 107 -- packages/web/src/query/QueryToolbar.js | 28 - packages/web/src/query/RunnerOuputFiles.js | 92 -- packages/web/src/query/RunnerOutputPane.js | 27 - packages/web/src/query/ShellToolbar.js | 18 - packages/web/src/query/SocketMessagesView.js | 57 - packages/web/src/query/useNewQuery.js | 55 - packages/web/src/react-app-env.d.ts | 1 - packages/web/src/serviceWorker.js | 130 -- packages/web/src/setupTests.js | 5 - packages/web/src/sqleditor/GenericEditor.js | 72 -- packages/web/src/sqleditor/JslDataGrid.js | 24 - packages/web/src/sqleditor/ResultTabs.js | 39 - packages/web/src/sqleditor/SqlEditor.js | 172 --- .../web/src/sqleditor/SqlEditorContextMenu.js | 29 - .../web/src/sqleditor/analyseQuerySources.js | 38 - .../web/src/sqleditor/useCodeCompletion.js | 127 -- packages/web/src/tabs/ArchiveFileTab.js | 8 - packages/web/src/tabs/ChartTab.js | 74 -- packages/web/src/tabs/FavoriteEditorTab.js | 100 -- packages/web/src/tabs/FreeTableTab.js | 67 - packages/web/src/tabs/InfoPageTab.js | 12 - packages/web/src/tabs/MarkdownEditorTab.js | 78 -- packages/web/src/tabs/MarkdownPreviewTab.js | 23 - packages/web/src/tabs/MarkdownViewTab.js | 36 - packages/web/src/tabs/PluginTab.js | 138 -- packages/web/src/tabs/QueryDesignTab.js | 229 ---- packages/web/src/tabs/QueryTab.js | 253 ---- packages/web/src/tabs/ShellTab.js | 148 --- packages/web/src/tabs/TableDataTab.js | 31 - packages/web/src/tabs/TableStructureTab.js | 141 --- packages/web/src/tabs/ViewDataTab.js | 59 - packages/web/src/tabs/index.js | 33 - packages/web/src/theme/_theme.bak.js | 33 - packages/web/src/theme/colorUtil.js | 163 --- packages/web/src/theme/dark.js | 38 - packages/web/src/theme/dimensions.js | 18 - packages/web/src/theme/fillTheme.js | 95 -- packages/web/src/theme/light.js | 37 - packages/web/src/theme/useTheme.js | 10 - packages/web/src/themes/ThemeHelmet.js | 72 -- packages/web/src/utility/ConnectionsPinger.js | 35 - packages/web/src/utility/ErrorBoundary.js | 95 -- packages/web/src/utility/FormArgumentList.js | 42 - packages/web/src/utility/FormProvider.js | 65 - packages/web/src/utility/ObjectListControl.js | 45 - .../web/src/utility/SaveFileToolbarButton.js | 26 - packages/web/src/utility/SocketProvider.js | 21 - packages/web/src/utility/TableControl.js | 123 -- packages/web/src/utility/ToolbarPortal.js | 13 - packages/web/src/utility/UploadButton.js | 26 - packages/web/src/utility/UploadsProvider.js | 109 -- packages/web/src/utility/applySqlTemplate.js | 32 - packages/web/src/utility/axios.js | 14 - packages/web/src/utility/cache.js | 40 - packages/web/src/utility/clipboard.js | 56 - packages/web/src/utility/common.js | 24 - packages/web/src/utility/fileformats.js | 33 - packages/web/src/utility/flexGrid.js | 73 -- packages/web/src/utility/formStyle.js | 122 -- packages/web/src/utility/formatFileSize.js | 6 - packages/web/src/utility/forms.js | 370 ------ packages/web/src/utility/fullDisplayName.js | 4 - packages/web/src/utility/getAsArray.js | 7 - packages/web/src/utility/getElectron.js | 7 - packages/web/src/utility/globalState.js | 154 --- packages/web/src/utility/inputs.js | 22 - packages/web/src/utility/keycodes.js | 99 -- packages/web/src/utility/layout.js | 7 - .../utility/localStorageGarbageCollector.js | 35 - packages/web/src/utility/metadataLoaders.js | 309 ----- packages/web/src/utility/resolveApi.js | 21 - packages/web/src/utility/useDimensions.js | 107 -- packages/web/src/utility/useDocumentClick.js | 20 - packages/web/src/utility/useEditorData.js | 126 -- packages/web/src/utility/useExtensions.js | 35 - packages/web/src/utility/useFetch.js | 88 -- packages/web/src/utility/useGridConfig.js | 23 - packages/web/src/utility/useHasPermission.js | 10 - .../web/src/utility/useOpenElectronFile.js | 88 -- packages/web/src/utility/useOpenNewTab.js | 90 -- packages/web/src/utility/usePrevious.js | 17 - packages/web/src/utility/usePropsCompare.js | 12 - packages/web/src/utility/useStorage.js | 41 - packages/web/src/utility/useTimerLabel.js | 37 - packages/web/src/utility/useUndoReducer.js | 69 - packages/web/src/widgets/ArchiveWidget.js | 82 -- packages/web/src/widgets/DatabaseWidget.js | 196 --- packages/web/src/widgets/DropDownButton.js | 20 - packages/web/src/widgets/ErrorInfo.js | 39 - packages/web/src/widgets/FavoritesWidget.js | 57 - packages/web/src/widgets/FilesWidget.js | 46 - packages/web/src/widgets/FormStyledButton.js | 77 -- packages/web/src/widgets/InlineButton.js | 74 -- packages/web/src/widgets/LargeButton.js | 70 - packages/web/src/widgets/LoadingInfo.js | 53 - packages/web/src/widgets/PluginsWidget.js | 65 - packages/web/src/widgets/SearchInput.js | 28 - packages/web/src/widgets/Splitter.js | 169 --- packages/web/src/widgets/StatusBar.js | 97 -- packages/web/src/widgets/TabControl.js | 130 -- packages/web/src/widgets/Toolbar.js | 183 --- packages/web/src/widgets/ToolbarButton.js | 117 -- packages/web/src/widgets/WidgetColumnBar.js | 79 -- packages/web/src/widgets/WidgetContainer.js | 21 - packages/web/src/widgets/WidgetIconPanel.js | 93 -- packages/web/src/widgets/WidgetStyles.js | 87 -- packages/web/tsconfig.json | 28 - 240 files changed, 22572 deletions(-) delete mode 100644 packages/web/.eslintrc.js delete mode 100644 packages/web/package.json delete mode 100644 packages/web/public/favicon.ico delete mode 100644 packages/web/public/index.html delete mode 100644 packages/web/public/logo192.png delete mode 100644 packages/web/public/logo512.png delete mode 100644 packages/web/public/manifest.json delete mode 100644 packages/web/public/robots.txt delete mode 100644 packages/web/public/splash.css delete mode 100644 packages/web/public/splash.html delete mode 100644 packages/web/public/unknown.svg delete mode 100644 packages/web/src/App.css delete mode 100644 packages/web/src/App.js delete mode 100644 packages/web/src/App.test.js delete mode 100644 packages/web/src/DragAndDropFileTarget.js delete mode 100644 packages/web/src/Screen.js delete mode 100644 packages/web/src/TabContent.js delete mode 100644 packages/web/src/TabsPanel.js delete mode 100644 packages/web/src/appobj/AppObjectCore.js delete mode 100644 packages/web/src/appobj/AppObjectList.js delete mode 100644 packages/web/src/appobj/ArchiveFileAppObject.js delete mode 100644 packages/web/src/appobj/ArchiveFolderAppObject.js delete mode 100644 packages/web/src/appobj/ClosedTabAppObject.js delete mode 100644 packages/web/src/appobj/ConnectionAppObject.js delete mode 100644 packages/web/src/appobj/DatabaseAppObject.js delete mode 100644 packages/web/src/appobj/DatabaseObjectAppObject.js delete mode 100644 packages/web/src/appobj/FavoriteFileAppObject.js delete mode 100644 packages/web/src/appobj/MacroAppObject.js delete mode 100644 packages/web/src/appobj/SavedFileAppObject.js delete mode 100644 packages/web/src/appobj/SubColumnParamList.js delete mode 100644 packages/web/src/celldata/CellDataView.js delete mode 100644 packages/web/src/celldata/JsonCellDataView.js delete mode 100644 packages/web/src/celldata/TextCellView.js delete mode 100644 packages/web/src/charts/ChartEditor.js delete mode 100644 packages/web/src/charts/ChartToolbar.js delete mode 100644 packages/web/src/charts/DataChart.js delete mode 100644 packages/web/src/charts/chartDataLoader.ts delete mode 100644 packages/web/src/datagrid/ChangeSetGrider.ts delete mode 100644 packages/web/src/datagrid/ColumnHeaderControl.js delete mode 100644 packages/web/src/datagrid/ColumnLabel.js delete mode 100644 packages/web/src/datagrid/ColumnManager.js delete mode 100644 packages/web/src/datagrid/DataFilterControl.js delete mode 100644 packages/web/src/datagrid/DataGrid.js delete mode 100644 packages/web/src/datagrid/DataGridContextMenu.js delete mode 100644 packages/web/src/datagrid/DataGridCore.js delete mode 100644 packages/web/src/datagrid/DataGridRow.js delete mode 100644 packages/web/src/datagrid/DataGridToolbar.js delete mode 100644 packages/web/src/datagrid/Grider.ts delete mode 100644 packages/web/src/datagrid/InplaceEditor.js delete mode 100644 packages/web/src/datagrid/JslDataGridCore.js delete mode 100644 packages/web/src/datagrid/LoadingDataGridCore.js delete mode 100644 packages/web/src/datagrid/ManagerStyles.js delete mode 100644 packages/web/src/datagrid/ReferenceHeader.js delete mode 100644 packages/web/src/datagrid/ReferenceManager.js delete mode 100644 packages/web/src/datagrid/RowsArrayGrider.ts delete mode 100644 packages/web/src/datagrid/ScrollBars.js delete mode 100644 packages/web/src/datagrid/SeriesSizes-old.js delete mode 100644 packages/web/src/datagrid/SeriesSizes.ts delete mode 100644 packages/web/src/datagrid/SqlDataGridCore.js delete mode 100644 packages/web/src/datagrid/TableDataGrid.js delete mode 100644 packages/web/src/datagrid/gridutil.ts delete mode 100644 packages/web/src/datagrid/selection.ts delete mode 100644 packages/web/src/datagrid/types.ts delete mode 100644 packages/web/src/designer/Designer.js delete mode 100644 packages/web/src/designer/DesignerComponentCreator.ts delete mode 100644 packages/web/src/designer/DesignerQueryDumper.ts delete mode 100644 packages/web/src/designer/DesignerReference.js delete mode 100644 packages/web/src/designer/DesignerTable.js delete mode 100644 packages/web/src/designer/DomTableRef.ts delete mode 100644 packages/web/src/designer/QueryDesignColumns.js delete mode 100644 packages/web/src/designer/QueryDesignToolbar.js delete mode 100644 packages/web/src/designer/QueryDesigner.js delete mode 100644 packages/web/src/designer/cleanupDesignColumns.js delete mode 100644 packages/web/src/designer/designerTools.ts delete mode 100644 packages/web/src/designer/types.ts delete mode 100644 packages/web/src/formview/ChangeSetFormer.ts delete mode 100644 packages/web/src/formview/FormView.js delete mode 100644 packages/web/src/formview/FormViewContextMenu.js delete mode 100644 packages/web/src/formview/FormViewFilters.js delete mode 100644 packages/web/src/formview/FormViewToolbar.js delete mode 100644 packages/web/src/formview/Former.ts delete mode 100644 packages/web/src/formview/SqlFormView.js delete mode 100644 packages/web/src/formview/openReferenceForm.js delete mode 100644 packages/web/src/freetable/FreeTableColumnEditor.js delete mode 100644 packages/web/src/freetable/FreeTableGrid.js delete mode 100644 packages/web/src/freetable/FreeTableGridCore.js delete mode 100644 packages/web/src/freetable/FreeTableGrider.ts delete mode 100644 packages/web/src/freetable/MacroDetail.js delete mode 100644 packages/web/src/freetable/MacroManager.js delete mode 100644 packages/web/src/freetable/MacroParameters.js delete mode 100644 packages/web/src/freetable/MacroPreviewGrider.ts delete mode 100644 packages/web/src/freetable/macros.js delete mode 100644 packages/web/src/freetable/useNewFreeTable.js delete mode 100644 packages/web/src/icons.js delete mode 100644 packages/web/src/impexp/ImportExportConfigurator.js delete mode 100644 packages/web/src/impexp/PreviewDataGrid.js delete mode 100644 packages/web/src/impexp/ScriptWriter.js delete mode 100644 packages/web/src/impexp/createImpExpScript.js delete mode 100644 packages/web/src/index.css delete mode 100644 packages/web/src/index.js delete mode 100644 packages/web/src/markdown/MarkdownExtendedView.js delete mode 100644 packages/web/src/markdown/MarkdownLink.js delete mode 100644 packages/web/src/markdown/MarkdownToolbar.js delete mode 100644 packages/web/src/markdown/OpenChartLink.js delete mode 100644 packages/web/src/markdown/OpenSqlLink.js delete mode 100644 packages/web/src/modals/AboutModal.js delete mode 100644 packages/web/src/modals/ChangeDownloadUrlModal.js delete mode 100644 packages/web/src/modals/ConfirmModal.js delete mode 100644 packages/web/src/modals/ConfirmSqlModal.js delete mode 100644 packages/web/src/modals/ConnectionModal.js delete mode 100644 packages/web/src/modals/CreateDatabaseModal.js delete mode 100644 packages/web/src/modals/DropDownMenu.js delete mode 100644 packages/web/src/modals/ErrorMessageModal.js delete mode 100644 packages/web/src/modals/FavoriteModal.js delete mode 100644 packages/web/src/modals/FilterMultipleValuesModal.js delete mode 100644 packages/web/src/modals/ImportExportModal.js delete mode 100644 packages/web/src/modals/InputTextModal.js delete mode 100644 packages/web/src/modals/InsertJoinModal.js delete mode 100644 packages/web/src/modals/ModalBase.js delete mode 100644 packages/web/src/modals/ModalContent.js delete mode 100644 packages/web/src/modals/ModalFooter.js delete mode 100644 packages/web/src/modals/ModalHeader.js delete mode 100644 packages/web/src/modals/SaveArchiveModal.js delete mode 100644 packages/web/src/modals/SaveFileModal.js delete mode 100644 packages/web/src/modals/SaveTabModal.js delete mode 100644 packages/web/src/modals/SetFilterModal.js delete mode 100644 packages/web/src/modals/showMenu.js delete mode 100644 packages/web/src/modals/showModal.js delete mode 100644 packages/web/src/modals/useModalState.js delete mode 100644 packages/web/src/plugins/PluginsList.js delete mode 100644 packages/web/src/plugins/PluginsProvider.js delete mode 100644 packages/web/src/plugins/manifestExtractors.js delete mode 100644 packages/web/src/query/MessagesView.js delete mode 100644 packages/web/src/query/QueryToolbar.js delete mode 100644 packages/web/src/query/RunnerOuputFiles.js delete mode 100644 packages/web/src/query/RunnerOutputPane.js delete mode 100644 packages/web/src/query/ShellToolbar.js delete mode 100644 packages/web/src/query/SocketMessagesView.js delete mode 100644 packages/web/src/query/useNewQuery.js delete mode 100644 packages/web/src/react-app-env.d.ts delete mode 100644 packages/web/src/serviceWorker.js delete mode 100644 packages/web/src/setupTests.js delete mode 100644 packages/web/src/sqleditor/GenericEditor.js delete mode 100644 packages/web/src/sqleditor/JslDataGrid.js delete mode 100644 packages/web/src/sqleditor/ResultTabs.js delete mode 100644 packages/web/src/sqleditor/SqlEditor.js delete mode 100644 packages/web/src/sqleditor/SqlEditorContextMenu.js delete mode 100644 packages/web/src/sqleditor/analyseQuerySources.js delete mode 100644 packages/web/src/sqleditor/useCodeCompletion.js delete mode 100644 packages/web/src/tabs/ArchiveFileTab.js delete mode 100644 packages/web/src/tabs/ChartTab.js delete mode 100644 packages/web/src/tabs/FavoriteEditorTab.js delete mode 100644 packages/web/src/tabs/FreeTableTab.js delete mode 100644 packages/web/src/tabs/InfoPageTab.js delete mode 100644 packages/web/src/tabs/MarkdownEditorTab.js delete mode 100644 packages/web/src/tabs/MarkdownPreviewTab.js delete mode 100644 packages/web/src/tabs/MarkdownViewTab.js delete mode 100644 packages/web/src/tabs/PluginTab.js delete mode 100644 packages/web/src/tabs/QueryDesignTab.js delete mode 100644 packages/web/src/tabs/QueryTab.js delete mode 100644 packages/web/src/tabs/ShellTab.js delete mode 100644 packages/web/src/tabs/TableDataTab.js delete mode 100644 packages/web/src/tabs/TableStructureTab.js delete mode 100644 packages/web/src/tabs/ViewDataTab.js delete mode 100644 packages/web/src/tabs/index.js delete mode 100644 packages/web/src/theme/_theme.bak.js delete mode 100644 packages/web/src/theme/colorUtil.js delete mode 100644 packages/web/src/theme/dark.js delete mode 100644 packages/web/src/theme/dimensions.js delete mode 100644 packages/web/src/theme/fillTheme.js delete mode 100644 packages/web/src/theme/light.js delete mode 100644 packages/web/src/theme/useTheme.js delete mode 100644 packages/web/src/themes/ThemeHelmet.js delete mode 100644 packages/web/src/utility/ConnectionsPinger.js delete mode 100644 packages/web/src/utility/ErrorBoundary.js delete mode 100644 packages/web/src/utility/FormArgumentList.js delete mode 100644 packages/web/src/utility/FormProvider.js delete mode 100644 packages/web/src/utility/ObjectListControl.js delete mode 100644 packages/web/src/utility/SaveFileToolbarButton.js delete mode 100644 packages/web/src/utility/SocketProvider.js delete mode 100644 packages/web/src/utility/TableControl.js delete mode 100644 packages/web/src/utility/ToolbarPortal.js delete mode 100644 packages/web/src/utility/UploadButton.js delete mode 100644 packages/web/src/utility/UploadsProvider.js delete mode 100644 packages/web/src/utility/applySqlTemplate.js delete mode 100644 packages/web/src/utility/axios.js delete mode 100644 packages/web/src/utility/cache.js delete mode 100644 packages/web/src/utility/clipboard.js delete mode 100644 packages/web/src/utility/common.js delete mode 100644 packages/web/src/utility/fileformats.js delete mode 100644 packages/web/src/utility/flexGrid.js delete mode 100644 packages/web/src/utility/formStyle.js delete mode 100644 packages/web/src/utility/formatFileSize.js delete mode 100644 packages/web/src/utility/forms.js delete mode 100644 packages/web/src/utility/fullDisplayName.js delete mode 100644 packages/web/src/utility/getAsArray.js delete mode 100644 packages/web/src/utility/getElectron.js delete mode 100644 packages/web/src/utility/globalState.js delete mode 100644 packages/web/src/utility/inputs.js delete mode 100644 packages/web/src/utility/keycodes.js delete mode 100644 packages/web/src/utility/layout.js delete mode 100644 packages/web/src/utility/localStorageGarbageCollector.js delete mode 100644 packages/web/src/utility/metadataLoaders.js delete mode 100644 packages/web/src/utility/resolveApi.js delete mode 100644 packages/web/src/utility/useDimensions.js delete mode 100644 packages/web/src/utility/useDocumentClick.js delete mode 100644 packages/web/src/utility/useEditorData.js delete mode 100644 packages/web/src/utility/useExtensions.js delete mode 100644 packages/web/src/utility/useFetch.js delete mode 100644 packages/web/src/utility/useGridConfig.js delete mode 100644 packages/web/src/utility/useHasPermission.js delete mode 100644 packages/web/src/utility/useOpenElectronFile.js delete mode 100644 packages/web/src/utility/useOpenNewTab.js delete mode 100644 packages/web/src/utility/usePrevious.js delete mode 100644 packages/web/src/utility/usePropsCompare.js delete mode 100644 packages/web/src/utility/useStorage.js delete mode 100644 packages/web/src/utility/useTimerLabel.js delete mode 100644 packages/web/src/utility/useUndoReducer.js delete mode 100644 packages/web/src/widgets/ArchiveWidget.js delete mode 100644 packages/web/src/widgets/DatabaseWidget.js delete mode 100644 packages/web/src/widgets/DropDownButton.js delete mode 100644 packages/web/src/widgets/ErrorInfo.js delete mode 100644 packages/web/src/widgets/FavoritesWidget.js delete mode 100644 packages/web/src/widgets/FilesWidget.js delete mode 100644 packages/web/src/widgets/FormStyledButton.js delete mode 100644 packages/web/src/widgets/InlineButton.js delete mode 100644 packages/web/src/widgets/LargeButton.js delete mode 100644 packages/web/src/widgets/LoadingInfo.js delete mode 100644 packages/web/src/widgets/PluginsWidget.js delete mode 100644 packages/web/src/widgets/SearchInput.js delete mode 100644 packages/web/src/widgets/Splitter.js delete mode 100644 packages/web/src/widgets/StatusBar.js delete mode 100644 packages/web/src/widgets/TabControl.js delete mode 100644 packages/web/src/widgets/Toolbar.js delete mode 100644 packages/web/src/widgets/ToolbarButton.js delete mode 100644 packages/web/src/widgets/WidgetColumnBar.js delete mode 100644 packages/web/src/widgets/WidgetContainer.js delete mode 100644 packages/web/src/widgets/WidgetIconPanel.js delete mode 100644 packages/web/src/widgets/WidgetStyles.js delete mode 100644 packages/web/tsconfig.json diff --git a/packages/web/.eslintrc.js b/packages/web/.eslintrc.js deleted file mode 100644 index a53d0ca70..000000000 --- a/packages/web/.eslintrc.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - "env": { - "browser": true, - "es6": true - }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended" - ], - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 2018, - "sourceType": "module" - }, - "plugins": [ - "react" - ], - "rules": { - "react/prop-types": "off", - "no-unused-vars": "warn" - } -}; \ No newline at end of file diff --git a/packages/web/package.json b/packages/web/package.json deleted file mode 100644 index 77b431d6c..000000000 --- a/packages/web/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "dbgate-web", - "version": "3.9.5", - "files": [ - "build" - ], - "scripts": { - "start": "cross-env BROWSER=none PORT=5000 react-scripts start", - "build:docker": "cross-env CI=false REACT_APP_API_URL=ORIGIN react-scripts build", - "build:app": "cross-env PUBLIC_URL=. CI=false react-scripts build", - "build": "cross-env CI=false REACT_APP_API_URL=ORIGIN react-scripts build", - "prepublishOnly": "yarn build", - "test": "react-scripts test", - "eject": "react-scripts eject", - "ts": "tsc" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "devDependencies": { - "@types/react": "^16.9.17", - "@types/styled-components": "^4.4.2", - "dbgate-types": "^3.9.5", - "typescript": "^3.7.4", - "@ant-design/colors": "^5.0.0", - "@mdi/font": "^5.8.55", - "@testing-library/jest-dom": "^4.2.4", - "@testing-library/react": "^9.3.2", - "@testing-library/user-event": "^7.1.2", - "ace-builds": "^1.4.8", - "axios": "^0.19.0", - "chart.js": "^2.9.4", - "compare-versions": "^3.6.0", - "cross-env": "^6.0.3", - "dbgate-datalib": "^3.9.5", - "dbgate-sqltree": "^3.9.5", - "dbgate-tools": "^3.9.5", - "eslint": "^6.8.0", - "eslint-plugin-react": "^7.17.0", - "json-stable-stringify": "^1.0.1", - "localforage": "^1.9.0", - "markdown-to-jsx": "^7.1.0", - "randomcolor": "^0.6.2", - "react": "^16.12.0", - "react-ace": "^8.0.0", - "react-chartjs-2": "^2.11.1", - "react-dom": "^16.12.0", - "react-dropzone": "^11.2.3", - "react-helmet": "^6.1.0", - "react-json-view": "^1.19.1", - "react-modal": "^3.11.1", - "react-scripts": "3.3.0", - "react-select": "^3.1.0", - "resize-observer-polyfill": "^1.5.1", - "socket.io-client": "^2.3.0", - "sql-formatter": "^2.3.3", - "styled-components": "^4.4.1", - "uuid": "^3.4.0" - } -} \ No newline at end of file diff --git a/packages/web/public/favicon.ico b/packages/web/public/favicon.ico deleted file mode 100644 index dd0ba64871b8315946bd10af20a5846686e75cb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 186605 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 z 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 diff --git a/packages/web/public/index.html b/packages/web/public/index.html deleted file mode 100644 index 94e81e72b..000000000 --- a/packages/web/public/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - DbGate - - - - -
Loading DbGate...
- - - - \ No newline at end of file diff --git a/packages/web/public/logo192.png b/packages/web/public/logo192.png deleted file mode 100644 index 56d4efb815992cdbb08e168fd4326010f54f040d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30847 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 - - - - - - - -
Starting DbGate...
- - - \ No newline at end of file diff --git a/packages/web/public/unknown.svg b/packages/web/public/unknown.svg deleted file mode 100644 index ff30dd8cf..000000000 --- a/packages/web/public/unknown.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - ? - \ No newline at end of file diff --git a/packages/web/src/App.css b/packages/web/src/App.css deleted file mode 100644 index 74b5e0534..000000000 --- a/packages/web/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/packages/web/src/App.js b/packages/web/src/App.js deleted file mode 100644 index b325a80ae..000000000 --- a/packages/web/src/App.js +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import './index.css'; -import Screen from './Screen'; -import { - CurrentWidgetProvider, - CurrentDatabaseProvider, - OpenedTabsProvider, - OpenedConnectionsProvider, - LeftPanelWidthProvider, - CurrentArchiveProvider, - CurrentThemeProvider, -} from './utility/globalState'; -import { SocketProvider } from './utility/SocketProvider'; -import ConnectionsPinger from './utility/ConnectionsPinger'; -import { ModalLayerProvider } from './modals/showModal'; -import UploadsProvider from './utility/UploadsProvider'; -import ThemeHelmet from './themes/ThemeHelmet'; -import PluginsProvider from './plugins/PluginsProvider'; -import { ExtensionsProvider } from './utility/useExtensions'; -import { MenuLayerProvider } from './modals/showMenu'; - -function App() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} - -export default App; diff --git a/packages/web/src/App.test.js b/packages/web/src/App.test.js deleted file mode 100644 index f50f6952c..000000000 --- a/packages/web/src/App.test.js +++ /dev/null @@ -1,11 +0,0 @@ -// @ts-nocheck - -import React from 'react'; -import { render } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - const { getByText } = render(); - const linkElement = getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/packages/web/src/DragAndDropFileTarget.js b/packages/web/src/DragAndDropFileTarget.js deleted file mode 100644 index 5741ca470..000000000 --- a/packages/web/src/DragAndDropFileTarget.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { FontIcon } from './icons'; -import useTheme from './theme/useTheme'; -import getElectron from './utility/getElectron'; -import useExtensions from './utility/useExtensions'; - -const TargetStyled = styled.div` - position: fixed; - display: flex; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: ${props => props.theme.main_background_blue[3]}; - align-items: center; - justify-content: space-around; - z-index: 1000; -`; - -const InfoBox = styled.div``; - -const IconWrapper = styled.div` - display: flex; - justify-content: space-around; - font-size: 50px; - margin-bottom: 20px; -`; - -const InfoWrapper = styled.div` - display: flex; - justify-content: space-around; - margin-top: 10px; -`; - -const TitleWrapper = styled.div` - font-size: 30px; - display: flex; - justify-content: space-around; -`; - -export default function DragAndDropFileTarget({ isDragActive, inputProps }) { - const theme = useTheme(); - const { fileFormats } = useExtensions(); - const electron = getElectron(); - const fileTypeNames = fileFormats.filter(x => x.readerFunc).map(x => x.name); - if (electron) fileTypeNames.push('SQL'); - return ( - !!isDragActive && ( - - - - - - Drop the files to upload to DbGate - Supported file types: {fileTypeNames.join(', ')} - - - - ) - ); -} diff --git a/packages/web/src/Screen.js b/packages/web/src/Screen.js deleted file mode 100644 index 8299dce9f..000000000 --- a/packages/web/src/Screen.js +++ /dev/null @@ -1,147 +0,0 @@ -// @ts-nocheck - -import React from 'react'; -import dimensions from './theme/dimensions'; -import styled from 'styled-components'; -import TabsPanel from './TabsPanel'; -import TabContent from './TabContent'; -import WidgetIconPanel from './widgets/WidgetIconPanel'; -import { useCurrentWidget, useLeftPanelWidth, useSetLeftPanelWidth } from './utility/globalState'; -import WidgetContainer from './widgets/WidgetContainer'; -import ToolBar from './widgets/Toolbar'; -import StatusBar from './widgets/StatusBar'; -import { useSplitterDrag, HorizontalSplitHandle } from './widgets/Splitter'; -import { ModalLayer } from './modals/showModal'; -import DragAndDropFileTarget from './DragAndDropFileTarget'; -import { useUploadsZone } from './utility/UploadsProvider'; -import useTheme from './theme/useTheme'; -import { MenuLayer } from './modals/showMenu'; -import ErrorBoundary, { ErrorBoundaryTest } from './utility/ErrorBoundary'; - -const BodyDiv = styled.div` - position: fixed; - top: ${dimensions.tabsPanel.height + dimensions.toolBar.height}px; - left: ${props => props.contentLeft}px; - bottom: ${dimensions.statusBar.height}px; - right: 0; - background-color: ${props => props.theme.content_background}; -`; - -const ToolBarDiv = styled.div` - position: fixed; - top: 0; - left: 0; - right: 0; - background-color: ${props => props.theme.toolbar_background}; - height: ${dimensions.toolBar.height}px; -`; - -const IconBar = styled.div` - position: fixed; - top: ${dimensions.toolBar.height}px; - left: 0; - bottom: ${dimensions.statusBar.height}px; - width: ${dimensions.widgetMenu.iconSize}px; - background-color: ${props => props.theme.widget_background}; -`; - -const LeftPanel = styled.div` - position: fixed; - top: ${dimensions.toolBar.height}px; - left: ${dimensions.widgetMenu.iconSize}px; - bottom: ${dimensions.statusBar.height}px; - background-color: ${props => props.theme.left_background}; - display: flex; -`; - -const TabsPanelContainer = styled.div` - display: flex; - position: fixed; - top: ${dimensions.toolBar.height}px; - left: ${props => props.contentLeft}px; - height: ${dimensions.tabsPanel.height}px; - right: 0; - background-color: ${props => props.theme.tabs_background2}; - border-top: 1px solid ${props => props.theme.border}; - - overflow-x: auto; - - ::-webkit-scrollbar { - height: 7px; - } -} -`; - -const StausBarContainer = styled.div` - position: fixed; - height: ${dimensions.statusBar.height}px; - left: 0; - right: 0; - bottom: 0; - background-color: ${props => props.theme.statusbar_background}; -`; - -const ScreenHorizontalSplitHandle = styled(HorizontalSplitHandle)` - position: absolute; - top: ${dimensions.toolBar.height}px; - bottom: ${dimensions.statusBar.height}px; -`; - -// const StyledRoot = styled.div` -// // color: ${(props) => props.theme.fontColor}; -// `; - -export default function Screen() { - const theme = useTheme(); - const currentWidget = useCurrentWidget(); - const leftPanelWidth = useLeftPanelWidth(); - const setLeftPanelWidth = useSetLeftPanelWidth(); - const contentLeft = currentWidget - ? dimensions.widgetMenu.iconSize + leftPanelWidth + dimensions.splitter.thickness - : dimensions.widgetMenu.iconSize; - const toolbarPortalRef = React.useRef(); - const statusbarPortalRef = React.useRef(); - const onSplitDown = useSplitterDrag('clientX', diff => setLeftPanelWidth(v => v + diff)); - - const { getRootProps, getInputProps, isDragActive } = useUploadsZone(); - - return ( -
- - - - - - - - {!!currentWidget && ( - - - - - - )} - {!!currentWidget && ( - - )} - - - - - - - - - - - - - - -
- ); -} diff --git a/packages/web/src/TabContent.js b/packages/web/src/TabContent.js deleted file mode 100644 index f90da055f..000000000 --- a/packages/web/src/TabContent.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import styled from 'styled-components'; -import tabs from './tabs'; -import { useOpenedTabs } from './utility/globalState'; -import ErrorBoundary from './utility/ErrorBoundary'; - -const TabContainerStyled = styled.div` - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - display: flex; - visibility: ${props => - // @ts-ignore - props.tabVisible ? 'visible' : 'hidden'}; -`; - -function TabContainer({ TabComponent, ...props }) { - const { tabVisible, tabid, toolbarPortalRef, statusbarPortalRef } = props; - return ( - // @ts-ignore - - - - - - ); -} - -const TabContainerMemo = React.memo(TabContainer); - -function createTabComponent(selectedTab) { - const TabComponent = tabs[selectedTab.tabComponent]; - if (TabComponent) { - return { - TabComponent, - props: selectedTab.props, - }; - } - return null; -} - -export default function TabContent({ toolbarPortalRef, statusbarPortalRef }) { - const files = useOpenedTabs(); - - const [mountedTabs, setMountedTabs] = React.useState({}); - - const selectedTab = files.find(x => x.selected && x.closedTime == null); - - React.useEffect(() => { - // cleanup closed tabs - - if ( - _.difference( - _.keys(mountedTabs), - _.map( - files.filter(x => x.closedTime == null), - 'tabid' - ) - ).length > 0 - ) { - setMountedTabs(_.pickBy(mountedTabs, (v, k) => files.find(x => x.tabid == k && x.closedTime == null))); - } - - if (selectedTab) { - const { tabid } = selectedTab; - if (tabid && !mountedTabs[tabid]) - setMountedTabs({ - ...mountedTabs, - [tabid]: createTabComponent(selectedTab), - }); - } - }, [mountedTabs, files]); - - return _.keys(mountedTabs).map(tabid => { - const { TabComponent, props } = mountedTabs[tabid]; - const tabVisible = tabid == (selectedTab && selectedTab.tabid); - return ( - - ); - }); -} diff --git a/packages/web/src/TabsPanel.js b/packages/web/src/TabsPanel.js deleted file mode 100644 index 37c3e121d..000000000 --- a/packages/web/src/TabsPanel.js +++ /dev/null @@ -1,300 +0,0 @@ -import _ from 'lodash'; -import React from 'react'; -import styled from 'styled-components'; -import { DropDownMenuItem, DropDownMenuDivider } from './modals/DropDownMenu'; - -import { useOpenedTabs, useSetOpenedTabs, useCurrentDatabase, useSetCurrentDatabase } from './utility/globalState'; -import { getConnectionInfo } from './utility/metadataLoaders'; -import { FontIcon } from './icons'; -import useTheme from './theme/useTheme'; -import usePropsCompare from './utility/usePropsCompare'; -import { useShowMenu } from './modals/showMenu'; -import { setSelectedTabFunc } from './utility/common'; -import getElectron from './utility/getElectron'; - -// const files = [ -// { name: 'app.js' }, -// { name: 'BranchCategory', type: 'table', selected: true }, -// { name: 'ApplicationList' }, -// ]; - -const DbGroupHandler = styled.div` - display: flex; - flex: 1; - align-content: stretch; -`; - -const DbWrapperHandler = styled.div` - display: flex; - flex-direction: column; - align-items: stretch; -`; - -const DbNameWrapper = styled.div` - text-align: center; - font-size: 8pt; - border-bottom: 1px solid ${props => props.theme.border}; - border-right: 1px solid ${props => props.theme.border}; - cursor: pointer; - user-select: none; - padding: 1px; - position: relative; - white-space: nowrap; - - overflow: hidden; - text-overflow: ellipsis; - // height: 15px; - - &:hover { - background-color: ${props => props.theme.tabs_background3}; - } - background-color: ${props => - // @ts-ignore - props.selected ? props.theme.tabs_background1 : 'inherit'}; -`; - -// const DbNameWrapperInner = styled.div` -// position: absolute; -// white-space: nowrap; -// `; - -const FileTabItem = styled.div` - border-right: 1px solid ${props => props.theme.border}; - padding-left: 15px; - padding-right: 15px; - flex-shrink: 1; - flex-grow: 1; - min-width: 10px; - display: flex; - align-items: center; - cursor: pointer; - user-select: none; - &:hover { - color: ${props => props.theme.tabs_font_hover}; - } - background-color: ${props => - // @ts-ignore - props.selected ? props.theme.tabs_background1 : 'inherit'}; -`; - -const FileNameWrapper = styled.span` - margin-left: 5px; -`; - -const CloseButton = styled(FontIcon)` - margin-left: 5px; - color: gray; - &:hover { - color: ${props => props.theme.tabs_font2}; - } -`; - -function TabContextMenu({ close, closeAll, closeOthers, closeWithSameDb, closeWithOtherDb, props }) { - const { database } = props || {}; - const { conid } = props || {}; - return ( - <> - Close - Close all - Close others - {conid && database && ( - Close with same DB - {database} - )} - {conid && database && ( - Close with other DB than {database} - )} - - ); -} - -function getTabDbName(tab) { - if (tab.props && tab.props.conid && tab.props.database) return tab.props.database; - if (tab.props && tab.props.archiveFolder) return tab.props.archiveFolder; - return '(no DB)'; -} - -function getTabDbKey(tab) { - if (tab.props && tab.props.conid && tab.props.database) return `database://${tab.props.database}-${tab.props.conid}`; - if (tab.props && tab.props.archiveFolder) return `archive://${tab.props.archiveFolder}`; - return '_no'; -} - -function getDbIcon(key) { - if (key.startsWith('database://')) return 'icon database'; - if (key.startsWith('archive://')) return 'icon archive'; - return 'icon file'; -} - -function buildTooltip(tab) { - let res = tab.tooltip; - if (tab.props && tab.props.savedFilePath) { - if (res) res += '\n'; - res += tab.props.savedFilePath; - } - return res; -} - -export default function TabsPanel() { - // const formatDbKey = (conid, database) => `${database}-${conid}`; - const theme = useTheme(); - const showMenu = useShowMenu(); - - const tabs = useOpenedTabs(); - const setOpenedTabs = useSetOpenedTabs(); - const currentDb = useCurrentDatabase(); - const setCurrentDb = useSetCurrentDatabase(); - - const { name, connection } = currentDb || {}; - const currentDbKey = name && connection ? `database://${name}-${connection._id}` : '_no'; - - const handleTabClick = (e, tabid) => { - if (e.target.closest('.tabCloseButton')) { - return; - } - setOpenedTabs(files => setSelectedTabFunc(files, tabid)); - }; - const closeTabFunc = closeCondition => tabid => { - setOpenedTabs(files => { - const active = files.find(x => x.tabid == tabid); - if (!active) return files; - - const newFiles = files.map(x => ({ - ...x, - closedTime: x.closedTime || (closeCondition(x, active) ? new Date().getTime() : undefined), - })); - - if (newFiles.find(x => x.selected && x.closedTime == null)) { - return newFiles; - } - - const selectedIndex = _.findLastIndex(newFiles, x => x.closedTime == null); - - return newFiles.map((x, index) => ({ - ...x, - selected: index == selectedIndex, - })); - }); - }; - - const closeTab = closeTabFunc((x, active) => x.tabid == active.tabid); - const closeAll = () => { - const closedTime = new Date().getTime(); - setOpenedTabs(tabs => - tabs.map(tab => ({ - ...tab, - closedTime: tab.closedTime || closedTime, - selected: false, - })) - ); - }; - const closeWithSameDb = closeTabFunc( - (x, active) => - _.get(x, 'props.conid') == _.get(active, 'props.conid') && - _.get(x, 'props.database') == _.get(active, 'props.database') - ); - const closeWithOtherDb = closeTabFunc( - (x, active) => - _.get(x, 'props.conid') != _.get(active, 'props.conid') || - _.get(x, 'props.database') != _.get(active, 'props.database') - ); - const closeOthers = closeTabFunc((x, active) => x.tabid != active.tabid); - const handleMouseUp = (e, tabid) => { - if (e.button == 1) { - e.preventDefault(); - closeTab(tabid); - } - }; - const handleContextMenu = (event, tabid, props) => { - event.preventDefault(); - showMenu( - event.pageX, - event.pageY, - closeTab(tabid)} - closeAll={closeAll} - closeOthers={() => closeOthers(tabid)} - closeWithSameDb={() => closeWithSameDb(tabid)} - closeWithOtherDb={() => closeWithOtherDb(tabid)} - props={props} - /> - ); - }; - - React.useEffect(() => { - const electron = getElectron(); - if (electron) { - const { ipcRenderer } = electron; - const activeTab = tabs.find(x => x.selected); - window['dbgate_activeTabId'] = activeTab ? activeTab.tabid : null; - ipcRenderer.send('update-menu'); - } - }, [tabs]); - - // console.log( - // 't', - // tabs.map(x => x.tooltip) - // ); - const tabsWithDb = tabs - .filter(x => !x.closedTime) - .map(tab => ({ - ...tab, - tabDbName: getTabDbName(tab), - tabDbKey: getTabDbKey(tab), - })); - const tabsByDb = _.groupBy(tabsWithDb, 'tabDbKey'); - const dbKeys = _.keys(tabsByDb).sort(); - - const handleSetDb = async props => { - const { conid, database } = props || {}; - if (conid) { - const connection = await getConnectionInfo({ conid, database }); - if (connection) { - setCurrentDb({ connection, name: database }); - return; - } - } - setCurrentDb(null); - }; - - return ( - <> - {dbKeys.map(dbKey => ( - - handleSetDb(tabsByDb[dbKey][0].props)} - theme={theme} - > - {tabsByDb[dbKey][0].tabDbName} - - - {_.sortBy(tabsByDb[dbKey], ['title', 'tabid']).map(tab => ( - handleTabClick(e, tab.tabid)} - onMouseUp={e => handleMouseUp(e, tab.tabid)} - onContextMenu={e => handleContextMenu(e, tab.tabid, tab.props)} - > - {} - {tab.title} - { - e.preventDefault(); - closeTab(tab.tabid); - }} - /> - - ))} - - - ))} - - ); -} diff --git a/packages/web/src/appobj/AppObjectCore.js b/packages/web/src/appobj/AppObjectCore.js deleted file mode 100644 index 58efa6b2d..000000000 --- a/packages/web/src/appobj/AppObjectCore.js +++ /dev/null @@ -1,97 +0,0 @@ -// @ts-nocheck - -import _ from 'lodash'; -import React from 'react'; -import styled from 'styled-components'; -import { FontIcon } from '../icons'; -import { useShowMenu } from '../modals/showMenu'; -import useTheme from '../theme/useTheme'; - -const AppObjectDiv = styled.div` - padding: 5px; - ${props => - !props.disableHover && - ` - &:hover { - background-color: ${props.theme.left_background_blue[1]}; - } - `} - cursor: pointer; - white-space: nowrap; - font-weight: ${props => (props.isBold ? 'bold' : 'normal')}; -`; - -const IconWrap = styled.span` - margin-right: 5px; -`; - -const StatusIconWrap = styled.span` - margin-left: 5px; -`; - -const ExtInfoWrap = styled.span` - font-weight: normal; - margin-left: 5px; - color: ${props => props.theme.left_font3}; -`; - -export function AppObjectCore({ - title, - icon, - data, - onClick = undefined, - onClick2 = undefined, - onClick3 = undefined, - isBold = undefined, - isBusy = undefined, - prefix = undefined, - statusIcon = undefined, - extInfo = undefined, - statusTitle = undefined, - disableHover = false, - children = null, - Menu = undefined, - ...other -}) { - const theme = useTheme(); - const showMenu = useShowMenu(); - - const handleContextMenu = event => { - if (!Menu) return; - - event.preventDefault(); - showMenu(event.pageX, event.pageY, ); - }; - - return ( - <> - { - if (onClick) onClick(data); - if (onClick2) onClick2(data); - if (onClick3) onClick3(data); - }} - theme={theme} - isBold={isBold} - draggable - onDragStart={e => { - e.dataTransfer.setData('app_object_drag_data', JSON.stringify(data)); - }} - disableHover={disableHover} - {...other} - > - {prefix} - {isBusy ? : } - {title} - {statusIcon && ( - - - - )} - {extInfo && {extInfo}} - - {children} - - ); -} diff --git a/packages/web/src/appobj/AppObjectList.js b/packages/web/src/appobj/AppObjectList.js deleted file mode 100644 index 7f6ac8556..000000000 --- a/packages/web/src/appobj/AppObjectList.js +++ /dev/null @@ -1,164 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import styled from 'styled-components'; -import { ExpandIcon } from '../icons'; -import useTheme from '../theme/useTheme'; - -const SubItemsDiv = styled.div` - margin-left: 28px; -`; - -const ExpandIconHolder2 = styled.span` - margin-right: 3px; - // position: relative; - // top: -3px; -`; - -const ExpandIconHolder = styled.span` - margin-right: 5px; -`; - -const GroupDiv = styled.div` - user-select: none; - padding: 5px; - &:hover { - background-color: ${props => props.theme.left_background_blue[1]}; - } - cursor: pointer; - white-space: nowrap; - font-weight: bold; -`; - -function AppObjectListItem({ - AppObjectComponent, - data, - filter, - onObjectClick, - isExpandable, - SubItems, - getCommonProps, - expandOnClick, - ExpandIconComponent, -}) { - const [isExpanded, setIsExpanded] = React.useState(false); - - const expandable = data && isExpandable && isExpandable(data); - - React.useEffect(() => { - if (!expandable) { - setIsExpanded(false); - } - }, [expandable]); - - let commonProps = { - prefix: SubItems ? ( - - {expandable ? ( - { - setIsExpanded(v => !v); - e.stopPropagation(); - }} - /> - ) : ( - - )} - - ) : null, - }; - - if (SubItems && expandOnClick) { - commonProps.onClick2 = () => setIsExpanded(v => !v); - } - if (onObjectClick) { - commonProps.onClick3 = onObjectClick; - } - - if (getCommonProps) { - commonProps = { ...commonProps, ...getCommonProps(data) }; - } - - let res = ; - if (SubItems && isExpanded) { - res = ( - <> - {res} - - - - - ); - } - return res; -} - -function AppObjectGroup({ group, items }) { - const [isExpanded, setIsExpanded] = React.useState(true); - const theme = useTheme(); - const filtered = items.filter(x => x.component); - let countText = filtered.length.toString(); - if (filtered.length < items.length) countText += `/${items.length}`; - - return ( - <> - setIsExpanded(!isExpanded)} theme={theme}> - - - - {group} {items && `(${countText})`} - - {isExpanded && filtered.map(x => x.component)} - - ); -} - -export function AppObjectList({ - list, - AppObjectComponent, - SubItems = undefined, - onObjectClick = undefined, - filter = undefined, - groupFunc = undefined, - groupOrdered = undefined, - isExpandable = undefined, - getCommonProps = undefined, - expandOnClick = false, - ExpandIconComponent = ExpandIcon, -}) { - const createComponent = data => ( - - ); - - if (groupFunc) { - const listGrouped = _.compact( - (list || []).map(data => { - const matcher = AppObjectComponent.createMatcher && AppObjectComponent.createMatcher(data); - const component = matcher && !matcher(filter) ? null : createComponent(data); - const group = groupFunc(data); - return { group, data, component }; - }) - ); - const groups = _.groupBy(listGrouped, 'group'); - return (groupOrdered || _.keys(groups)).map(group => ( - - )); - } - - return (list || []).map(data => { - const matcher = AppObjectComponent.createMatcher && AppObjectComponent.createMatcher(data); - if (matcher && !matcher(filter)) return null; - return createComponent(data); - }); -} diff --git a/packages/web/src/appobj/ArchiveFileAppObject.js b/packages/web/src/appobj/ArchiveFileAppObject.js deleted file mode 100644 index 3fed3ba7c..000000000 --- a/packages/web/src/appobj/ArchiveFileAppObject.js +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import { DropDownMenuItem } from '../modals/DropDownMenu'; -import { filterName } from 'dbgate-datalib'; -import axios from '../utility/axios'; -import { AppObjectCore } from './AppObjectCore'; -import useOpenNewTab from '../utility/useOpenNewTab'; - -function openArchive(openNewTab, fileName, folderName) { - openNewTab({ - title: fileName, - icon: 'img archive', - tooltip: `${folderName}\n${fileName}`, - tabComponent: 'ArchiveFileTab', - props: { - archiveFile: fileName, - archiveFolder: folderName, - }, - }); -} - -function Menu({ data }) { - const openNewTab = useOpenNewTab(); - const handleDelete = () => { - axios.post('archive/delete-file', { file: data.fileName, folder: data.folderName }); - // setOpenedTabs((tabs) => tabs.filter((x) => x.tabid != data.tabid)); - }; - const handleOpenRead = () => { - openArchive(openNewTab, data.fileName, data.folderName); - }; - const handleOpenWrite = async () => { - // const resp = await axios.post('archive/load-free-table', { file: data.fileName, folder: data.folderName }); - - openNewTab({ - title: data.fileName, - icon: 'img archive', - tabComponent: 'FreeTableTab', - props: { - initialArgs: { - functionName: 'archiveReader', - props: { - fileName: data.fileName, - folderName: data.folderName, - }, - }, - archiveFile: data.fileName, - archiveFolder: data.folderName, - }, - }); - }; - - return ( - <> - Open (readonly) - Open in free table editor - Delete - - ); -} - -function ArchiveFileAppObject({ data, commonProps }) { - const { fileName, folderName } = data; - const openNewTab = useOpenNewTab(); - const onClick = () => { - openArchive(openNewTab, fileName, folderName); - }; - - return ( - - ); -} - -ArchiveFileAppObject.extractKey = data => data.fileName; -ArchiveFileAppObject.createMatcher = ({ fileName }) => filter => filterName(filter, fileName); - -export default ArchiveFileAppObject; diff --git a/packages/web/src/appobj/ArchiveFolderAppObject.js b/packages/web/src/appobj/ArchiveFolderAppObject.js deleted file mode 100644 index bcfced98e..000000000 --- a/packages/web/src/appobj/ArchiveFolderAppObject.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import { DropDownMenuItem } from '../modals/DropDownMenu'; -import axios from '../utility/axios'; -import { filterName } from 'dbgate-datalib'; -import { AppObjectCore } from './AppObjectCore'; -import { useCurrentArchive } from '../utility/globalState'; - -function Menu({ data }) { - const handleDelete = () => { - axios.post('archive/delete-folder', { folder: data.name }); - }; - return <>{data.name != 'default' && Delete}; -} - -function ArchiveFolderAppObject({ data, commonProps }) { - const { name } = data; - const currentArchive = useCurrentArchive(); - - return ( - - ); -} - -ArchiveFolderAppObject.extractKey = data => data.name; -ArchiveFolderAppObject.createMatcher = data => filter => filterName(filter, data.name); - -export default ArchiveFolderAppObject; diff --git a/packages/web/src/appobj/ClosedTabAppObject.js b/packages/web/src/appobj/ClosedTabAppObject.js deleted file mode 100644 index 0ef7e11b9..000000000 --- a/packages/web/src/appobj/ClosedTabAppObject.js +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import moment from 'moment'; -import { DropDownMenuItem } from '../modals/DropDownMenu'; -import { useSetOpenedTabs } from '../utility/globalState'; -import { AppObjectCore } from './AppObjectCore'; -import { setSelectedTabFunc } from '../utility/common'; -import styled from 'styled-components'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; - -const InfoDiv = styled.div` - margin-left: 30px; - color: ${props => props.theme.left_font3}; -`; - -function Menu({ data }) { - const setOpenedTabs = useSetOpenedTabs(); - const handleDelete = () => { - setOpenedTabs(tabs => tabs.filter(x => x.tabid != data.tabid)); - }; - const handleDeleteOlder = () => { - setOpenedTabs(tabs => tabs.filter(x => !x.closedTime || x.closedTime >= data.closedTime)); - }; - return ( - <> - Delete - Delete older - - ); -} - -function ClosedTabAppObject({ data, commonProps }) { - const { tabid, props, selected, icon, title, closedTime, busy } = data; - const setOpenedTabs = useSetOpenedTabs(); - const theme = useTheme(); - - const onClick = () => { - setOpenedTabs(files => - setSelectedTabFunc( - files.map(x => ({ - ...x, - closedTime: x.tabid == tabid ? undefined : x.closedTime, - })), - tabid - ) - ); - }; - - return ( - - {data.props && data.props.database && ( - - {data.props.database} - - )} - {data.contentPreview && {data.contentPreview}} - - ); -} - -ClosedTabAppObject.extractKey = data => data.tabid; - -export default ClosedTabAppObject; diff --git a/packages/web/src/appobj/ConnectionAppObject.js b/packages/web/src/appobj/ConnectionAppObject.js deleted file mode 100644 index 4e5c7c3b0..000000000 --- a/packages/web/src/appobj/ConnectionAppObject.js +++ /dev/null @@ -1,119 +0,0 @@ -import _ from 'lodash'; -import React from 'react'; -import { DropDownMenuItem } from '../modals/DropDownMenu'; -import ConnectionModal from '../modals/ConnectionModal'; -import axios from '../utility/axios'; -import { filterName } from 'dbgate-datalib'; -import ConfirmModal from '../modals/ConfirmModal'; -import CreateDatabaseModal from '../modals/CreateDatabaseModal'; -import { useCurrentDatabase, useOpenedConnections, useSetOpenedConnections } from '../utility/globalState'; -import { AppObjectCore } from './AppObjectCore'; -import useShowModal from '../modals/showModal'; -import { useConfig } from '../utility/metadataLoaders'; -import useExtensions from '../utility/useExtensions'; - -function Menu({ data }) { - const openedConnections = useOpenedConnections(); - const setOpenedConnections = useSetOpenedConnections(); - const showModal = useShowModal(); - const config = useConfig(); - - const handleEdit = () => { - showModal(modalState => ); - }; - const handleDelete = () => { - showModal(modalState => ( - axios.post('connections/delete', data)} - /> - )); - }; - const handleCreateDatabase = () => { - showModal(modalState => ); - }; - const handleRefresh = () => { - axios.post('server-connections/refresh', { conid: data._id }); - }; - const handleDisconnect = () => { - setOpenedConnections(list => list.filter(x => x != data._id)); - }; - const handleConnect = () => { - setOpenedConnections(list => _.uniq([...list, data._id])); - }; - return ( - <> - {config.runAsPortal == false && ( - <> - Edit - Delete - - )} - {!openedConnections.includes(data._id) && Connect} - {openedConnections.includes(data._id) && data.status && ( - Refresh - )} - {openedConnections.includes(data._id) && ( - Disconnect - )} - {openedConnections.includes(data._id) && ( - Create database - )} - - ); -} - -function ConnectionAppObject({ data, commonProps }) { - const { _id, server, displayName, engine, status } = data; - const openedConnections = useOpenedConnections(); - const setOpenedConnections = useSetOpenedConnections(); - const currentDatabase = useCurrentDatabase(); - const extensions = useExtensions(); - - const isBold = _.get(currentDatabase, 'connection._id') == _id; - const onClick = () => setOpenedConnections(c => _.uniq([...c, _id])); - - let statusIcon = null; - let statusTitle = null; - - let extInfo = null; - if (extensions.drivers.find(x => x.engine == engine)) { - const match = (engine || '').match(/^([^@]*)@/); - extInfo = match ? match[1] : engine; - } else { - extInfo = engine; - statusIcon = 'img warn'; - statusTitle = `Engine driver ${engine} not found, review installed plugins and change engine in edit connection dialog`; - } - - if (openedConnections.includes(_id)) { - if (!status) statusIcon = 'icon loading'; - else if (status.name == 'pending') statusIcon = 'icon loading'; - else if (status.name == 'ok') statusIcon = 'img ok'; - else statusIcon = 'img error'; - if (status && status.name == 'error') { - statusTitle = status.message; - } - } - - return ( - - ); -} - -ConnectionAppObject.extractKey = data => data._id; -ConnectionAppObject.createMatcher = ({ displayName, server }) => filter => filterName(filter, displayName, server); - -export default ConnectionAppObject; diff --git a/packages/web/src/appobj/DatabaseAppObject.js b/packages/web/src/appobj/DatabaseAppObject.js deleted file mode 100644 index 04cef6bd9..000000000 --- a/packages/web/src/appobj/DatabaseAppObject.js +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import { DropDownMenuItem } from '../modals/DropDownMenu'; -import ImportExportModal from '../modals/ImportExportModal'; -import { getDefaultFileFormat } from '../utility/fileformats'; -import { useCurrentDatabase } from '../utility/globalState'; -import { AppObjectCore } from './AppObjectCore'; -import useShowModal from '../modals/showModal'; -import useExtensions from '../utility/useExtensions'; -import useOpenNewTab from '../utility/useOpenNewTab'; - -function Menu({ data }) { - const { connection, name } = data; - const openNewTab = useOpenNewTab(); - - const extensions = useExtensions(); - const showModal = useShowModal(); - - const tooltip = `${connection.displayName || connection.server}\n${name}`; - - const handleNewQuery = () => { - openNewTab({ - title: 'Query #', - icon: 'img sql-file', - tooltip, - tabComponent: 'QueryTab', - props: { - conid: connection._id, - database: name, - }, - }); - }; - - const handleImport = () => { - showModal(modalState => ( - - )); - }; - - const handleExport = () => { - showModal(modalState => ( - - )); - }; - - return ( - <> - New query - Import - Export - - ); -} - -function DatabaseAppObject({ data, commonProps }) { - const { name, connection } = data; - const currentDatabase = useCurrentDatabase(); - return ( - - ); -} - -DatabaseAppObject.extractKey = props => props.name; - -export default DatabaseAppObject; diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.js b/packages/web/src/appobj/DatabaseObjectAppObject.js deleted file mode 100644 index dbf5bff84..000000000 --- a/packages/web/src/appobj/DatabaseObjectAppObject.js +++ /dev/null @@ -1,325 +0,0 @@ -import _ from 'lodash'; -import React from 'react'; -import { DropDownMenuDivider, DropDownMenuItem } from '../modals/DropDownMenu'; -import { getConnectionInfo } from '../utility/metadataLoaders'; -import fullDisplayName from '../utility/fullDisplayName'; -import { filterName } from 'dbgate-datalib'; -import ImportExportModal from '../modals/ImportExportModal'; -import { useSetOpenedTabs } from '../utility/globalState'; -import { AppObjectCore } from './AppObjectCore'; -import useShowModal from '../modals/showModal'; -import { findEngineDriver } from 'dbgate-tools'; -import useExtensions from '../utility/useExtensions'; -import useOpenNewTab from '../utility/useOpenNewTab'; -import uuidv1 from 'uuid/v1'; -import { AppObjectList } from './AppObjectList'; - -const icons = { - tables: 'img table', - views: 'img view', - procedures: 'img procedure', - functions: 'img function', -}; - -const menus = { - tables: [ - { - label: 'Open data', - tab: 'TableDataTab', - forceNewTab: true, - }, - { - label: 'Open form', - tab: 'TableDataTab', - forceNewTab: true, - initialData: { - grid: { - isFormView: true, - }, - }, - }, - { - label: 'Open structure', - tab: 'TableStructureTab', - }, - { - label: 'Query designer', - isQueryDesigner: true, - }, - { - isDivider: true, - }, - { - label: 'Export', - isExport: true, - }, - { - label: 'Open in free table editor', - isOpenFreeTable: true, - }, - { - label: 'Open active chart', - isActiveChart: true, - }, - { - isDivider: true, - }, - { - label: 'SQL: CREATE TABLE', - sqlTemplate: 'CREATE TABLE', - }, - ], - views: [ - { - label: 'Open data', - tab: 'ViewDataTab', - forceNewTab: true, - }, - { - label: 'Open structure', - tab: 'TableStructureTab', - }, - { - label: 'Query designer', - isQueryDesigner: true, - }, - { - isDivider: true, - }, - { - label: 'Export', - isExport: true, - }, - { - label: 'Open in free table editor', - isOpenFreeTable: true, - }, - { - label: 'Open active chart', - isActiveChart: true, - }, - { - isDivider: true, - }, - { - label: 'SQL: CREATE VIEW', - sqlTemplate: 'CREATE OBJECT', - }, - { - label: 'SQL: CREATE TABLE', - sqlTemplate: 'CREATE TABLE', - }, - ], - procedures: [ - { - label: 'SQL: CREATE PROCEDURE', - sqlTemplate: 'CREATE OBJECT', - }, - { - label: 'SQL: EXECUTE', - sqlTemplate: 'EXECUTE PROCEDURE', - }, - ], - functions: [ - { - label: 'SQL: CREATE FUNCTION', - sqlTemplate: 'CREATE OBJECT', - }, - ], -}; - -const defaultTabs = { - tables: 'TableDataTab', - views: 'ViewDataTab', -}; - -export async function openDatabaseObjectDetail( - openNewTab, - tabComponent, - sqlTemplate, - { schemaName, pureName, conid, database, objectTypeField }, - forceNewTab, - initialData -) { - const connection = await getConnectionInfo({ conid }); - const tooltip = `${connection.displayName || connection.server}\n${database}\n${fullDisplayName({ - schemaName, - pureName, - })}`; - - openNewTab( - { - title: sqlTemplate ? 'Query #' : pureName, - tooltip, - icon: sqlTemplate ? 'img sql-file' : icons[objectTypeField], - tabComponent: sqlTemplate ? 'QueryTab' : tabComponent, - props: { - schemaName, - pureName, - conid, - database, - objectTypeField, - initialArgs: sqlTemplate ? { sqlTemplate } : null, - }, - }, - initialData, - { forceNewTab } - ); -} - -function Menu({ data }) { - const showModal = useShowModal(); - const openNewTab = useOpenNewTab(); - const extensions = useExtensions(); - - const getDriver = async () => { - const conn = await getConnectionInfo(data); - if (!conn) return; - const driver = findEngineDriver(conn, extensions); - return driver; - }; - - return ( - <> - {menus[data.objectTypeField].map(menu => - menu.isDivider ? ( - - ) : ( - { - if (menu.isExport) { - showModal(modalState => ( - - )); - } else if (menu.isOpenFreeTable) { - const coninfo = await getConnectionInfo(data); - openNewTab({ - title: data.pureName, - icon: 'img free-table', - tabComponent: 'FreeTableTab', - props: { - initialArgs: { - functionName: 'tableReader', - props: { - connection: { - ...coninfo, - database: data.database, - }, - schemaName: data.schemaName, - pureName: data.pureName, - }, - }, - }, - }); - } else if (menu.isActiveChart) { - const driver = await getDriver(); - const dmp = driver.createDumper(); - dmp.put('^select * from %f', data); - openNewTab( - { - title: data.pureName, - icon: 'img chart', - tabComponent: 'ChartTab', - props: { - conid: data.conid, - database: data.database, - }, - }, - { - editor: { - config: { chartType: 'bar' }, - sql: dmp.s, - }, - } - ); - } else if (menu.isQueryDesigner) { - openNewTab( - { - title: 'Query #', - icon: 'img query-design', - tabComponent: 'QueryDesignTab', - props: { - conid: data.conid, - database: data.database, - }, - }, - { - editor: { - tables: [ - { - ...data, - designerId: uuidv1(), - left: 50, - top: 50, - }, - ], - }, - } - ); - } else { - openDatabaseObjectDetail( - openNewTab, - menu.tab, - menu.sqlTemplate, - data, - menu.forceNewTab, - menu.initialData - ); - } - }} - > - {menu.label} - - ) - )} - - ); -} - -function DatabaseObjectAppObject({ data, commonProps }) { - const { conid, database, pureName, schemaName, objectTypeField } = data; - const openNewTab = useOpenNewTab(); - const onClick = ({ schemaName, pureName }) => { - openDatabaseObjectDetail( - openNewTab, - defaultTabs[objectTypeField], - defaultTabs[objectTypeField] ? null : 'CREATE OBJECT', - { - schemaName, - pureName, - conid, - database, - objectTypeField, - }, - false - ); - }; - - return ( - - ); -} - -DatabaseObjectAppObject.extractKey = ({ schemaName, pureName }) => - schemaName ? `${schemaName}.${pureName}` : pureName; - -DatabaseObjectAppObject.createMatcher = ({ pureName }) => filter => filterName(filter, pureName); - -export default DatabaseObjectAppObject; diff --git a/packages/web/src/appobj/FavoriteFileAppObject.js b/packages/web/src/appobj/FavoriteFileAppObject.js deleted file mode 100644 index d9136a0f5..000000000 --- a/packages/web/src/appobj/FavoriteFileAppObject.js +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; -import { DropDownMenuItem } from '../modals/DropDownMenu'; -import FavoriteModal from '../modals/FavoriteModal'; -import useShowModal from '../modals/showModal'; -import axios from '../utility/axios'; -import { copyTextToClipboard } from '../utility/clipboard'; -import getElectron from '../utility/getElectron'; -import useOpenNewTab from '../utility/useOpenNewTab'; -import { SavedFileAppObjectBase } from './SavedFileAppObject'; - -export function useOpenFavorite() { - const openNewTab = useOpenNewTab(); - - const openFavorite = React.useCallback( - async favorite => { - const { icon, tabComponent, title, props, tabdata } = favorite; - let tabdataNew = tabdata; - if (props.savedFile) { - const resp = await axios.post('files/load', { - folder: props.savedFolder, - file: props.savedFile, - format: props.savedFormat, - }); - tabdataNew = { - ...tabdata, - editor: resp.data, - }; - } - openNewTab( - { - title, - icon: icon || 'img favorite', - props, - tabComponent, - }, - tabdataNew - ); - }, - [openNewTab] - ); - - return openFavorite; -} - -export function FavoriteFileAppObject({ data, commonProps }) { - const { icon, tabComponent, title, props, tabdata, urlPath } = data; - const openNewTab = useOpenNewTab(); - const showModal = useShowModal(); - const openFavorite = useOpenFavorite(); - const electron = getElectron(); - - const editFavorite = () => { - showModal(modalState => ); - }; - - const editFavoriteJson = async () => { - const resp = await axios.post('files/load', { - folder: 'favorites', - file: data.file, - format: 'text', - }); - - openNewTab( - { - icon: 'icon favorite', - title, - tabComponent: 'FavoriteEditorTab', - props: { - savedFile: data.file, - savedFormat: 'text', - savedFolder: 'favorites', - }, - }, - { editor: JSON.stringify(JSON.parse(resp.data), null, 2) } - ); - }; - - const copyLink = () => { - copyTextToClipboard(`${document.location.origin}#favorite=${urlPath}`); - }; - - return ( - { - openFavorite(data); - }} - menuExt={ - <> - Edit - Edit JSON definition - {!electron && urlPath && Copy link} - - } - /> - ); -} - -FavoriteFileAppObject.extractKey = data => data.file; diff --git a/packages/web/src/appobj/MacroAppObject.js b/packages/web/src/appobj/MacroAppObject.js deleted file mode 100644 index 4a59e6c35..000000000 --- a/packages/web/src/appobj/MacroAppObject.js +++ /dev/null @@ -1,15 +0,0 @@ -import _ from 'lodash'; -import React from 'react'; -import { filterName } from 'dbgate-datalib'; -import { AppObjectCore } from './AppObjectCore'; - -function MacroAppObject({ data, commonProps }) { - const { name, type, title, group } = data; - - return ; -} - -MacroAppObject.extractKey = data => data.name; -MacroAppObject.createMatcher = ({ name, title }) => filter => filterName(filter, name, title); - -export default MacroAppObject; diff --git a/packages/web/src/appobj/SavedFileAppObject.js b/packages/web/src/appobj/SavedFileAppObject.js deleted file mode 100644 index 3feb1e9fb..000000000 --- a/packages/web/src/appobj/SavedFileAppObject.js +++ /dev/null @@ -1,314 +0,0 @@ -import React from 'react'; -import axios from '../utility/axios'; -import _ from 'lodash'; -import { DropDownMenuItem } from '../modals/DropDownMenu'; -import { AppObjectCore } from './AppObjectCore'; -import useNewQuery from '../query/useNewQuery'; -import { useCurrentDatabase } from '../utility/globalState'; -import ScriptWriter from '../impexp/ScriptWriter'; -import { extractPackageName } from 'dbgate-tools'; -import useShowModal from '../modals/showModal'; -import InputTextModal from '../modals/InputTextModal'; -import useHasPermission from '../utility/useHasPermission'; -import useOpenNewTab from '../utility/useOpenNewTab'; -import ConfirmModal from '../modals/ConfirmModal'; - -function Menu({ data, menuExt = null, title = undefined, disableRename = false }) { - const hasPermission = useHasPermission(); - const showModal = useShowModal(); - const handleDelete = () => { - showModal(modalState => ( - { - axios.post('files/delete', data); - }} - /> - )); - }; - const handleRename = () => { - showModal(modalState => ( - { - axios.post('files/rename', { ...data, newFile }); - }} - /> - )); - }; - return ( - <> - {hasPermission(`files/${data.folder}/write`) && ( - Delete - )} - {hasPermission(`files/${data.folder}/write`) && !disableRename && ( - Rename - )} - {menuExt} - - ); -} - -export function SavedFileAppObjectBase({ - data, - commonProps, - format, - icon, - onLoad, - title = undefined, - menuExt = null, - disableRename = false, -}) { - const { file, folder } = data; - - const onClick = async () => { - const resp = await axios.post('files/load', { folder, file, format }); - onLoad(resp.data); - }; - - return ( - } - /> - ); -} - -export function SavedSqlFileAppObject({ data, commonProps }) { - const { file, folder } = data; - const newQuery = useNewQuery(); - const currentDatabase = useCurrentDatabase(); - const openNewTab = useOpenNewTab(); - - const connection = _.get(currentDatabase, 'connection'); - const database = _.get(currentDatabase, 'name'); - - const handleGenerateExecute = () => { - const script = new ScriptWriter(); - const conn = { - ..._.omit(connection, ['displayName', '_id']), - database, - }; - script.put(`const sql = await dbgateApi.loadFile('${folder}/${file}');`); - script.put(`await dbgateApi.executeQuery({ sql, connection: ${JSON.stringify(conn)} });`); - // @ts-ignore - script.requirePackage(extractPackageName(conn.engine)); - - openNewTab( - { - title: 'Shell #', - icon: 'img shell', - tabComponent: 'ShellTab', - }, - { editor: script.getScript() } - ); - }; - - return ( - Generate shell execute - ) : null - } - onLoad={data => { - newQuery({ - title: file, - initialData: data, - // @ts-ignore - savedFile: file, - savedFolder: 'sql', - savedFormat: 'text', - }); - }} - /> - ); -} - -export function SavedShellFileAppObject({ data, commonProps }) { - const { file, folder } = data; - const openNewTab = useOpenNewTab(); - - return ( - { - openNewTab( - { - title: file, - icon: 'img shell', - tabComponent: 'ShellTab', - props: { - savedFile: file, - savedFolder: 'shell', - savedFormat: 'text', - }, - }, - { editor: data } - ); - }} - /> - ); -} - -export function SavedChartFileAppObject({ data, commonProps }) { - const { file, folder } = data; - const openNewTab = useOpenNewTab(); - - const currentDatabase = useCurrentDatabase(); - - const connection = _.get(currentDatabase, 'connection') || {}; - const database = _.get(currentDatabase, 'name'); - - const tooltip = `${connection.displayName || connection.server}\n${database}`; - - return ( - { - openNewTab( - { - title: file, - icon: 'img chart', - tooltip, - props: { - conid: connection._id, - database, - savedFile: file, - savedFolder: 'charts', - savedFormat: 'json', - }, - tabComponent: 'ChartTab', - }, - { editor: data } - ); - }} - /> - ); -} - -export function SavedQueryFileAppObject({ data, commonProps }) { - const { file, folder } = data; - const openNewTab = useOpenNewTab(); - - const currentDatabase = useCurrentDatabase(); - - const connection = _.get(currentDatabase, 'connection') || {}; - const database = _.get(currentDatabase, 'name'); - - const tooltip = `${connection.displayName || connection.server}\n${database}`; - - return ( - { - openNewTab( - { - title: file, - icon: 'img query-design', - tooltip, - props: { - conid: connection._id, - database, - savedFile: file, - savedFolder: 'query', - savedFormat: 'json', - }, - tabComponent: 'QueryDesignTab', - }, - { editor: data } - ); - }} - /> - ); -} - -export function SavedMarkdownFileAppObject({ data, commonProps }) { - const { file, folder } = data; - const openNewTab = useOpenNewTab(); - - const showPage = () => { - openNewTab({ - title: file, - icon: 'img markdown', - tabComponent: 'MarkdownViewTab', - props: { - savedFile: file, - savedFolder: 'markdown', - savedFormat: 'text', - }, - }); - }; - return ( - { - openNewTab( - { - title: file, - icon: 'img markdown', - tabComponent: 'MarkdownEditorTab', - props: { - savedFile: file, - savedFolder: 'markdown', - savedFormat: 'text', - }, - }, - { editor: data } - ); - }} - menuExt={Show page} - /> - ); -} - -export function SavedFileAppObject({ data, commonProps }) { - const { folder } = data; - const folderTypes = { - sql: SavedSqlFileAppObject, - shell: SavedShellFileAppObject, - charts: SavedChartFileAppObject, - markdown: SavedMarkdownFileAppObject, - query: SavedQueryFileAppObject, - }; - const AppObject = folderTypes[folder]; - if (AppObject) { - return ; - } - return null; -} - -[ - SavedSqlFileAppObject, - SavedShellFileAppObject, - SavedChartFileAppObject, - SavedMarkdownFileAppObject, - SavedFileAppObject, -].forEach(fn => { - // @ts-ignore - fn.extractKey = data => data.file; -}); diff --git a/packages/web/src/appobj/SubColumnParamList.js b/packages/web/src/appobj/SubColumnParamList.js deleted file mode 100644 index 792b915d8..000000000 --- a/packages/web/src/appobj/SubColumnParamList.js +++ /dev/null @@ -1,36 +0,0 @@ -import { findForeignKeyForColumn } from 'dbgate-tools'; -import React from 'react'; -import { getColumnIcon } from '../datagrid/ColumnLabel'; -import { AppObjectCore } from './AppObjectCore'; -import { AppObjectList } from './AppObjectList'; - -function ColumnAppObject({ data, commonProps }) { - const { columnName, dataType, foreignKey } = data; - let extInfo = dataType; - if (foreignKey) extInfo += ` -> ${foreignKey.refTableName}`; - return ( - - ); -} -ColumnAppObject.extractKey = ({ columnName }) => columnName; - -export default function SubColumnParamList({ data }) { - const { columns } = data; - - return ( - ({ - ...col, - foreignKey: findForeignKeyForColumn(data, col), - }))} - AppObjectComponent={ColumnAppObject} - /> - ); -} diff --git a/packages/web/src/celldata/CellDataView.js b/packages/web/src/celldata/CellDataView.js deleted file mode 100644 index bea7c6ec5..000000000 --- a/packages/web/src/celldata/CellDataView.js +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import { SelectField } from '../utility/inputs'; -import ErrorInfo from '../widgets/ErrorInfo'; -import styled from 'styled-components'; -import { TextCellViewWrap, TextCellViewNoWrap } from './TextCellView'; -import JsonCellView from './JsonCellDataView'; -import useTheme from '../theme/useTheme'; - -const Toolbar = styled.div` - display: flex; - background: ${props => props.theme.toolbar_background}; - align-items: center; -`; - -const MainWrapper = styled.div` - display: flex; - flex: 1; - flex-direction: column; -`; - -const DataWrapper = styled.div` - display: flex; - flex: 1; -`; - -const formats = [ - { - type: 'textWrap', - title: 'Text (wrap)', - Component: TextCellViewWrap, - single: true, - }, - { - type: 'text', - title: 'Text (no wrap)', - Component: TextCellViewNoWrap, - single: true, - }, - { - type: 'json', - title: 'Json', - Component: JsonCellView, - single: true, - }, -]; - -function autodetect(selection, grider, value) { - if (_.isString(value)) { - if (value.startsWith('[') || value.startsWith('{')) return 'json'; - } - return 'textWrap'; -} - -export default function CellDataView({ selection = undefined, grider = undefined, selectedValue = undefined }) { - const [selectedFormatType, setSelectedFormatType] = React.useState('autodetect'); - const theme = useTheme(); - let value = null; - if (grider && selection && selection.length == 1) { - const rowData = grider.getRowData(selection[0].row); - const { column } = selection[0]; - if (rowData) value = rowData[column]; - } - if (selectedValue) { - value = selectedValue; - } - const autodetectFormatType = React.useMemo(() => autodetect(selection, grider, value), [selection, grider, value]); - const autodetectFormat = formats.find(x => x.type == autodetectFormatType); - - const usedFormatType = selectedFormatType == 'autodetect' ? autodetectFormatType : selectedFormatType; - const usedFormat = formats.find(x => x.type == usedFormatType); - - const { Component } = usedFormat || {}; - - return ( - - - Format: - setSelectedFormatType(e.target.value)}> - - - {formats.map(fmt => ( - - ))} - - - - - {usedFormat == null || (usedFormat.single && value == null) ? ( - - ) : ( - - )} - - - ); -} diff --git a/packages/web/src/celldata/JsonCellDataView.js b/packages/web/src/celldata/JsonCellDataView.js deleted file mode 100644 index 3987fc456..000000000 --- a/packages/web/src/celldata/JsonCellDataView.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import ReactJson from 'react-json-view'; -import ErrorInfo from '../widgets/ErrorInfo'; -import useTheme from '../theme/useTheme'; - -const OuterWrapper = styled.div` - flex: 1; - position: relative; -`; - -const InnerWrapper = styled.div` - overflow: scroll; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; -`; - -export default function JsonCellView({ value }) { - const theme = useTheme(); - try { - const json = React.useMemo(() => JSON.parse(value), [value]); - return ( - - - - - - ); - } catch (err) { - return ; - } -} diff --git a/packages/web/src/celldata/TextCellView.js b/packages/web/src/celldata/TextCellView.js deleted file mode 100644 index dc9bc4882..000000000 --- a/packages/web/src/celldata/TextCellView.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; - -const StyledInput = styled.textarea` - flex: 1; -`; - -export function TextCellViewWrap({ value, grider, selection }) { - return ; -} - -export function TextCellViewNoWrap({ value, grider, selection }) { - return ( - grider.setCellValue(selection[0].row, selection[0].column, e.target.value)} - /> - ); -} diff --git a/packages/web/src/charts/ChartEditor.js b/packages/web/src/charts/ChartEditor.js deleted file mode 100644 index 588f740a0..000000000 --- a/packages/web/src/charts/ChartEditor.js +++ /dev/null @@ -1,154 +0,0 @@ -import React from 'react'; -import Chart from 'react-chartjs-2'; -import _ from 'lodash'; -import styled from 'styled-components'; -import useTheme from '../theme/useTheme'; -import useDimensions from '../utility/useDimensions'; -import { HorizontalSplitter } from '../widgets/Splitter'; -import WidgetColumnBar, { WidgetColumnBarItem } from '../widgets/WidgetColumnBar'; -import { FormCheckboxField, FormSelectField, FormTextField } from '../utility/forms'; -import DataChart from './DataChart'; -import { FormProviderCore } from '../utility/FormProvider'; -import { loadChartData, loadChartStructure } from './chartDataLoader'; -import useExtensions from '../utility/useExtensions'; -import { getConnectionInfo } from '../utility/metadataLoaders'; -import { findEngineDriver } from 'dbgate-tools'; -import { FormFieldTemplateTiny } from '../utility/formStyle'; -import { ManagerInnerContainer } from '../datagrid/ManagerStyles'; -import { presetPrimaryColors } from '@ant-design/colors'; -import ErrorInfo from '../widgets/ErrorInfo'; - -const LeftContainer = styled.div` - background-color: ${props => props.theme.manager_background}; - display: flex; - flex: 1; -`; - -export default function ChartEditor({ data, config, setConfig, sql, conid, database }) { - const [managerSize, setManagerSize] = React.useState(0); - const theme = useTheme(); - const extensions = useExtensions(); - const [error, setError] = React.useState(null); - - const [availableColumnNames, setAvailableColumnNames] = React.useState([]); - const [loadedData, setLoadedData] = React.useState(null); - - const getDriver = async () => { - const conn = await getConnectionInfo({ conid }); - if (!conn) return; - const driver = findEngineDriver(conn, extensions); - return driver; - }; - - const handleLoadColumns = async () => { - const driver = await getDriver(); - if (!driver) return; - try { - const columns = await loadChartStructure(driver, conid, database, sql); - setAvailableColumnNames(columns); - } catch (err) { - setError(err.message); - } - }; - - const handleLoadData = async () => { - const driver = await getDriver(); - if (!driver) return; - const loaded = await loadChartData(driver, conid, database, sql, config); - if (!loaded) return; - const { columns, rows } = loaded; - setLoadedData({ - structure: columns, - rows, - }); - }; - - React.useEffect(() => { - if (sql && conid && database) { - handleLoadColumns(); - } - }, [sql, conid, database, extensions]); - - React.useEffect(() => { - if (data) { - setAvailableColumnNames(data ? data.structure.columns.map(x => x.columnName) : []); - } - }, [data]); - - React.useEffect(() => { - if (config.labelColumn && sql && conid && database) { - handleLoadData(); - } - }, [config, sql, conid, database, availableColumnNames]); - - if (error) { - return ( -
- -
- ); - } - - return ( - - - - - - - - - - {/* */} - - - {/* - */} - - - - - - - - - - - - - {availableColumnNames.length > 0 && ( - - - {availableColumnNames.map(col => ( - - ))} - - )} - {availableColumnNames.map(col => ( - - - {config[`dataColumn_${col}`] && ( - - - - {_.keys(presetPrimaryColors).map(color => ( - - ))} - - )} - - ))} - - - - - - - - - ); -} diff --git a/packages/web/src/charts/ChartToolbar.js b/packages/web/src/charts/ChartToolbar.js deleted file mode 100644 index 62b3a1aaf..000000000 --- a/packages/web/src/charts/ChartToolbar.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import ToolbarButton from '../widgets/ToolbarButton'; - -export default function ChartToolbar({ modelState, dispatchModel }) { - return ( - <> - dispatchModel({ type: 'undo' })} icon="icon undo"> - Undo - - dispatchModel({ type: 'redo' })} icon="icon redo"> - Redo - - - ); -} diff --git a/packages/web/src/charts/DataChart.js b/packages/web/src/charts/DataChart.js deleted file mode 100644 index 6afcf36dc..000000000 --- a/packages/web/src/charts/DataChart.js +++ /dev/null @@ -1,165 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import Chart from 'react-chartjs-2'; -import randomcolor from 'randomcolor'; -import styled from 'styled-components'; -import useDimensions from '../utility/useDimensions'; -import { useForm } from '../utility/FormProvider'; -import useTheme from '../theme/useTheme'; -import moment from 'moment'; - -const ChartWrapper = styled.div` - flex: 1; - overflow: hidden; -`; - -function getTimeAxis(labels) { - const res = []; - for (const label of labels) { - const parsed = moment(label); - if (!parsed.isValid()) return null; - const iso = parsed.toISOString(); - if (iso < '1850-01-01T00:00:00' || iso > '2150-01-01T00:00:00') return null; - res.push(parsed); - } - return res; -} - -function getLabels(labelValues, timeAxis, chartType) { - if (!timeAxis) return labelValues; - if (chartType === 'line') return timeAxis.map(x => x.toDate()); - return timeAxis.map(x => x.format('D. M. YYYY')); -} - -function getOptions(timeAxis, chartType) { - if (timeAxis && chartType === 'line') { - return { - scales: { - xAxes: [ - { - type: 'time', - distribution: 'linear', - - time: { - tooltipFormat: 'D. M. YYYY HH:mm', - displayFormats: { - millisecond: 'HH:mm:ss.SSS', - second: 'HH:mm:ss', - minute: 'HH:mm', - hour: 'D.M hA', - day: 'D. M.', - week: 'D. M. YYYY', - month: 'MM-YYYY', - quarter: '[Q]Q - YYYY', - year: 'YYYY', - }, - }, - }, - ], - }, - }; - } - return {}; -} - -function createChartData(freeData, labelColumn, dataColumns, colorSeed, chartType, dataColumnColors, theme) { - if (!freeData || !labelColumn || !dataColumns || !freeData.rows || dataColumns.length == 0) return [{}, {}]; - const colors = randomcolor({ - count: _.max([freeData.rows.length, dataColumns.length, 1]), - seed: colorSeed, - }); - let backgroundColor = null; - let borderColor = null; - const labelValues = freeData.rows.map(x => x[labelColumn]); - const timeAxis = getTimeAxis(labelValues); - const labels = getLabels(labelValues, timeAxis, chartType); - const res = { - labels, - datasets: dataColumns.map((dataColumn, columnIndex) => { - if (chartType == 'line' || chartType == 'bar') { - const color = dataColumnColors[dataColumn]; - if (color) { - backgroundColor = theme.main_palettes[color][4] + '80'; - borderColor = theme.main_palettes[color][7]; - } else { - backgroundColor = colors[columnIndex] + '80'; - borderColor = colors[columnIndex]; - } - } else { - backgroundColor = colors; - } - - return { - label: dataColumn, - data: freeData.rows.map(row => row[dataColumn]), - backgroundColor, - borderColor, - borderWidth: 1, - }; - }), - }; - - const options = getOptions(timeAxis, chartType); - return [res, options]; -} - -export function extractDataColumns(values) { - const dataColumns = []; - for (const key in values) { - if (key.startsWith('dataColumn_') && values[key]) { - dataColumns.push(key.substring('dataColumn_'.length)); - } - } - return dataColumns; -} -export function extractDataColumnColors(values, dataColumns) { - const res = {}; - for (const column of dataColumns) { - const color = values[`dataColumnColor_${column}`]; - if (color) res[column] = color; - } - return res; -} - -export default function DataChart({ data }) { - const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions(); - const { values } = useForm(); - const theme = useTheme(); - - const { labelColumn } = values; - const dataColumns = extractDataColumns(values); - const dataColumnColors = extractDataColumnColors(values, dataColumns); - const [chartData, options] = createChartData( - data, - labelColumn, - dataColumns, - values.colorSeed || '5', - values.chartType, - dataColumnColors, - theme - ); - - return ( - - - - ); -} diff --git a/packages/web/src/charts/chartDataLoader.ts b/packages/web/src/charts/chartDataLoader.ts deleted file mode 100644 index 7ee23cadd..000000000 --- a/packages/web/src/charts/chartDataLoader.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { dumpSqlSelect, Select } from 'dbgate-sqltree'; -import { EngineDriver } from 'dbgate-types'; -import axios from '../utility/axios'; -import _ from 'lodash'; -import { extractDataColumns } from './DataChart'; - -export async function loadChartStructure(driver: EngineDriver, conid, database, sql) { - const select: Select = { - commandType: 'select', - selectAll: true, - topRecords: 1, - from: { - subQueryString: sql, - alias: 'subq', - }, - }; - - const dmp = driver.createDumper(); - dumpSqlSelect(dmp, select); - const resp = await axios.post('database-connections/query-data', { conid, database, sql: dmp.s }); - if (resp.data.errorMessage) throw new Error(resp.data.errorMessage); - return resp.data.columns.map(x => x.columnName); -} - -export async function loadChartData(driver: EngineDriver, conid, database, sql, config) { - const dataColumns = extractDataColumns(config); - const { labelColumn, truncateFrom, truncateLimit, showRelativeValues } = config; - if (!labelColumn || !dataColumns || dataColumns.length == 0) return null; - - const select: Select = { - commandType: 'select', - - columns: [ - { - exprType: 'column', - source: { alias: 'subq' }, - columnName: labelColumn, - alias: labelColumn, - }, - // @ts-ignore - ...dataColumns.map(columnName => ({ - exprType: 'call', - func: 'SUM', - args: [ - { - exprType: 'column', - columnName, - source: { alias: 'subq' }, - }, - ], - alias: columnName, - })), - ], - topRecords: truncateLimit || 100, - from: { - subQueryString: sql, - alias: 'subq', - }, - groupBy: [ - { - exprType: 'column', - source: { alias: 'subq' }, - columnName: labelColumn, - }, - ], - orderBy: [ - { - exprType: 'column', - source: { alias: 'subq' }, - columnName: labelColumn, - direction: truncateFrom == 'end' ? 'DESC' : 'ASC', - }, - ], - }; - - const dmp = driver.createDumper(); - dumpSqlSelect(dmp, select); - const resp = await axios.post('database-connections/query-data', { conid, database, sql: dmp.s }); - let { rows, columns } = resp.data; - if (truncateFrom == 'end' && rows) { - rows = _.reverse([...rows]); - } - if (showRelativeValues) { - const maxValues = dataColumns.map(col => _.max(rows.map(row => row[col]))); - for (const [col, max] of _.zip(dataColumns, maxValues)) { - if (!max) continue; - if (!_.isNumber(max)) continue; - if (!(max > 0)) continue; - rows = rows.map(row => ({ - ...row, - [col]: (row[col] / max) * 100, - })); - // columns = columns.map((x) => { - // if (x.columnName == col) { - // return { columnName: `${col} %` }; - // } - // return x; - // }); - } - } - return { - columns, - rows, - }; -} diff --git a/packages/web/src/datagrid/ChangeSetGrider.ts b/packages/web/src/datagrid/ChangeSetGrider.ts deleted file mode 100644 index 6aac275b6..000000000 --- a/packages/web/src/datagrid/ChangeSetGrider.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { - ChangeSet, - changeSetContainsChanges, - changeSetInsertNewRow, - createChangeSet, - deleteChangeSetRows, - findExistingChangeSetItem, - getChangeSetInsertedRows, - GridDisplay, - revertChangeSetRowChanges, - setChangeSetValue, -} from 'dbgate-datalib'; -import Grider, { GriderRowStatus } from './Grider'; - -export default class ChangeSetGrider extends Grider { - public insertedRows: any[]; - public changeSet: ChangeSet; - public setChangeSet: Function; - private rowCacheIndexes: Set; - private rowDataCache; - private rowStatusCache; - private rowDefinitionsCache; - private batchChangeSet: ChangeSet; - - constructor(public sourceRows: any[], public changeSetState, public dispatchChangeSet, public display: GridDisplay) { - super(); - this.changeSet = changeSetState && changeSetState.value; - this.insertedRows = getChangeSetInsertedRows(this.changeSet, display.baseTable); - this.setChangeSet = value => dispatchChangeSet({ type: 'set', value }); - this.rowCacheIndexes = new Set(); - this.rowDataCache = {}; - this.rowStatusCache = {}; - this.rowDefinitionsCache = {}; - this.batchChangeSet = null; - } - - getRowSource(index: number) { - if (index < this.sourceRows.length) return this.sourceRows[index]; - return null; - } - - getInsertedRowIndex(index) { - return index >= this.sourceRows.length ? index - this.sourceRows.length : null; - } - - requireRowCache(index: number) { - if (this.rowCacheIndexes.has(index)) return; - const row = this.getRowSource(index); - const insertedRowIndex = this.getInsertedRowIndex(index); - const rowDefinition = this.display.getChangeSetRow(row, insertedRowIndex); - const [matchedField, matchedChangeSetItem] = findExistingChangeSetItem(this.changeSet, rowDefinition); - const rowUpdated = matchedChangeSetItem ? { ...row, ...matchedChangeSetItem.fields } : row; - let status = 'regular'; - if (matchedChangeSetItem && matchedField == 'updates') status = 'updated'; - if (matchedField == 'deletes') status = 'deleted'; - if (insertedRowIndex != null) status = 'inserted'; - const rowStatus = { - status, - modifiedFields: - matchedChangeSetItem && matchedChangeSetItem.fields ? new Set(Object.keys(matchedChangeSetItem.fields)) : null, - }; - this.rowDataCache[index] = rowUpdated; - this.rowStatusCache[index] = rowStatus; - this.rowDefinitionsCache[index] = rowDefinition; - this.rowCacheIndexes.add(index); - } - - get editable() { - return this.display.editable; - } - - get canInsert() { - return !!this.display.baseTable; - } - - getRowData(index: number) { - this.requireRowCache(index); - return this.rowDataCache[index]; - } - - getRowStatus(index): GriderRowStatus { - this.requireRowCache(index); - return this.rowStatusCache[index]; - } - - get rowCount() { - return this.sourceRows.length + this.insertedRows.length; - } - - applyModification(changeSetReducer) { - if (this.batchChangeSet) { - this.batchChangeSet = changeSetReducer(this.batchChangeSet); - } else { - this.setChangeSet(changeSetReducer(this.changeSet)); - } - } - - setCellValue(index: number, uniqueName: string, value: any) { - const row = this.getRowSource(index); - const definition = this.display.getChangeSetField(row, uniqueName, this.getInsertedRowIndex(index)); - this.applyModification(chs => setChangeSetValue(chs, definition, value)); - } - - deleteRow(index: number) { - this.requireRowCache(index); - this.applyModification(chs => deleteChangeSetRows(chs, this.rowDefinitionsCache[index])); - } - - get rowCountInUpdate() { - if (this.batchChangeSet) { - const newRows = getChangeSetInsertedRows(this.batchChangeSet, this.display.baseTable); - return this.sourceRows.length + newRows.length; - } else { - return this.rowCount; - } - } - - insertRow(): number { - const res = this.rowCountInUpdate; - this.applyModification(chs => changeSetInsertNewRow(chs, this.display.baseTable)); - return res; - } - - beginUpdate() { - this.batchChangeSet = this.changeSet; - } - endUpdate() { - this.setChangeSet(this.batchChangeSet); - this.batchChangeSet = null; - } - - revertRowChanges(index: number) { - this.requireRowCache(index); - this.applyModification(chs => revertChangeSetRowChanges(chs, this.rowDefinitionsCache[index])); - } - revertAllChanges() { - this.applyModification(chs => createChangeSet()); - } - undo() { - this.dispatchChangeSet({ type: 'undo' }); - } - redo() { - this.dispatchChangeSet({ type: 'redo' }); - } - get canUndo() { - return this.changeSetState.canUndo; - } - get canRedo() { - return this.changeSetState.canRedo; - } - get containsChanges() { - return changeSetContainsChanges(this.changeSet); - } - get disableLoadNextPage() { - return this.insertedRows.length > 0; - } - - static factory({ sourceRows, changeSetState, dispatchChangeSet, display }): ChangeSetGrider { - return new ChangeSetGrider(sourceRows, changeSetState, dispatchChangeSet, display); - } - static factoryDeps({ sourceRows, changeSetState, dispatchChangeSet, display }) { - return [sourceRows, changeSetState ? changeSetState.value : null, dispatchChangeSet, display]; - } -} diff --git a/packages/web/src/datagrid/ColumnHeaderControl.js b/packages/web/src/datagrid/ColumnHeaderControl.js deleted file mode 100644 index f0f121409..000000000 --- a/packages/web/src/datagrid/ColumnHeaderControl.js +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import ColumnLabel from './ColumnLabel'; -import DropDownButton from '../widgets/DropDownButton'; -import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu'; -import { useSplitterDrag } from '../widgets/Splitter'; -import { isTypeDateTime } from 'dbgate-tools'; -import { openDatabaseObjectDetail } from '../appobj/DatabaseObjectAppObject'; -import { useSetOpenedTabs } from '../utility/globalState'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; -import useOpenNewTab from '../utility/useOpenNewTab'; - -const HeaderDiv = styled.div` - display: flex; - flex-wrap: nowrap; -`; - -const LabelDiv = styled.div` - flex: 1; - min-width: 10px; - // padding-left: 2px; - padding: 2px; - margin: auto; - white-space: nowrap; -`; - -const IconWrapper = styled.span` - margin-left: 3px; -`; - -const ResizeHandle = styled.div` - background-color: ${props => props.theme.border}; - width: 2px; - cursor: col-resize; - z-index: 1; -`; - -const GroupingLabel = styled.span` - color: green; - white-space: nowrap; -`; - -export default function ColumnHeaderControl({ - column, - setSort, - onResize, - order, - setGrouping, - grouping, - conid, - database, -}) { - const onResizeDown = useSplitterDrag('clientX', onResize); - const { foreignKey } = column; - const openNewTab = useOpenNewTab(); - const theme = useTheme(); - - const openReferencedTable = () => { - openDatabaseObjectDetail(openNewTab, 'TableDataTab', null, { - schemaName: foreignKey.refSchemaName, - pureName: foreignKey.refTableName, - conid, - database, - objectTypeField: 'tables', - }); - // openNewTab(setOpenedTabs, { - // title: foreignKey.refTableName, - // tooltip, - // icon: sqlTemplate ? 'sql.svg' : icons[objectTypeField], - // tabComponent: sqlTemplate ? 'QueryTab' : tabComponent, - // props: { - // schemaName, - // pureName, - // conid, - // database, - // objectTypeField, - // initialArgs: sqlTemplate ? { sqlTemplate } : null, - // }, - // }); - }; - return ( - - - {grouping && ( - {grouping == 'COUNT DISTINCT' ? 'distinct' : grouping.toLowerCase()}: - )} - - - {order == 'ASC' && ( - - - - )} - {order == 'DESC' && ( - - - - )} - - {setSort && ( - - setSort('ASC')}>Sort ascending - setSort('DESC')}>Sort descending - - {foreignKey && ( - - Open table {foreignKey.refTableName} - - )} - {foreignKey && } - setGrouping('GROUP')}>Group by - setGrouping('MAX')}>MAX - setGrouping('MIN')}>MIN - setGrouping('SUM')}>SUM - setGrouping('AVG')}>AVG - setGrouping('COUNT')}>COUNT - setGrouping('COUNT DISTINCT')}>COUNT DISTINCT - {isTypeDateTime(column.dataType) && ( - <> - - setGrouping('GROUP:YEAR')}>Group by YEAR - setGrouping('GROUP:MONTH')}>Group by MONTH - setGrouping('GROUP:DAY')}>Group by DAY - {/* setGrouping('GROUP:HOUR')}>Group by HOUR - setGrouping('GROUP:MINUTE')}>Group by MINUTE */} - - )} - - )} - - - ); -} diff --git a/packages/web/src/datagrid/ColumnLabel.js b/packages/web/src/datagrid/ColumnLabel.js deleted file mode 100644 index 89c6eb953..000000000 --- a/packages/web/src/datagrid/ColumnLabel.js +++ /dev/null @@ -1,35 +0,0 @@ -//@ts-nocheck - -import React from 'react'; -import styled from 'styled-components'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; - -const Label = styled.span` - font-weight: ${props => (props.notNull ? 'bold' : 'normal')}; - white-space: nowrap; -`; -const ExtInfoWrap = styled.span` - font-weight: normal; - margin-left: 5px; - color: ${props => props.theme.left_font3}; -`; - -export function getColumnIcon(column, forceIcon = false) { - if (column.autoIncrement) return 'img autoincrement'; - if (column.foreignKey) return 'img foreign-key'; - if (forceIcon) return 'img column'; - return null; -} - -/** @param column {import('dbgate-datalib').DisplayColumn|import('dbgate-types').ColumnInfo} */ -export default function ColumnLabel(column) { - const icon = getColumnIcon(column, column.forceIcon); - const theme = useTheme(); - return ( - - ); -} diff --git a/packages/web/src/datagrid/ColumnManager.js b/packages/web/src/datagrid/ColumnManager.js deleted file mode 100644 index 1ae61b112..000000000 --- a/packages/web/src/datagrid/ColumnManager.js +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import ColumnLabel from './ColumnLabel'; -import { filterName } from 'dbgate-datalib'; -import { ExpandIcon } from '../icons'; -import InlineButton from '../widgets/InlineButton'; -import { ManagerInnerContainer } from './ManagerStyles'; -import SearchInput from '../widgets/SearchInput'; -import useTheme from '../theme/useTheme'; - -const Wrapper = styled.div``; - -const Row = styled.div` - margin-left: 5px; - margin-right: 5px; - cursor: pointer; - white-space: nowrap; - &:hover { - background-color: ${props => props.theme.manager_background_blue[1]}; - } -`; - -const SearchBoxWrapper = styled.div` - display: flex; - margin-bottom: 5px; -`; - -const Button = styled.button` - // -webkit-appearance: none; - // -moz-appearance: none; - // appearance: none; - // width: 50px; -`; - -/** - * @param {object} props - * @param {import('dbgate-datalib').GridDisplay} props.display - * @param {import('dbgate-datalib').DisplayColumn} props.column - */ -function ColumnManagerRow(props) { - const { display, column } = props; - const [isHover, setIsHover] = React.useState(false); - const theme = useTheme(); - return ( - setIsHover(true)} - onMouseLeave={() => setIsHover(false)} - theme={theme} - onClick={e => { - // @ts-ignore - if (e.target.closest('.expandColumnIcon')) return; - display.focusColumn(column.uniqueName); - }} - > - display.toggleExpandedColumn(column.uniqueName)} - /> - display.setColumnVisibility(column.uniquePath, !column.isChecked)} - > - - - ); -} - -/** @param props {import('./types').DataGridProps} */ -export default function ColumnManager(props) { - const { display } = props; - const [columnFilter, setColumnFilter] = React.useState(''); - - return ( - <> - - - display.hideAllColumns()}>Hide - display.showAllColumns()}>Show - - - {display - .getColumns(columnFilter) - .filter(column => filterName(columnFilter, column.columnName)) - .map(column => ( - - ))} - - - ); -} diff --git a/packages/web/src/datagrid/DataFilterControl.js b/packages/web/src/datagrid/DataFilterControl.js deleted file mode 100644 index 8a09d4191..000000000 --- a/packages/web/src/datagrid/DataFilterControl.js +++ /dev/null @@ -1,501 +0,0 @@ -// @ts-nocheck -import React from 'react'; -import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu'; -import styled from 'styled-components'; -import keycodes from '../utility/keycodes'; -import { parseFilter, createMultiLineFilter } from 'dbgate-filterparser'; -import InlineButton from '../widgets/InlineButton'; -import useShowModal from '../modals/showModal'; -import FilterMultipleValuesModal from '../modals/FilterMultipleValuesModal'; -import SetFilterModal from '../modals/SetFilterModal'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; -import { useShowMenu } from '../modals/showMenu'; -// import { $ } from '../../Utility/jquery'; -// import autobind from 'autobind-decorator'; -// import * as React from 'react'; - -// import { createMultiLineFilter } from '../../DataLib/FilterTools'; -// import { ModalDialog } from '../Dialogs'; -// import { FilterDialog } from '../Dialogs/FilterDialog'; -// import { FilterMultipleValuesDialog } from '../Dialogs/FilterMultipleValuesDialog'; -// import { IconSpan } from '../Navigation/NavUtils'; -// import { KeyCodes } from '../ReactDataGrid/KeyCodes'; -// import { DropDownMenu, DropDownMenuDivider, DropDownMenuItem, DropDownSubmenuItem } from './DropDownMenu'; -// import { FilterParserType } from '../../SwaggerClients'; -// import { IFilterHolder } from '../CommonControls'; -// import { GrayFilterIcon } from '../Icons'; - -// export interface IDataFilterControlProps { -// filterType: FilterParserType; -// getFilter: Function; -// setFilter: Function; -// width: number; -// onControlKey?: Function; -// isReadOnly?: boolean; -// inputElementId?: string; -// } - -const FilterDiv = styled.div` - display: flex; -`; -const FilterInput = styled.input` - flex: 1; - min-width: 10px; - background-color: ${props => - props.state == 'ok' - ? props.theme.input_background_green[1] - : props.state == 'error' - ? props.theme.input_background_red[1] - : props.theme.input_background}; -`; -// const FilterButton = styled.button` -// color: gray; -// `; - -function DropDownContent({ filterType, setFilter, filterMultipleValues, openFilterWindow }) { - switch (filterType) { - case 'number': - return ( - <> - setFilter('')}>Clear Filter - filterMultipleValues()}>Filter multiple values - openFilterWindow('=')}>Equals... - openFilterWindow('<>')}>Does Not Equal... - setFilter('NULL')}>Is Null - setFilter('NOT NULL')}>Is Not Null - openFilterWindow('>')}>Greater Than... - openFilterWindow('>=')}>Greater Than Or Equal To... - openFilterWindow('<')}>Less Than... - openFilterWindow('<=')}>Less Than Or Equal To... - - ); - case 'logical': - return ( - <> - setFilter('')}>Clear Filter - filterMultipleValues()}>Filter multiple values - setFilter('NULL')}>Is Null - setFilter('NOT NULL')}>Is Not Null - setFilter('TRUE')}>Is True - setFilter('FALSE')}>Is False - setFilter('TRUE, NULL')}>Is True or NULL - setFilter('FALSE, NULL')}>Is False or NULL - - ); - case 'datetime': - return ( - <> - setFilter('')}>Clear Filter - filterMultipleValues()}>Filter multiple values - setFilter('NULL')}>Is Null - setFilter('NOT NULL')}>Is Not Null - - - - openFilterWindow('<=')}>Before... - openFilterWindow('>=')}>After... - openFilterWindow('>=;<=')}>Between... - - - - setFilter('TOMORROW')}>Tomorrow - setFilter('TODAY')}>Today - setFilter('YESTERDAY')}>Yesterday - - - - setFilter('NEXT WEEK')}>Next Week - setFilter('THIS WEEK')}>This Week - setFilter('LAST WEEK')}>Last Week - - - - setFilter('NEXT MONTH')}>Next Month - setFilter('THIS MONTH')}>This Month - setFilter('LAST MONTH')}>Last Month - - - - setFilter('NEXT YEAR')}>Next Year - setFilter('THIS YEAR')}>This Year - setFilter('LAST YEAR')}>Last Year - - - - {/* - setFilter('JAN')}>January - setFilter('FEB')}>February - setFilter('MAR')}>March - setFilter('APR')}>April - setFilter('JUN')}>June - setFilter('JUL')}>July - setFilter('AUG')}>August - setFilter('SEP')}>September - setFilter('OCT')}>October - setFilter('NOV')}>November - setFilter('DEC')}>December - - - - setFilter('MON')}>Monday - setFilter('TUE')}>Tuesday - setFilter('WED')}>Wednesday - setFilter('THU')}>Thursday - setFilter('FRI')}>Friday - setFilter('SAT')}>Saturday - setFilter('SUN')}>Sunday - */} - - ); - case 'string': - return ( - <> - setFilter('')}>Clear Filter - filterMultipleValues()}>Filter multiple values - - openFilterWindow('=')}>Equals... - openFilterWindow('<>')}>Does Not Equal... - setFilter('NULL')}>Is Null - setFilter('NOT NULL')}>Is Not Null - setFilter('EMPTY, NULL')}>Is Empty Or Null - setFilter('NOT EMPTY NOT NULL')}>Has Not Empty Value - - - - openFilterWindow('+')}>Contains... - openFilterWindow('~')}>Does Not Contain... - openFilterWindow('^')}>Begins With... - openFilterWindow('!^')}>Does Not Begin With... - openFilterWindow('$')}>Ends With... - openFilterWindow('!$')}>Does Not End With... - - ); - } -} - -export default function DataFilterControl({ - isReadOnly = false, - filterType, - filter, - setFilter, - focusIndex = 0, - onFocusGrid = undefined, -}) { - const showModal = useShowModal(); - const showMenu = useShowMenu(); - const theme = useTheme(); - const [filterState, setFilterState] = React.useState('empty'); - const setFilterText = filter => { - setFilter(filter); - editorRef.current.value = filter || ''; - updateFilterState(); - }; - const applyFilter = () => { - if ((filter || '') == (editorRef.current.value || '')) return; - setFilter(editorRef.current.value); - }; - const filterMultipleValues = () => { - showModal(modalState => ( - setFilterText(createMultiLineFilter(mode, text))} - /> - )); - }; - const openFilterWindow = operator => { - showModal(modalState => ( - setFilterText(text)} - condition1={operator} - /> - )); - }; - const buttonRef = React.useRef(); - const editorRef = React.useRef(); - - React.useEffect(() => { - if (focusIndex) editorRef.current.focus(); - }, [focusIndex]); - - const handleKeyDown = ev => { - if (isReadOnly) return; - if (ev.keyCode == keycodes.enter) { - applyFilter(); - } - if (ev.keyCode == keycodes.escape) { - setFilterText(''); - } - if (ev.keyCode == keycodes.downArrow) { - if (onFocusGrid) onFocusGrid(); - // ev.stopPropagation(); - ev.preventDefault(); - } - // if (ev.keyCode == KeyCodes.DownArrow || ev.keyCode == KeyCodes.UpArrow) { - // if (this.props.onControlKey) this.props.onControlKey(ev.keyCode); - // } - }; - - const updateFilterState = () => { - const value = editorRef.current.value; - try { - if (value) { - parseFilter(value, filterType); - setFilterState('ok'); - } else { - setFilterState('empty'); - } - } catch (err) { - // console.log('PARSE ERROR', err); - setFilterState('error'); - } - }; - - React.useEffect(() => { - editorRef.current.value = filter || ''; - updateFilterState(); - }, [filter]); - - const handleShowMenu = () => { - const rect = buttonRef.current.getBoundingClientRect(); - showMenu( - rect.left, - rect.bottom, - - ); - }; - - function handlePaste(event) { - var pastedText = undefined; - // @ts-ignore - if (window.clipboardData && window.clipboardData.getData) { - // IE - // @ts-ignore - pastedText = window.clipboardData.getData('Text'); - } else if (event.clipboardData && event.clipboardData.getData) { - pastedText = event.clipboardData.getData('text/plain'); - } - if (pastedText && pastedText.includes('\n')) { - event.preventDefault(); - setFilterText(createMultiLineFilter('is', pastedText)); - } - } - - return ( - - - - - - - ); -} -// domEditor: Element; - -// @autobind -// applyFilter() { -// this.props.setFilter($(this.domEditor).val()); -// } - -// @autobind -// clearFilter() { -// $(this.domEditor).val(''); -// this.applyFilter(); -// } - -// setFilter(value: string) { -// $(this.domEditor).val(value); -// this.applyFilter(); -// return false; -// } - -// render() { -// let dropDownContent = null; - -// let filterIconSpan = ; -// //filterIconSpan = null; - -// if (this.props.filterType == 'Number') { -// dropDownContent = -// this.setFilter('')}>Clear Filter -// this.filterMultipleValues()}>Filter multiple values -// this.openFilterWindow('=')}>Equals... -// this.openFilterWindow('<>')}>Does Not Equal... -// this.setFilter('NULL')}>Is Null -// this.setFilter('NOT NULL')}>Is Not Null -// this.openFilterWindow('>')}>Greater Than... -// this.openFilterWindow('>=')}>Greater Than Or Equal To... -// this.openFilterWindow('<')}>Less Than... -// this.openFilterWindow('<=')}>Less Than Or Equal To... -// ; -// } - -// if (this.props.filterType == 'Logical') { -// dropDownContent = -// this.setFilter('')}>Clear Filter -// this.filterMultipleValues()}>Filter multiple values -// this.setFilter('NULL')}>Is Null -// this.setFilter('NOT NULL')}>Is Not Null -// this.setFilter('TRUE')}>Is True -// this.setFilter('FALSE')}>Is False -// this.setFilter('TRUE, NULL')}>Is True or NULL -// this.setFilter('FALSE, NULL')}>Is False or NULL -// ; -// } - -// if (this.props.filterType == 'DateTime') { -// dropDownContent = -// this.setFilter('')}>Clear Filter -// this.filterMultipleValues()}>Filter multiple values -// this.setFilter('NULL')}>Is Null -// this.setFilter('NOT NULL')}>Is Not Null - -// - -// this.openFilterWindow('<=')}>Before... -// this.openFilterWindow('>=')}>After... -// this.openFilterWindow('>=;<=')}>Between... - -// - -// this.setFilter('TOMORROW')}>Tomorrow -// this.setFilter('TODAY')}>Today -// this.setFilter('YESTERDAY')}>Yesterday - -// - -// this.setFilter('NEXT WEEK')}>Next Week -// this.setFilter('THIS WEEK')}>This Week -// this.setFilter('LAST WEEK')}>Last Week - -// - -// this.setFilter('NEXT MONTH')}>Next Month -// this.setFilter('THIS MONTH')}>This Month -// this.setFilter('LAST MONTH')}>Last Month - -// - -// this.setFilter('NEXT YEAR')}>Next Year -// this.setFilter('THIS YEAR')}>This Year -// this.setFilter('LAST YEAR')}>Last Year - -// - -// - -// this.setFilter('JAN')}>January -// this.setFilter('FEB')}>February -// this.setFilter('MAR')}>March -// this.setFilter('APR')}>April -// this.setFilter('JUN')}>June -// this.setFilter('JUL')}>July -// this.setFilter('AUG')}>August -// this.setFilter('SEP')}>September -// this.setFilter('OCT')}>October -// this.setFilter('NOV')}>November -// this.setFilter('DEC')}>December - -// - -// this.setFilter('MON')}>Monday -// this.setFilter('TUE')}>Tuesday -// this.setFilter('WED')}>Wednesday -// this.setFilter('THU')}>Thursday -// this.setFilter('FRI')}>Friday -// this.setFilter('SAT')}>Saturday -// this.setFilter('SUN')}>Sunday - -// -// ; -// } - -// if (this.props.filterType == 'String') { -// dropDownContent = -// this.setFilter('')}>Clear Filter -// this.filterMultipleValues()}>Filter multiple values - -// this.openFilterWindow('=')}>Equals... -// this.openFilterWindow('<>')}>Does Not Equal... -// this.setFilter('NULL')}>Is Null -// this.setFilter('NOT NULL')}>Is Not Null -// this.setFilter('EMPTY, NULL')}>Is Empty Or Null -// this.setFilter('NOT EMPTY NOT NULL')}>Has Not Empty Value - -// - -// this.openFilterWindow('+')}>Contains... -// this.openFilterWindow('~')}>Does Not Contain... -// this.openFilterWindow('^')}>Begins With... -// this.openFilterWindow('!^')}>Does Not Begin With... -// this.openFilterWindow('$')}>Ends With... -// this.openFilterWindow('!$')}>Does Not End With... -// ; -// } - -// if (this.props.isReadOnly) { -// dropDownContent = ; -// } - -// return
-// this.setDomEditor(x)} onKeyDown={this.editorKeyDown} placeholder='Search' > - -// {dropDownContent} -//
; -// } - -// async filterMultipleValues() { -// let result = await ModalDialog.run(); -// if (!result) return; -// let { mode, text } = result; -// let filter = createMultiLineFilter(mode, text); -// this.setFilter(filter); -// } - -// openFilterWindow(selectedOperator: string) { -// FilterDialog.runFilter(this, this.props.filterType, selectedOperator); -// return false; -// } - -// setDomEditor(editor) { -// this.domEditor = editor; -// $(editor).val(this.props.getFilter()); -// } - -// @autobind -// editorKeyDown(ev) { -// if (this.props.isReadOnly) return; -// if (ev.keyCode == KeyCodes.Enter) { -// this.applyFilter(); -// } -// if (ev.keyCode == KeyCodes.Escape) { -// this.clearFilter(); -// } -// if (ev.keyCode == KeyCodes.DownArrow || ev.keyCode == KeyCodes.UpArrow) { -// if (this.props.onControlKey) this.props.onControlKey(ev.keyCode); -// } -// } - -// focus() { -// $(this.domEditor).focus(); -// } -// } diff --git a/packages/web/src/datagrid/DataGrid.js b/packages/web/src/datagrid/DataGrid.js deleted file mode 100644 index d8cca115a..000000000 --- a/packages/web/src/datagrid/DataGrid.js +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import ColumnManager from './ColumnManager'; -import FormViewFilters from '../formview/FormViewFilters'; - -import ReferenceManager from './ReferenceManager'; -import { HorizontalSplitter } from '../widgets/Splitter'; -import WidgetColumnBar, { WidgetColumnBarItem } from '../widgets/WidgetColumnBar'; -import CellDataView from '../celldata/CellDataView'; -import useTheme from '../theme/useTheme'; - -const LeftContainer = styled.div` - background-color: ${props => props.theme.manager_background}; - display: flex; - flex: 1; -`; - -const DataGridContainer = styled.div` - position: relative; - flex-grow: 1; -`; - -export default function DataGrid(props) { - const { GridCore, FormView, formDisplay } = props; - const theme = useTheme(); - const [managerSize, setManagerSize] = React.useState(0); - const [selection, setSelection] = React.useState([]); - const [formSelection, setFormSelection] = React.useState(null); - const [grider, setGrider] = React.useState(null); - const [collapsedWidgets, setCollapsedWidgets] = React.useState([]); - // const [formViewData, setFormViewData] = React.useState(null); - const isFormView = !!(formDisplay && formDisplay.config && formDisplay.config.isFormView); - - return ( - - - - {!isFormView && ( - - - - )} - {isFormView && ( - - - - )} - {props.showReferences && props.display.hasReferences && ( - - - - )} - - {isFormView ? ( - - ) : ( - - )} - - - - - - {isFormView ? ( - - ) : ( - - )} - - - ); -} diff --git a/packages/web/src/datagrid/DataGridContextMenu.js b/packages/web/src/datagrid/DataGridContextMenu.js deleted file mode 100644 index f95dbff32..000000000 --- a/packages/web/src/datagrid/DataGridContextMenu.js +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu'; - -export default function DataGridContextMenu({ - copy, - revertRowChanges, - deleteSelectedRows, - insertNewRow, - setNull, - reload, - exportGrid, - filterSelectedValue, - openQuery, - openFreeTable, - openChartSelection, - openActiveChart, - switchToForm, -}) { - return ( - <> - {!!reload && ( - - Reload - - )} - {!!reload && } - - Copy - - {revertRowChanges && ( - - Revert row changes - - )} - {deleteSelectedRows && ( - - Delete selected rows - - )} - {insertNewRow && ( - - Insert new row - - )} - - {setNull && ( - - Set NULL - - )} - {exportGrid && Export} - {filterSelectedValue && ( - - Filter selected value - - )} - {openQuery && Open query} - Open selection in free table editor - Open chart from selection - {openActiveChart && Open active chart} - {!!switchToForm && ( - - Form view - - )} - - ); -} diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js deleted file mode 100644 index 933089ce7..000000000 --- a/packages/web/src/datagrid/DataGridCore.js +++ /dev/null @@ -1,1124 +0,0 @@ -import _ from 'lodash'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import styled from 'styled-components'; -import { HorizontalScrollBar, VerticalScrollBar } from './ScrollBars'; -import useDimensions from '../utility/useDimensions'; -import DataFilterControl from './DataFilterControl'; -import stableStringify from 'json-stable-stringify'; -import { getFilterType, getFilterValueExpression } from 'dbgate-filterparser'; -import { cellFromEvent, getCellRange, topLeftCell, isRegularCell, nullCell, emptyCellArray } from './selection'; -import keycodes from '../utility/keycodes'; -import DataGridRow from './DataGridRow'; -import { - countColumnSizes, - countVisibleRealColumns, - filterCellForRow, - filterCellsForRow, - cellIsSelected, -} from './gridutil'; -import { copyTextToClipboard } from '../utility/clipboard'; -import DataGridToolbar from './DataGridToolbar'; -// import usePropsCompare from '../utility/usePropsCompare'; -import ColumnHeaderControl from './ColumnHeaderControl'; -import InlineButton from '../widgets/InlineButton'; -import DataGridContextMenu from './DataGridContextMenu'; -import LoadingInfo from '../widgets/LoadingInfo'; -import ErrorInfo from '../widgets/ErrorInfo'; -import { useSetOpenedTabs } from '../utility/globalState'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; -import { useShowMenu } from '../modals/showMenu'; -import useOpenNewTab from '../utility/useOpenNewTab'; -import axios from '../utility/axios'; -import openReferenceForm from '../formview/openReferenceForm'; - -const GridContainer = styled.div` - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - user-select: none; -`; - -const Table = styled.table` - position: absolute; - left: 0; - top: 0; - bottom: 20px; - // right: 20px; - overflow: scroll; - border-collapse: collapse; - outline: none; -`; -const TableHead = styled.thead` - // display: block; - // width: 300px; -`; -const TableBody = styled.tbody` - // display: block; - // overflow: auto; - // height: 100px; -`; -const TableHeaderRow = styled.tr` - // height: 35px; -`; -const TableHeaderCell = styled.td` - // font-weight: bold; - border: 1px solid ${props => props.theme.border}; - // border-collapse: collapse; - text-align: left; - padding: 0; - // padding: 2px; - margin: 0; - background-color: ${props => props.theme.gridheader_background}; - overflow: hidden; -`; -const TableFilterCell = styled.td` - text-align: left; - overflow: hidden; - margin: 0; - padding: 0; -`; -const wheelRowCount = 5; -const FocusField = styled.input` - // visibility: hidden - position: absolute; - left: -1000px; - top: -1000px; -`; - -const RowCountLabel = styled.div` - position: absolute; - background-color: ${props => props.theme.gridbody_background_yellow[1]}; - right: 40px; - bottom: 20px; -`; - -/** @param props {import('./types').DataGridProps} */ -export default function DataGridCore(props) { - const { - display, - conid, - database, - tabVisible, - loadNextData, - errorMessage, - isLoadedAll, - loadedTime, - exportGrid, - openActiveChart, - allRowCount, - openQuery, - onSave, - isLoading, - grider, - onSelectionChanged, - frameSelection, - onKeyDown, - formViewAvailable, - } = props; - // console.log('RENDER GRID', display.baseTable.pureName); - const columns = React.useMemo(() => display.allColumns, [display]); - const openNewTab = useOpenNewTab(); - - // usePropsCompare(props); - - // console.log(`GRID, conid=${conid}, database=${database}, sql=${sql}`); - - const focusFieldRef = React.useRef(null); - - const [vScrollValueToSet, setvScrollValueToSet] = React.useState(); - const [vScrollValueToSetDate, setvScrollValueToSetDate] = React.useState(new Date()); - - const [hScrollValueToSet, sethScrollValueToSet] = React.useState(); - const [hScrollValueToSetDate, sethScrollValueToSetDate] = React.useState(new Date()); - - const [currentCell, setCurrentCell] = React.useState(topLeftCell); - const [selectedCells, setSelectedCells] = React.useState([topLeftCell]); - const [dragStartCell, setDragStartCell] = React.useState(nullCell); - const [shiftDragStartCell, setShiftDragStartCell] = React.useState(nullCell); - const [autofillDragStartCell, setAutofillDragStartCell] = React.useState(nullCell); - const [autofillSelectedCells, setAutofillSelectedCells] = React.useState(emptyCellArray); - const [focusFilterInputs, setFocusFilterInputs] = React.useState({}); - const showMenu = useShowMenu(); - - const autofillMarkerCell = React.useMemo( - () => - selectedCells && selectedCells.length > 0 && _.uniq(selectedCells.map(x => x[0])).length == 1 - ? [_.max(selectedCells.map(x => x[0])), _.max(selectedCells.map(x => x[1]))] - : null, - [selectedCells] - ); - - const [firstVisibleRowScrollIndex, setFirstVisibleRowScrollIndex] = React.useState(0); - const [firstVisibleColumnScrollIndex, setFirstVisibleColumnScrollIndex] = React.useState(0); - - const [headerRowRef, { height: rowHeight }] = useDimensions(); - const [tableBodyRef] = useDimensions(); - const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions(); - - const [inplaceEditorState, dispatchInsplaceEditor] = React.useReducer((state, action) => { - switch (action.type) { - case 'show': - if (!grider.editable) return {}; - return { - cell: action.cell, - text: action.text, - selectAll: action.selectAll, - }; - case 'close': { - const [row, col] = currentCell || []; - if (focusFieldRef.current) focusFieldRef.current.focus(); - // @ts-ignore - if (action.mode == 'enter' && row) setTimeout(() => moveCurrentCell(row + 1, col), 0); - if (action.mode == 'save') setTimeout(handleSave, 0); - return {}; - } - case 'shouldSave': { - return { - ...state, - shouldSave: true, - }; - } - } - return {}; - }, {}); - - // usePropsCompare({ loadedRows, columns, containerWidth, display }); - - const columnSizes = React.useMemo(() => countColumnSizes(grider, columns, containerWidth, display), [ - grider, - columns, - containerWidth, - display, - ]); - const headerColWidth = 40; - - // console.log('containerWidth', containerWidth); - - const gridScrollAreaHeight = containerHeight - 2 * rowHeight; - const gridScrollAreaWidth = containerWidth - columnSizes.frozenSize - headerColWidth - 32; - - const visibleRowCountUpperBound = Math.ceil(gridScrollAreaHeight / Math.floor(Math.max(1, rowHeight))); - const visibleRowCountLowerBound = Math.floor(gridScrollAreaHeight / Math.ceil(Math.max(1, rowHeight))); - // const visibleRowCountUpperBound = 20; - // const visibleRowCountLowerBound = 20; - // console.log('containerHeight', containerHeight); - // console.log('visibleRowCountUpperBound', visibleRowCountUpperBound); - // console.log('rowHeight', rowHeight); - - React.useEffect(() => { - if (tabVisible) { - if (focusFieldRef.current) focusFieldRef.current.focus(); - } - }, [tabVisible, focusFieldRef.current]); - - React.useEffect(() => { - if (onSelectionChanged) { - onSelectionChanged(getSelectedCellsPublished()); - } - }, [onSelectionChanged, selectedCells]); - - const maxScrollColumn = React.useMemo(() => { - let newColumn = columnSizes.scrollInView(0, columns.length - 1 - columnSizes.frozenCount, gridScrollAreaWidth); - return newColumn; - }, [columnSizes, gridScrollAreaWidth]); - - React.useEffect(() => { - if (props.onReferenceSourceChanged && (grider.rowCount > 0 || isLoadedAll)) { - props.onReferenceSourceChanged(getSelectedRowData(), loadedTime); - } - }, [props.onReferenceSourceChanged, selectedCells, props.refReloadToken, grider.getRowData(0)]); - - // usePropsCompare({ columnSizes, firstVisibleColumnScrollIndex, gridScrollAreaWidth, columns }); - - const visibleRealColumns = React.useMemo( - () => countVisibleRealColumns(columnSizes, firstVisibleColumnScrollIndex, gridScrollAreaWidth, columns), - [columnSizes, firstVisibleColumnScrollIndex, gridScrollAreaWidth, columns] - ); - - const realColumnUniqueNames = React.useMemo( - () => - _.range(columnSizes.realCount).map(realIndex => (columns[columnSizes.realToModel(realIndex)] || {}).uniqueName), - [columnSizes, columns] - ); - - React.useEffect(() => { - if (display && display.focusedColumn) { - const invMap = _.invert(realColumnUniqueNames); - const colIndex = invMap[display.focusedColumn]; - if (colIndex) { - scrollIntoView([null, colIndex]); - } - } - }, [display && display.focusedColumn]); - - React.useEffect(() => { - if (loadNextData && firstVisibleRowScrollIndex + visibleRowCountUpperBound >= grider.rowCount) { - loadNextData(); - } - }); - - React.useEffect(() => { - if (display.groupColumns && display.baseTable) { - props.onReferenceClick({ - schemaName: display.baseTable.schemaName, - pureName: display.baseTable.pureName, - columns: display.groupColumns.map(col => ({ - baseName: col, - refName: col, - dataType: _.get(display.baseTable && display.baseTable.columns.find(x => x.columnName == col), 'dataType'), - })), - }); - } - }, [display.baseTable, stableStringify(display && display.groupColumns)]); - - const theme = useTheme(); - - const rowCountInfo = React.useMemo(() => { - if (selectedCells.length > 1 && selectedCells.every(x => _.isNumber(x[0]) && _.isNumber(x[1]))) { - let sum = _.sumBy(selectedCells, cell => { - const row = grider.getRowData(cell[0]); - if (row) { - const colName = realColumnUniqueNames[cell[1]]; - if (colName) { - const data = row[colName]; - if (!data) return 0; - let num = +data; - if (_.isNaN(num)) return 0; - return num; - } - } - return 0; - }); - let count = selectedCells.length; - let rowCount = getSelectedRowData().length; - return `Rows: ${rowCount.toLocaleString()}, Count: ${count.toLocaleString()}, Sum:${sum.toLocaleString()}`; - } - if (allRowCount == null) return 'Loading row count...'; - return `Rows: ${allRowCount.toLocaleString()}`; - }, [selectedCells, allRowCount, grider, visibleRealColumns]); - - const handleSetFormView = React.useMemo( - () => - formViewAvailable && display.baseTable && display.baseTable.primaryKey - ? (rowData, column) => { - if (column) { - openReferenceForm(rowData, column, openNewTab, conid, database); - } else { - display.switchToFormView(rowData); - } - } - : null, - [formViewAvailable, display, openNewTab] - ); - - if (!columns || columns.length == 0) return ; - - if (errorMessage) { - return ; - } - - if (grider.errors && grider.errors.length > 0) { - return ( -
- {grider.errors.map((err, index) => ( - - ))} -
- ); - } - - const handleRowScroll = value => { - setFirstVisibleRowScrollIndex(value); - }; - - const handleColumnScroll = value => { - setFirstVisibleColumnScrollIndex(value); - }; - - const getSelectedFreeData = () => { - const columns = getSelectedColumns(); - const rows = getSelectedRowData().map(row => _.pickBy(row, (v, col) => columns.find(x => x.columnName == col))); - return { - structure: { - columns, - }, - rows, - }; - }; - - const handleOpenFreeTable = () => { - openNewTab( - { - title: 'Data #', - icon: 'img free-table', - tabComponent: 'FreeTableTab', - props: {}, - }, - { editor: getSelectedFreeData() } - ); - }; - - const handleOpenChart = () => { - openNewTab( - { - title: 'Chart #', - icon: 'img chart', - tabComponent: 'ChartTab', - props: {}, - }, - { - editor: { - data: getSelectedFreeData(), - config: { chartType: 'bar' }, - }, - } - ); - }; - - const handleContextMenu = event => { - event.preventDefault(); - showMenu( - event.pageX, - event.pageY, - display.reload() : null} - setNull={grider.editable ? setNull : null} - exportGrid={exportGrid} - filterSelectedValue={display.filterable ? filterSelectedValue : null} - openQuery={openQuery} - openFreeTable={handleOpenFreeTable} - openChartSelection={handleOpenChart} - openActiveChart={openActiveChart} - switchToForm={handleSwitchToFormView} - /> - ); - }; - - function handleGridMouseDown(event) { - if (event.target.closest('.buttonLike')) return; - if (event.target.closest('.resizeHandleControl')) return; - if (event.target.closest('input')) return; - - // event.target.closest('table').focus(); - event.preventDefault(); - if (focusFieldRef.current) focusFieldRef.current.focus(); - const cell = cellFromEvent(event); - - if (event.button == 2 && cell && cellIsSelected(cell[0], cell[1], selectedCells)) return; - - const autofill = event.target.closest('div.autofillHandleMarker'); - if (autofill) { - setAutofillDragStartCell(cell); - } else { - setCurrentCell(cell); - - if (event.ctrlKey) { - if (isRegularCell(cell)) { - if (selectedCells.find(x => x[0] == cell[0] && x[1] == cell[1])) { - setSelectedCells(selectedCells.filter(x => x[0] != cell[0] || x[1] != cell[1])); - } else { - setSelectedCells([...selectedCells, cell]); - } - } - } else { - setSelectedCells(getCellRange(cell, cell)); - setDragStartCell(cell); - - if (isRegularCell(cell) && !_.isEqual(cell, inplaceEditorState.cell) && _.isEqual(cell, currentCell)) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'show', cell, selectAll: true }); - } else if (!_.isEqual(cell, inplaceEditorState.cell)) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'close' }); - } - } - } - - if (display.focusedColumn) display.focusColumn(null); - } - - function handleCopy(event) { - if (event && event.target.localName == 'input') return; - if (event) event.preventDefault(); - copyToClipboard(); - } - - function setCellValue(cell, value) { - grider.setCellValue(cell[0], realColumnUniqueNames[cell[1]], value); - } - - function handlePaste(event) { - var pastedText = undefined; - // @ts-ignore - if (window.clipboardData && window.clipboardData.getData) { - // IE - // @ts-ignore - pastedText = window.clipboardData.getData('Text'); - } else if (event.clipboardData && event.clipboardData.getData) { - pastedText = event.clipboardData.getData('text/plain'); - } - event.preventDefault(); - grider.beginUpdate(); - const pasteRows = pastedText - .replace(/\r/g, '') - .split('\n') - .map(row => row.split('\t')); - const selectedRegular = cellsToRegularCells(selectedCells); - if (selectedRegular.length <= 1) { - const startRow = isRegularCell(currentCell) ? currentCell[0] : grider.rowCount; - const startCol = isRegularCell(currentCell) ? currentCell[1] : 0; - let rowIndex = startRow; - for (const rowData of pasteRows) { - if (rowIndex >= grider.rowCountInUpdate) { - grider.insertRow(); - } - let colIndex = startCol; - for (const cell of rowData) { - setCellValue([rowIndex, colIndex], cell == '(NULL)' ? null : cell); - colIndex += 1; - } - rowIndex += 1; - } - } - if (selectedRegular.length > 1) { - const startRow = _.min(selectedRegular.map(x => x[0])); - const startCol = _.min(selectedRegular.map(x => x[1])); - for (const cell of selectedRegular) { - const [rowIndex, colIndex] = cell; - const selectionRow = rowIndex - startRow; - const selectionCol = colIndex - startCol; - const pasteRow = pasteRows[selectionRow % pasteRows.length]; - const pasteCell = pasteRow[selectionCol % pasteRow.length]; - setCellValue(cell, pasteCell); - } - } - grider.endUpdate(); - } - - function setNull() { - grider.beginUpdate(); - selectedCells.filter(isRegularCell).forEach(cell => { - setCellValue(cell, null); - }); - grider.endUpdate(); - } - - function cellsToRegularCells(cells) { - cells = _.flatten( - cells.map(cell => { - if (cell[1] == 'header') { - return _.range(0, columnSizes.count).map(col => [cell[0], col]); - } - return [cell]; - }) - ); - cells = _.flatten( - cells.map(cell => { - if (cell[0] == 'header') { - return _.range(0, grider.rowCount).map(row => [row, cell[1]]); - } - return [cell]; - }) - ); - return cells.filter(isRegularCell); - } - - function copyToClipboard() { - const cells = cellsToRegularCells(selectedCells); - const rowIndexes = _.sortBy(_.uniq(cells.map(x => x[0]))); - const lines = rowIndexes.map(rowIndex => { - let colIndexes = _.sortBy(cells.filter(x => x[0] == rowIndex).map(x => x[1])); - const rowData = grider.getRowData(rowIndex); - if (!rowData) return ''; - const line = colIndexes - .map(col => realColumnUniqueNames[col]) - .map(col => (rowData[col] == null ? '(NULL)' : rowData[col])) - .join('\t'); - return line; - }); - const text = lines.join('\r\n'); - copyTextToClipboard(text); - } - - function handleGridMouseMove(event) { - if (autofillDragStartCell) { - const cell = cellFromEvent(event); - if (isRegularCell(cell) && (cell[0] == autofillDragStartCell[0] || cell[1] == autofillDragStartCell[1])) { - const autoFillStart = [selectedCells[0][0], _.min(selectedCells.map(x => x[1]))]; - // @ts-ignore - setAutofillSelectedCells(getCellRange(autoFillStart, cell)); - } - } else if (dragStartCell) { - const cell = cellFromEvent(event); - setCurrentCell(cell); - setSelectedCells(getCellRange(dragStartCell, cell)); - } - } - - function handleGridMouseUp(event) { - if (dragStartCell) { - const cell = cellFromEvent(event); - setCurrentCell(cell); - setSelectedCells(getCellRange(dragStartCell, cell)); - setDragStartCell(null); - } - if (autofillDragStartCell) { - const currentRowNumber = currentCell[0]; - if (_.isNumber(currentRowNumber)) { - const rowIndexes = _.uniq((autofillSelectedCells || []).map(x => x[0])).filter(x => x != currentRowNumber); - const colNames = selectedCells.map(cell => realColumnUniqueNames[cell[1]]); - const changeObject = _.pick(grider.getRowData(currentRowNumber), colNames); - grider.beginUpdate(); - for (const index of rowIndexes) grider.updateRow(index, changeObject); - grider.endUpdate(); - } - - setAutofillDragStartCell(null); - setAutofillSelectedCells([]); - setSelectedCells(autofillSelectedCells); - } - } - - function getSelectedRowIndexes() { - if (selectedCells.find(x => x[0] == 'header')) return _.range(0, grider.rowCount); - return _.uniq((selectedCells || []).map(x => x[0])).filter(x => _.isNumber(x)); - } - - function getSelectedColumnIndexes() { - if (selectedCells.find(x => x[1] == 'header')) return _.range(0, realColumnUniqueNames.length); - return _.uniq((selectedCells || []).map(x => x[1])).filter(x => _.isNumber(x)); - } - - function getSelectedCellsPublished() { - const regular = cellsToRegularCells(selectedCells); - // @ts-ignore - return regular - .map(cell => ({ - row: cell[0], - column: realColumnUniqueNames[cell[1]], - })) - .filter(x => x.column); - - // return regular.map((cell) => { - // const row = cell[0]; - // const column = realColumnUniqueNames[cell[1]]; - // let value = null; - // if (grider && column) { - // let rowData = grider.getRowData(row); - // if (rowData) value = rowData[column]; - // } - // return { - // row, - // column, - // value, - // }; - // }); - } - - function getSelectedRowData() { - return _.compact(getSelectedRowIndexes().map(index => grider.getRowData(index))); - } - - function getSelectedColumns() { - return _.compact( - getSelectedColumnIndexes().map(index => ({ - columnName: realColumnUniqueNames[index], - })) - ); - } - - function revertRowChanges() { - grider.beginUpdate(); - for (const index of getSelectedRowIndexes()) { - if (_.isNumber(index)) grider.revertRowChanges(index); - } - grider.endUpdate(); - } - - function filterSelectedValue() { - const flts = {}; - for (const cell of selectedCells) { - if (!isRegularCell(cell)) continue; - const modelIndex = columnSizes.realToModel(cell[1]); - const columnName = columns[modelIndex].uniqueName; - let value = grider.getRowData(cell[0])[columnName]; - let svalue = getFilterValueExpression(value, columns[modelIndex].dataType); - if (_.has(flts, columnName)) flts[columnName] += ',' + svalue; - else flts[columnName] = svalue; - } - - display.setFilters(flts); - } - - function deleteSelectedRows() { - grider.beginUpdate(); - for (const index of getSelectedRowIndexes()) { - if (_.isNumber(index)) grider.deleteRow(index); - } - grider.endUpdate(); - } - - function handleGridWheel(event) { - let newFirstVisibleRowScrollIndex = firstVisibleRowScrollIndex; - if (event.deltaY > 0) { - newFirstVisibleRowScrollIndex += wheelRowCount; - } - if (event.deltaY < 0) { - newFirstVisibleRowScrollIndex -= wheelRowCount; - } - let rowCount = grider.rowCount; - if (newFirstVisibleRowScrollIndex + visibleRowCountLowerBound > rowCount) { - newFirstVisibleRowScrollIndex = rowCount - visibleRowCountLowerBound + 1; - } - if (newFirstVisibleRowScrollIndex < 0) { - newFirstVisibleRowScrollIndex = 0; - } - setFirstVisibleRowScrollIndex(newFirstVisibleRowScrollIndex); - // @ts-ignore - setvScrollValueToSet(newFirstVisibleRowScrollIndex); - setvScrollValueToSetDate(new Date()); - } - - function undo() { - grider.undo(); - } - function redo() { - grider.redo(); - } - - function handleSave() { - if (inplaceEditorState.cell) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'shouldSave' }); - return; - } - if (onSave) onSave(); - } - - const insertNewRow = () => { - if (grider.canInsert) { - const rowIndex = grider.insertRow(); - const cell = [rowIndex, (currentCell && currentCell[1]) || 0]; - // @ts-ignore - setCurrentCell(cell); - // @ts-ignore - setSelectedCells([cell]); - scrollIntoView(cell); - } - }; - - const selectTopmostCell = uniquePath => { - const modelIndex = columns.findIndex(x => x.uniquePath == uniquePath); - const realIndex = columnSizes.modelToReal(modelIndex); - let cell = [firstVisibleRowScrollIndex, realIndex]; - // @ts-ignore - setCurrentCell(cell); - // @ts-ignore - setSelectedCells([cell]); - focusFieldRef.current.focus(); - }; - - function handleGridKeyDown(event) { - if (onKeyDown) { - onKeyDown(event); - } - - if (event.keyCode == keycodes.f5) { - event.preventDefault(); - display.reload(); - } - - if (event.keyCode == keycodes.f4) { - event.preventDefault(); - handleSwitchToFormView(); - } - - if (event.keyCode == keycodes.s && event.ctrlKey) { - event.preventDefault(); - handleSave(); - // this.saveAndFocus(); - } - - if (event.keyCode == keycodes.n0 && event.ctrlKey) { - event.preventDefault(); - setNull(); - } - - if (event.keyCode == keycodes.r && event.ctrlKey) { - event.preventDefault(); - revertRowChanges(); - } - - if (event.keyCode == keycodes.f && event.ctrlKey) { - event.preventDefault(); - filterSelectedValue(); - } - - if (event.keyCode == keycodes.z && event.ctrlKey) { - event.preventDefault(); - undo(); - } - - if (event.keyCode == keycodes.y && event.ctrlKey) { - event.preventDefault(); - redo(); - } - - if (event.keyCode == keycodes.c && event.ctrlKey) { - event.preventDefault(); - copyToClipboard(); - } - - if (event.keyCode == keycodes.delete && event.ctrlKey) { - event.preventDefault(); - deleteSelectedRows(); - // this.saveAndFocus(); - } - - if (event.keyCode == keycodes.insert && !event.ctrlKey) { - event.preventDefault(); - insertNewRow(); - // this.saveAndFocus(); - } - - if (inplaceEditorState.cell) return; - - if ( - !event.ctrlKey && - !event.altKey && - ((event.keyCode >= keycodes.a && event.keyCode <= keycodes.z) || - (event.keyCode >= keycodes.n0 && event.keyCode <= keycodes.n9) || - event.keyCode == keycodes.dash) - ) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'show', text: event.nativeEvent.key, cell: currentCell }); - // console.log('event', event.nativeEvent); - } - - if (event.keyCode == keycodes.f2) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'show', cell: currentCell, selectAll: true }); - } - - const moved = handleCursorMove(event); - - if (moved) { - if (event.shiftKey) { - if (!isRegularCell(shiftDragStartCell)) { - setShiftDragStartCell(currentCell); - } - } else { - setShiftDragStartCell(nullCell); - } - } - - const newCell = handleCursorMove(event); - if (event.shiftKey && newCell) { - // @ts-ignore - setSelectedCells(getCellRange(shiftDragStartCell || currentCell, newCell)); - } - } - - function handleCursorMove(event) { - if (!isRegularCell(currentCell)) return null; - let rowCount = grider.rowCount; - if (event.ctrlKey) { - switch (event.keyCode) { - case keycodes.upArrow: - case keycodes.pageUp: - return moveCurrentCell(0, currentCell[1], event); - case keycodes.downArrow: - case keycodes.pageDown: - return moveCurrentCell(rowCount - 1, currentCell[1], event); - case keycodes.leftArrow: - return moveCurrentCell(currentCell[0], 0, event); - case keycodes.rightArrow: - return moveCurrentCell(currentCell[0], columnSizes.realCount - 1, event); - case keycodes.home: - return moveCurrentCell(0, 0, event); - case keycodes.end: - return moveCurrentCell(rowCount - 1, columnSizes.realCount - 1, event); - case keycodes.a: - setSelectedCells([['header', 'header']]); - event.preventDefault(); - return ['header', 'header']; - } - } else { - switch (event.keyCode) { - case keycodes.upArrow: - if (currentCell[0] == 0) return focusFilterEditor(currentCell[1]); - return moveCurrentCell(currentCell[0] - 1, currentCell[1], event); - case keycodes.downArrow: - case keycodes.enter: - return moveCurrentCell(currentCell[0] + 1, currentCell[1], event); - case keycodes.leftArrow: - return moveCurrentCell(currentCell[0], currentCell[1] - 1, event); - case keycodes.rightArrow: - return moveCurrentCell(currentCell[0], currentCell[1] + 1, event); - case keycodes.home: - return moveCurrentCell(currentCell[0], 0, event); - case keycodes.end: - return moveCurrentCell(currentCell[0], columnSizes.realCount - 1, event); - case keycodes.pageUp: - return moveCurrentCell(currentCell[0] - visibleRowCountLowerBound, currentCell[1], event); - case keycodes.pageDown: - return moveCurrentCell(currentCell[0] + visibleRowCountLowerBound, currentCell[1], event); - } - } - return null; - } - - function focusFilterEditor(columnRealIndex) { - let modelIndex = columnSizes.realToModel(columnRealIndex); - setFocusFilterInputs(cols => ({ - ...cols, - [columns[modelIndex].uniqueName]: (cols[columns[modelIndex].uniqueName] || 0) + 1, - })); - // this.headerFilters[this.columns[modelIndex].uniquePath].focus(); - return ['filter', columnRealIndex]; - } - - function moveCurrentCell(row, col, event = null) { - const rowCount = grider.rowCount; - - if (row < 0) row = 0; - if (row >= rowCount) row = rowCount - 1; - if (col < 0) col = 0; - if (col >= columnSizes.realCount) col = columnSizes.realCount - 1; - setCurrentCell([row, col]); - // setSelectedCells([...(event.ctrlKey ? selectedCells : []), [row, col]]); - setSelectedCells([[row, col]]); - scrollIntoView([row, col]); - // this.selectedCells.push(this.currentCell); - // this.scrollIntoView(this.currentCell); - - if (event) event.preventDefault(); - return [row, col]; - } - - function scrollIntoView(cell) { - const [row, col] = cell; - - if (row != null) { - let newRow = null; - const rowCount = grider.rowCount; - if (rowCount == 0) return; - - if (row < firstVisibleRowScrollIndex) newRow = row; - else if (row + 1 >= firstVisibleRowScrollIndex + visibleRowCountLowerBound) - newRow = row - visibleRowCountLowerBound + 2; - - if (newRow < 0) newRow = 0; - if (newRow >= rowCount) newRow = rowCount - 1; - - if (newRow != null) { - setFirstVisibleRowScrollIndex(newRow); - // firstVisibleRowScrollIndex = newRow; - setvScrollValueToSet(newRow); - setvScrollValueToSetDate(new Date()); - // vscroll.value = newRow; - } - //int newRow = _rowSizes.ScrollInView(FirstVisibleRowScrollIndex, cell.Row.Value - _rowSizes.FrozenCount, GridScrollAreaHeight); - //ScrollContent(newRow, FirstVisibleColumnScrollIndex); - } - - if (col != null) { - if (col >= columnSizes.frozenCount) { - let newColumn = columnSizes.scrollInView( - firstVisibleColumnScrollIndex, - col - columnSizes.frozenCount, - gridScrollAreaWidth - ); - setFirstVisibleColumnScrollIndex(newColumn); - - // @ts-ignore - sethScrollValueToSet(newColumn); - sethScrollValueToSetDate(new Date()); - - // firstVisibleColumnScrollIndex = newColumn; - // hscroll.value = newColumn; - } - } - } - - function setGrouping(uniqueName, groupFunc) { - display.setGrouping(uniqueName, groupFunc); - } - - // console.log('visibleRowCountUpperBound', visibleRowCountUpperBound); - // console.log('gridScrollAreaHeight', gridScrollAreaHeight); - // console.log('containerHeight', containerHeight); - - const hederColwidthPx = `${headerColWidth}px`; - const filterCount = display.filterCount; - - const handleClearFilters = () => { - display.clearFilters(); - }; - - const handleSwitchToFormView = - formViewAvailable && display.baseTable && display.baseTable.primaryKey - ? () => { - const cell = currentCell; - const rowData = isRegularCell(cell) ? grider.getRowData(cell[0]) : null; - display.switchToFormView(rowData); - } - : null; - - // console.log('visibleRealColumnIndexes', visibleRealColumnIndexes); - // console.log( - // 'gridScrollAreaWidth / columnSizes.getVisibleScrollSizeSum()', - // gridScrollAreaWidth, - // columnSizes.getVisibleScrollSizeSum() - // ); - - // const loadedAndInsertedRows = [...loadedRows, ...insertedRows]; - - // console.log('focusFieldRef.current', focusFieldRef.current); - - return ( - - - - - - - {visibleRealColumns.map(col => ( - - display.setSort(col.uniqueName, order) : null} - order={display.getSortOrder(col.uniqueName)} - onResize={diff => display.resizeColumn(col.uniqueName, col.widthNumber, diff)} - setGrouping={display.sortable ? groupFunc => setGrouping(col.uniqueName, groupFunc) : null} - grouping={display.getGrouping(col.uniqueName)} - /> - - ))} - - {display.filterable && ( - - - {filterCount > 0 && ( - - - - )} - - {visibleRealColumns.map(col => ( - - display.setFilter(col.uniqueName, value)} - focusIndex={focusFilterInputs[col.uniqueName]} - onFocusGrid={() => { - selectTopmostCell(col.uniqueName); - // focusFieldRef.current.focus(); - }} - /> - - ))} - - )} - - - {_.range(firstVisibleRowScrollIndex, firstVisibleRowScrollIndex + visibleRowCountUpperBound).map(rowIndex => ( - - ))} - -
- - - {allRowCount && {rowCountInfo}} - {props.toolbarPortalRef && - props.toolbarPortalRef.current && - tabVisible && - ReactDOM.createPortal( - display.reload()} - save={handleSave} - grider={grider} - reconnect={async () => { - await axios.post('database-connections/refresh', { conid, database }); - display.reload(); - }} - switchToForm={handleSwitchToFormView} - />, - props.toolbarPortalRef.current - )} - {isLoading && } -
- ); -} diff --git a/packages/web/src/datagrid/DataGridRow.js b/packages/web/src/datagrid/DataGridRow.js deleted file mode 100644 index f468ac252..000000000 --- a/packages/web/src/datagrid/DataGridRow.js +++ /dev/null @@ -1,333 +0,0 @@ -// @ts-nocheck -import moment from 'moment'; -import _ from 'lodash'; -import React from 'react'; -import styled from 'styled-components'; -import InplaceEditor from './InplaceEditor'; -import { cellIsSelected } from './gridutil'; -import { isTypeLogical } from 'dbgate-tools'; -import useTheme from '../theme/useTheme'; -import { FontIcon } from '../icons'; - -const TableBodyCell = styled.td` - font-weight: normal; - border: 1px solid ${props => props.theme.border}; - // border-collapse: collapse; - padding: 2px; - white-space: nowrap; - position: relative; - overflow: hidden; - ${props => - props.isSelected && - !props.isAutofillSelected && - !props.isFocusedColumn && - ` - background: initial; - background-color: ${props.theme.gridbody_selection[4]}; - color: ${props.theme.gridbody_invfont1};`} - - ${props => - props.isFrameSelected && - ` - outline: 3px solid ${props.theme.gridbody_selection[4]}; - outline-offset: -3px;`} - - ${props => - props.isAutofillSelected && - !props.isFocusedColumn && - ` - outline: 3px solid ${props.theme.gridbody_selection[4]}; - outline-offset: -3px;`} - - ${props => - props.isModifiedRow && - !props.isInsertedRow && - !props.isSelected && - !props.isAutofillSelected && - !props.isModifiedCell && - !props.isFocusedColumn && - ` - background-color: ${props.theme.gridbody_background_gold[1]};`} - ${props => - !props.isSelected && - !props.isAutofillSelected && - !props.isInsertedRow && - !props.isFocusedColumn && - props.isModifiedCell && - ` - background-color: ${props.theme.gridbody_background_orange[1]};`} - - ${props => - !props.isSelected && - !props.isAutofillSelected && - !props.isFocusedColumn && - props.isInsertedRow && - ` - background-color: ${props.theme.gridbody_background_green[1]};`} - - ${props => - !props.isSelected && - !props.isAutofillSelected && - !props.isFocusedColumn && - props.isDeletedRow && - ` - background-color: ${props.theme.gridbody_background_volcano[1]}; - `} - - ${props => - props.isFocusedColumn && - ` - background-color: ${props.theme.gridbody_background_yellow[0]}; - `} - - ${props => - props.isDeletedRow && - ` - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAEElEQVQImWNgIAX8x4KJBAD+agT8INXz9wAAAABJRU5ErkJggg=='); - // from http://www.patternify.com/ - background-repeat: repeat-x; - background-position: 50% 50%;`} -`; - -const HintSpan = styled.span` - color: ${props => props.theme.gridbody_font3}; - margin-left: 5px; -`; -const NullSpan = styled.span` - color: ${props => props.theme.gridbody_font3}; - font-style: italic; -`; - -const TableBodyRow = styled.tr` - // height: 35px; - background-color: ${props => props.theme.gridbody_background}; - &:nth-child(6n + 3) { - background-color: ${props => props.theme.gridbody_background_alt2}; - } - &:nth-child(6n + 6) { - background-color: ${props => props.theme.gridbody_background_alt3}; - } -`; - -const TableHeaderCell = styled.td` - border: 1px solid ${props => props.theme.border}; - text-align: left; - padding: 2px; - background-color: ${props => props.theme.gridheader_background}; - overflow: hidden; - position: relative; -`; - -const AutoFillPoint = styled.div` - width: 8px; - height: 8px; - background-color: ${props => props.theme.gridbody_selection[6]}; - position: absolute; - right: 0px; - bottom: 0px; - overflow: visible; - cursor: crosshair; -`; - -export const ShowFormButton = styled.div` - position: absolute; - right: 0px; - top: 1px; - color: ${props => props.theme.gridbody_font3}; - background-color: ${props => props.theme.gridheader_background}; - border: 1px solid ${props => props.theme.gridheader_background}; - &:hover { - color: ${props => props.theme.gridheader_font_hover}; - border: 1px solid ${props => props.theme.border}; - top: 1px; - right: 0px; - } -`; - -function makeBulletString(value) { - return _.pad('', value.length, '•'); -} - -function highlightSpecialCharacters(value) { - value = value.replace(/\n/g, '↲'); - value = value.replace(/\r/g, ''); - value = value.replace(/^(\s+)/, makeBulletString); - value = value.replace(/(\s+)$/, makeBulletString); - value = value.replace(/(\s\s+)/g, makeBulletString); - return value; -} - -const dateTimeRegex = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d\d\d)?Z?$/; - -export function CellFormattedValue({ value, dataType, theme }) { - if (value == null) return (NULL); - if (_.isDate(value)) return moment(value).format('YYYY-MM-DD HH:mm:ss'); - if (value === true) return '1'; - if (value === false) return '0'; - if (_.isNumber(value)) { - if (value >= 10000 || value <= -10000) { - return value.toLocaleString(); - } - return value.toString(); - } - if (_.isString(value)) { - if (dateTimeRegex.test(value)) return moment(value).format('YYYY-MM-DD HH:mm:ss'); - return highlightSpecialCharacters(value); - } - if (_.isPlainObject(value)) { - if (_.isArray(value.data)) { - if (value.data.length == 1 && isTypeLogical(dataType)) return value.data[0]; - return ({value.data.length} bytes); - } - return (RAW); - } - return value.toString(); -} - -function RowHeaderCell({ rowIndex, theme, onSetFormView, rowData }) { - const [mouseIn, setMouseIn] = React.useState(false); - - return ( - setMouseIn(true) : null} - onMouseLeave={onSetFormView ? () => setMouseIn(false) : null} - > - {rowIndex + 1} - {!!onSetFormView && mouseIn && ( - { - e.stopPropagation(); - onSetFormView(rowData); - }} - > - - - )} - - ); -} - -/** @param props {import('./types').DataGridProps} */ -function DataGridRow(props) { - const { - rowHeight, - rowIndex, - visibleRealColumns, - inplaceEditorState, - dispatchInsplaceEditor, - autofillMarkerCell, - selectedCells, - autofillSelectedCells, - focusedColumn, - grider, - frameSelection, - onSetFormView, - } = props; - // usePropsCompare({ - // rowHeight, - // rowIndex, - // visibleRealColumns, - // inplaceEditorState, - // dispatchInsplaceEditor, - // row, - // display, - // changeSet, - // setChangeSet, - // insertedRowIndex, - // autofillMarkerCell, - // selectedCells, - // autofillSelectedCells, - // }); - - // console.log('RENDER ROW', rowIndex); - - const theme = useTheme(); - - const rowData = grider.getRowData(rowIndex); - const rowStatus = grider.getRowStatus(rowIndex); - - const hintFieldsAllowed = visibleRealColumns - .filter(col => { - if (!col.hintColumnName) return false; - if (rowStatus.modifiedFields && rowStatus.modifiedFields.has(col.uniqueName)) return false; - return true; - }) - .map(col => col.uniqueName); - - if (!rowData) return null; - - return ( - - - - {visibleRealColumns.map(col => ( - - {inplaceEditorState.cell && - rowIndex == inplaceEditorState.cell[0] && - col.colIndex == inplaceEditorState.cell[1] ? ( - grider.setCellValue(rowIndex, col.uniqueName, value)} - /> - ) : ( - <> - - {hintFieldsAllowed.includes(col.uniqueName) && ( - {rowData[col.hintColumnName]} - )} - {col.foreignKey && rowData[col.uniqueName] && ( - { - e.stopPropagation(); - onSetFormView(rowData, col); - }} - > - - - )} - - )} - {autofillMarkerCell && autofillMarkerCell[1] == col.colIndex && autofillMarkerCell[0] == rowIndex && ( - - )} - - ))} - - ); -} - -export default React.memo(DataGridRow); diff --git a/packages/web/src/datagrid/DataGridToolbar.js b/packages/web/src/datagrid/DataGridToolbar.js deleted file mode 100644 index 800aa8586..000000000 --- a/packages/web/src/datagrid/DataGridToolbar.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import ToolbarButton from '../widgets/ToolbarButton'; - -export default function DataGridToolbar({ reload, reconnect, grider, save, switchToForm }) { - return ( - <> - {switchToForm && ( - - Form view - - )} - - Refresh - - - Reconnect - - grider.undo()} icon="icon undo"> - Undo - - grider.redo()} icon="icon redo"> - Redo - - - Save - - grider.revertAllChanges()} icon="icon close"> - Revert - - - ); -} diff --git a/packages/web/src/datagrid/Grider.ts b/packages/web/src/datagrid/Grider.ts deleted file mode 100644 index 0f24aeea4..000000000 --- a/packages/web/src/datagrid/Grider.ts +++ /dev/null @@ -1,61 +0,0 @@ -export interface GriderRowStatus { - status: 'regular' | 'updated' | 'deleted' | 'inserted'; - modifiedFields?: Set; - insertedFields?: Set; - deletedFields?: Set; -} - -export default abstract class Grider { - abstract getRowData(index): any; - abstract get rowCount(): number; - - getRowStatus(index): GriderRowStatus { - const res: GriderRowStatus = { - status: 'regular', - }; - return res; - } - beginUpdate() {} - endUpdate() {} - setCellValue(index: number, uniqueName: string, value: any) {} - deleteRow(index: number) {} - insertRow(): number { - return null; - } - revertRowChanges(index: number) {} - revertAllChanges() {} - undo() {} - redo() {} - get editable() { - return false; - } - get canInsert() { - return false; - } - get allowSave() { - return this.containsChanges; - } - get rowCountInUpdate() { - return this.rowCount; - } - get canUndo() { - return false; - } - get canRedo() { - return false; - } - get containsChanges() { - return false; - } - get disableLoadNextPage() { - return false; - } - get errors() { - return null; - } - updateRow(index, changeObject) { - for (const key of Object.keys(changeObject)) { - this.setCellValue(index, key, changeObject[key]); - } - } -} diff --git a/packages/web/src/datagrid/InplaceEditor.js b/packages/web/src/datagrid/InplaceEditor.js deleted file mode 100644 index e32ab10ed..000000000 --- a/packages/web/src/datagrid/InplaceEditor.js +++ /dev/null @@ -1,98 +0,0 @@ -// @ts-nocheck - -import _ from 'lodash'; -import React from 'react'; -import styled from 'styled-components'; -import keycodes from '../utility/keycodes'; - -const StyledInput = styled.input` - border: 0px solid; - outline: none; - margin: 0px; - padding: 0px; -`; - -export default function InplaceEditor({ - widthPx, - // rowIndex, - // uniqueName, - // grider, - cellValue, - inplaceEditorState, - dispatchInsplaceEditor, - onSetValue, -}) { - const editorRef = React.useRef(); - const widthRef = React.useRef(widthPx); - const isChangedRef = React.useRef(!!inplaceEditorState.text); - React.useEffect(() => { - const editor = editorRef.current; - editor.value = inplaceEditorState.text || cellValue; - editor.focus(); - if (inplaceEditorState.selectAll) { - editor.select(); - } - }, []); - function handleBlur() { - if (isChangedRef.current) { - const editor = editorRef.current; - onSetValue(editor.value); - // grider.setCellValue(rowIndex, uniqueName, editor.value); - isChangedRef.current = false; - } - dispatchInsplaceEditor({ type: 'close' }); - } - if (inplaceEditorState.shouldSave) { - const editor = editorRef.current; - if (isChangedRef.current) { - onSetValue(editor.value); - // grider.setCellValue(rowIndex, uniqueName, editor.value); - isChangedRef.current = false; - } - editor.blur(); - dispatchInsplaceEditor({ type: 'close', mode: 'save' }); - } - function handleKeyDown(event) { - const editor = editorRef.current; - switch (event.keyCode) { - case keycodes.escape: - isChangedRef.current = false; - dispatchInsplaceEditor({ type: 'close' }); - break; - case keycodes.enter: - if (isChangedRef.current) { - // grider.setCellValue(rowIndex, uniqueName, editor.value); - onSetValue(editor.value); - isChangedRef.current = false; - } - editor.blur(); - dispatchInsplaceEditor({ type: 'close', mode: 'enter' }); - break; - case keycodes.s: - if (event.ctrlKey) { - if (isChangedRef.current) { - onSetValue(editor.value); - // grider.setCellValue(rowIndex, uniqueName, editor.value); - isChangedRef.current = false; - } - event.preventDefault(); - dispatchInsplaceEditor({ type: 'close', mode: 'save' }); - } - break; - } - } - return ( - (isChangedRef.current = true)} - onKeyDown={handleKeyDown} - style={{ - width: widthRef.current, - minWidth: widthRef.current, - maxWidth: widthRef.current, - }} - /> - ); -} diff --git a/packages/web/src/datagrid/JslDataGridCore.js b/packages/web/src/datagrid/JslDataGridCore.js deleted file mode 100644 index 1ec74e608..000000000 --- a/packages/web/src/datagrid/JslDataGridCore.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import axios from '../utility/axios'; -import { useSetOpenedTabs } from '../utility/globalState'; -import useSocket from '../utility/SocketProvider'; -import useShowModal from '../modals/showModal'; -import ImportExportModal from '../modals/ImportExportModal'; -import LoadingDataGridCore from './LoadingDataGridCore'; -import RowsArrayGrider from './RowsArrayGrider'; - -async function loadDataPage(props, offset, limit) { - const { jslid, display } = props; - - const response = await axios.post('jsldata/get-rows', { - jslid, - offset, - limit, - filters: display ? display.compileFilters() : null, - }); - - return response.data; -} - -function dataPageAvailable(props) { - return true; -} - -async function loadRowCount(props) { - const { jslid } = props; - - const response = await axios.request({ - url: 'jsldata/get-stats', - method: 'get', - params: { - jslid, - }, - }); - return response.data.rowCount; -} - -export default function JslDataGridCore(props) { - const { jslid } = props; - const [changeIndex, setChangeIndex] = React.useState(0); - const [rowCountLoaded, setRowCountLoaded] = React.useState(null); - - const showModal = useShowModal(); - - const setOpenedTabs = useSetOpenedTabs(); - const socket = useSocket(); - - function exportGrid() { - const initialValues = {}; - const archiveMatch = jslid.match(/^archive:\/\/([^/]+)\/(.*)$/); - if (archiveMatch) { - initialValues.sourceStorageType = 'archive'; - initialValues.sourceArchiveFolder = archiveMatch[1]; - initialValues.sourceList = [archiveMatch[2]]; - } else { - initialValues.sourceStorageType = 'jsldata'; - initialValues.sourceJslId = jslid; - initialValues.sourceList = ['query-data']; - } - showModal(modalState => ); - } - - const handleJslDataStats = React.useCallback( - stats => { - if (stats.changeIndex < changeIndex) return; - setChangeIndex(stats.changeIndex); - setRowCountLoaded(stats.rowCount); - }, - [changeIndex] - ); - - React.useEffect(() => { - if (jslid && socket) { - socket.on(`jsldata-stats-${jslid}`, handleJslDataStats); - return () => { - socket.off(`jsldata-stats-${jslid}`, handleJslDataStats); - }; - } - }, [jslid]); - - return ( - setChangeIndex(0)} - griderFactory={RowsArrayGrider.factory} - griderFactoryDeps={RowsArrayGrider.factoryDeps} - /> - ); -} diff --git a/packages/web/src/datagrid/LoadingDataGridCore.js b/packages/web/src/datagrid/LoadingDataGridCore.js deleted file mode 100644 index f3a4f5029..000000000 --- a/packages/web/src/datagrid/LoadingDataGridCore.js +++ /dev/null @@ -1,141 +0,0 @@ -import React from 'react'; -import DataGridCore from './DataGridCore'; - -export default function LoadingDataGridCore(props) { - const { - display, - loadDataPage, - dataPageAvailable, - loadRowCount, - loadNextDataToken, - onReload, - exportGrid, - openQuery, - griderFactory, - griderFactoryDeps, - onChangeGrider, - rowCountLoaded, - } = props; - - const [loadProps, setLoadProps] = React.useState({ - isLoading: false, - loadedRows: [], - isLoadedAll: false, - loadedTime: new Date().getTime(), - allRowCount: null, - errorMessage: null, - loadNextDataToken: 0, - }); - const { isLoading, loadedRows, isLoadedAll, loadedTime, allRowCount, errorMessage } = loadProps; - - const loadedTimeRef = React.useRef(0); - - const handleLoadRowCount = async () => { - const rowCount = await loadRowCount(props); - setLoadProps(oldLoadProps => ({ - ...oldLoadProps, - allRowCount: rowCount, - })); - }; - - const reload = () => { - setLoadProps({ - allRowCount: null, - isLoading: false, - loadedRows: [], - isLoadedAll: false, - loadedTime: new Date().getTime(), - errorMessage: null, - loadNextDataToken: 0, - }); - if (onReload) onReload(); - }; - - React.useEffect(() => { - if (props.masterLoadedTime && props.masterLoadedTime > loadedTime) { - display.reload(); - } - if (display.cache.refreshTime > loadedTime) { - reload(); - } - }); - - const loadNextData = async () => { - if (isLoading) return; - setLoadProps(oldLoadProps => ({ - ...oldLoadProps, - isLoading: true, - })); - const loadStart = new Date().getTime(); - loadedTimeRef.current = loadStart; - - const nextRows = await loadDataPage(props, loadedRows.length, 100); - if (loadedTimeRef.current !== loadStart) { - // new load was dispatched - return; - } - // if (!_.isArray(nextRows)) { - // console.log('Error loading data from server', nextRows); - // nextRows = []; - // } - // console.log('nextRows', nextRows); - if (nextRows.errorMessage) { - setLoadProps(oldLoadProps => ({ - ...oldLoadProps, - isLoading: false, - errorMessage: nextRows.errorMessage, - })); - } else { - if (allRowCount == null) handleLoadRowCount(); - const loadedInfo = { - loadedRows: [...loadedRows, ...nextRows], - loadedTime, - }; - setLoadProps(oldLoadProps => ({ - ...oldLoadProps, - isLoading: false, - isLoadedAll: oldLoadProps.loadNextDataToken == loadNextDataToken && nextRows.length === 0, - loadNextDataToken, - ...loadedInfo, - })); - } - }; - - React.useEffect(() => { - setLoadProps(oldProps => ({ - ...oldProps, - isLoadedAll: false, - })); - }, [loadNextDataToken]); - - const griderProps = { ...props, sourceRows: loadedRows }; - const grider = React.useMemo(() => griderFactory(griderProps), griderFactoryDeps(griderProps)); - - React.useEffect(() => { - if (onChangeGrider) onChangeGrider(grider); - }, [grider]); - - const handleLoadNextData = () => { - if (!isLoadedAll && !errorMessage && !grider.disableLoadNextPage) { - if (dataPageAvailable(props)) { - // If not, callbacks to load missing metadata are dispatched - loadNextData(); - } - } - }; - - return ( - - ); -} diff --git a/packages/web/src/datagrid/ManagerStyles.js b/packages/web/src/datagrid/ManagerStyles.js deleted file mode 100644 index 6de11af95..000000000 --- a/packages/web/src/datagrid/ManagerStyles.js +++ /dev/null @@ -1,7 +0,0 @@ -import styled from 'styled-components'; - -export const ManagerInnerContainer = styled.div` - flex: 1 1; - overflow-y: auto; - overflow-x: auto; -`; diff --git a/packages/web/src/datagrid/ReferenceHeader.js b/packages/web/src/datagrid/ReferenceHeader.js deleted file mode 100644 index 4f723a320..000000000 --- a/packages/web/src/datagrid/ReferenceHeader.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import ToolbarButton from '../widgets/ToolbarButton'; -import styled from 'styled-components'; -import dimensions from '../theme/dimensions'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; - -const Container = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - background: ${props => props.theme.gridheader_background_cyan[0]}; - height: ${dimensions.toolBar.height}px; - min-height: ${dimensions.toolBar.height}px; - overflow: hidden; - border-top: 1px solid ${props => props.theme.border}; - border-bottom: 1px solid ${props => props.theme.border}; -`; - -const Header = styled.div` - font-weight: bold; - margin-left: 10px; - display: flex; -`; - -const HeaderText = styled.div` - margin-left: 10px; -`; - -export default function ReferenceHeader({ reference, onClose }) { - const theme = useTheme(); - return ( - -
- - - {reference.pureName} [{reference.columns.map(x => x.refName).join(', ')}] = master [ - {reference.columns.map(x => x.baseName).join(', ')}] - -
- - Close - -
- ); -} diff --git a/packages/web/src/datagrid/ReferenceManager.js b/packages/web/src/datagrid/ReferenceManager.js deleted file mode 100644 index 76b1cfc0e..000000000 --- a/packages/web/src/datagrid/ReferenceManager.js +++ /dev/null @@ -1,114 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { ManagerInnerContainer } from './ManagerStyles'; -import SearchInput from '../widgets/SearchInput'; -import { filterName } from 'dbgate-datalib'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; - -const SearchBoxWrapper = styled.div` - display: flex; - margin-bottom: 5px; -`; - -const Header = styled.div` - font-weight: bold; - white-space: nowrap; -`; - -const LinkContainer = styled.div` - color: ${props => props.theme.manager_font_blue[7]}; - margin: 5px; - &:hover { - text-decoration: underline; - } - cursor: pointer; - display: flex; - flex-wrap: nowrap; -`; - -const NameContainer = styled.div` - margin-left: 5px; - white-space: nowrap; -`; - -function ManagerRow({ tableName, columns, icon, onClick }) { - const theme = useTheme(); - return ( - - - - {tableName} ({columns.map(x => x.columnName).join(', ')}) - - - ); -} - -/** @param props {import('./types').DataGridProps} */ -export default function ReferenceManager(props) { - const [filter, setFilter] = React.useState(''); - const { display } = props; - const { baseTable } = display || {}; - const { foreignKeys } = baseTable || {}; - const { dependencies } = baseTable || {}; - - return ( - <> - - - - - {foreignKeys && foreignKeys.length > 0 && ( - <> -
References tables ({foreignKeys.length})
- {foreignKeys - .filter(fk => filterName(filter, fk.refTableName)) - .map(fk => ( - - props.onReferenceClick({ - schemaName: fk.refSchemaName, - pureName: fk.refTableName, - columns: fk.columns.map(col => ({ - baseName: col.columnName, - refName: col.refColumnName, - })), - }) - } - /> - ))} - - )} - {dependencies && dependencies.length > 0 && ( - <> -
Dependend tables ({dependencies.length})
- {dependencies - .filter(fk => filterName(filter, fk.pureName)) - .map(fk => ( - - props.onReferenceClick({ - schemaName: fk.schemaName, - pureName: fk.pureName, - columns: fk.columns.map(col => ({ - baseName: col.refColumnName, - refName: col.columnName, - })), - }) - } - /> - ))} - - )} -
- - ); -} diff --git a/packages/web/src/datagrid/RowsArrayGrider.ts b/packages/web/src/datagrid/RowsArrayGrider.ts deleted file mode 100644 index 76a9e140d..000000000 --- a/packages/web/src/datagrid/RowsArrayGrider.ts +++ /dev/null @@ -1,20 +0,0 @@ -import Grider, { GriderRowStatus } from './Grider'; - -export default class RowsArrayGrider extends Grider { - constructor(private rows: any[]) { - super(); - } - getRowData(index: any) { - return this.rows[index]; - } - get rowCount() { - return this.rows.length; - } - - static factory({ sourceRows }): RowsArrayGrider { - return new RowsArrayGrider(sourceRows); - } - static factoryDeps({ sourceRows }) { - return [sourceRows]; - } -} diff --git a/packages/web/src/datagrid/ScrollBars.js b/packages/web/src/datagrid/ScrollBars.js deleted file mode 100644 index f2880615d..000000000 --- a/packages/web/src/datagrid/ScrollBars.js +++ /dev/null @@ -1,222 +0,0 @@ -import _ from 'lodash'; -import React from 'react'; -import styled from 'styled-components'; -import useDimensions from '../utility/useDimensions'; - -const StyledHorizontalScrollBar = styled.div` - overflow-x: scroll; - height: 16px; - position: absolute; - bottom: 0; - //left: 100px; - // right: 20px; - right: 0; - left: 0; -`; - -const StyledHorizontalScrollContent = styled.div``; - -const StyledVerticalScrollBar = styled.div` - overflow-y: scroll; - width: 20px; - position: absolute; - right: 0px; - width: 20px; - bottom: 16px; - // bottom: 0; - top: 0; -`; - -const StyledVerticalScrollContent = styled.div``; - -export function HorizontalScrollBar({ - onScroll = undefined, - valueToSet = undefined, - valueToSetDate = undefined, - minimum, - maximum, - viewportRatio = 0.5, -}) { - const [ref, { width }, node] = useDimensions(); - const contentSize = Math.round(width / viewportRatio); - - React.useEffect(() => { - const position01 = (valueToSet - minimum) / (maximum - minimum + 1); - const position = position01 * (contentSize - width); - if (node) node.scrollLeft = Math.floor(position); - }, [valueToSetDate]); - - const handleScroll = () => { - const position = node.scrollLeft; - const ratio = position / (contentSize - width); - if (ratio < 0) return 0; - let res = ratio * (maximum - minimum + 1) + minimum; - onScroll(Math.floor(res + 0.3)); - }; - - return ( - -   - - ); -} - -export function VerticalScrollBar({ - onScroll, - valueToSet = undefined, - valueToSetDate = undefined, - minimum, - maximum, - viewportRatio = 0.5, -}) { - const [ref, { height }, node] = useDimensions(); - const contentSize = Math.round(height / viewportRatio); - - React.useEffect(() => { - const position01 = (valueToSet - minimum) / (maximum - minimum + 1); - const position = position01 * (contentSize - height); - if (node) node.scrollTop = Math.floor(position); - }, [valueToSetDate]); - - const handleScroll = () => { - const position = node.scrollTop; - const ratio = position / (contentSize - height); - if (ratio < 0) return 0; - let res = ratio * (maximum - minimum + 1) + minimum; - onScroll(Math.floor(res + 0.3)); - }; - - return ( - -   - - ); -} - -// export interface IScrollBarProps { -// viewportRatio: number; -// minimum: number; -// maximum: number; -// containerStyle: any; -// onScroll?: any; -// } - -// export abstract class ScrollBarBase extends React.Component { -// domScrollContainer: Element; -// domScrollContent: Element; -// contentSize: number; -// containerResizedBind: any; - -// constructor(props) { -// super(props); -// this.containerResizedBind = this.containerResized.bind(this); -// } - -// componentDidMount() { -// $(this.domScrollContainer).scroll(this.onScroll.bind(this)); -// createResizeDetector(this.domScrollContainer, this.containerResized.bind(this)); -// window.addEventListener('resize', this.containerResizedBind); -// this.updateContentSize(); -// } - -// componentWillUnmount() { -// deleteResizeDetector(this.domScrollContainer); -// window.removeEventListener('resize', this.containerResizedBind); -// } - -// onScroll() { -// if (this.props.onScroll) { -// this.props.onScroll(this.value); -// } -// } - -// get value(): number { -// let position = this.getScrollPosition(); -// let ratio = position / (this.contentSize - this.getContainerSize()); -// if (ratio < 0) return 0; -// let res = ratio * (this.props.maximum - this.props.minimum + 1) + this.props.minimum; -// return Math.floor(res + 0.3); -// } - -// set value(value: number) { -// let position01 = (value - this.props.minimum) / (this.props.maximum - this.props.minimum + 1); -// let position = position01 * (this.contentSize - this.getContainerSize()); -// this.setScrollPosition(Math.floor(position)); -// } - -// containerResized() { -// this.setContentSizeField(); -// this.updateContentSize(); -// } - -// setContentSizeField() { -// let lastContentSize = this.contentSize; -// this.contentSize = Math.round(this.getContainerSize() / this.props.viewportRatio); -// if (_.isNaN(this.contentSize)) this.contentSize = 0; -// if (this.contentSize > 1000000 && detectBrowser() == BrowserType.Firefox) this.contentSize = 1000000; -// if (lastContentSize != this.contentSize) { -// this.updateContentSize(); -// } -// } - -// abstract getContainerSize(): number; -// abstract updateContentSize(); -// abstract getScrollPosition(): number; -// abstract setScrollPosition(value: number); -// } - -// export class HorizontalScrollBar extends ScrollBarBase { -// render() { -// this.setContentSizeField(); - -// return
this.domScrollContainer = x} style={this.props.containerStyle}> -//
this.domScrollContent = x} style={{ width: this.contentSize }}> -//   -//
-//
; -// } - -// getContainerSize(): number { -// return $(this.domScrollContainer).width(); -// } - -// updateContentSize() { -// $(this.domScrollContent).width(this.contentSize); -// } - -// getScrollPosition() { -// return $(this.domScrollContainer).scrollLeft(); -// } - -// setScrollPosition(value: number) { -// $(this.domScrollContainer).scrollLeft(value); -// } -// } - -// export class VerticalScrollBar extends ScrollBarBase { -// render() { -// this.setContentSizeField(); - -// return
this.domScrollContainer = x} style={this.props.containerStyle}> -//
this.domScrollContent = x} style={{ height: this.contentSize }}> -//   -//
-//
; -// } - -// getContainerSize(): number { -// return $(this.domScrollContainer).height(); -// } - -// updateContentSize() { -// $(this.domScrollContent).height(this.contentSize); -// } - -// getScrollPosition() { -// return $(this.domScrollContainer).scrollTop(); -// } - -// setScrollPosition(value: number) { -// $(this.domScrollContainer).scrollTop(value); -// } -// } diff --git a/packages/web/src/datagrid/SeriesSizes-old.js b/packages/web/src/datagrid/SeriesSizes-old.js deleted file mode 100644 index 6c8f98d85..000000000 --- a/packages/web/src/datagrid/SeriesSizes-old.js +++ /dev/null @@ -1,340 +0,0 @@ -import _ from 'lodash'; - -export class SeriesSizeItem { - constructor() { - this.scrollIndex = -1; - this.frozenIndex = -1; - this.modelIndex = 0; - this.size = 0; - this.position = 0; - } - - // modelIndex; - // size; - // position; - - get endPosition() { - return this.position + this.size; - } -} - -export class SeriesSizes { - constructor() { - this.scrollItems = []; - this.sizeOverridesByModelIndex = {}; - this.positions = []; - this.scrollIndexes = []; - this.frozenItems = []; - this.hiddenAndFrozenModelIndexes = null; - this.frozenModelIndexes = null; - - this.count = 0; - this.maxSize = 1000; - this.defaultSize = 50; - } - - // private sizeOverridesByModelIndex: { [id] } = {}; - // count; - // defaultSize; - // maxSize; - // private hiddenAndFrozenModelIndexes[] = []; - // private frozenModelIndexes[] = []; - // private hiddenModelIndexes[] = []; - // private scrollItems: SeriesSizeItem[] = []; - // private positions[] = []; - // private scrollIndexes[] = []; - // private frozenItems: SeriesSizeItem[] = []; - - get scrollCount() { - return this.count - (this.hiddenAndFrozenModelIndexes != null ? this.hiddenAndFrozenModelIndexes.length : 0); - } - get frozenCount() { - return this.frozenModelIndexes != null ? this.frozenModelIndexes.length : 0; - } - get frozenSize() { - return _.sumBy(this.frozenItems, x => x.size); - } - get realCount() { - return this.frozenCount + this.scrollCount; - } - - putSizeOverride(modelIndex, size, sizeByUser = false) { - if (this.maxSize && size > this.maxSize && !sizeByUser) { - size = this.maxSize; - } - - let currentSize = this.sizeOverridesByModelIndex[modelIndex]; - if (sizeByUser || !currentSize || size > currentSize) { - this.sizeOverridesByModelIndex[modelIndex] = size; - } - // if (!_.has(this.sizeOverridesByModelIndex, modelIndex)) - // this.sizeOverridesByModelIndex[modelIndex] = size; - // if (size > this.sizeOverridesByModelIndex[modelIndex]) - // this.sizeOverridesByModelIndex[modelIndex] = size; - } - buildIndex() { - this.scrollItems = []; - this.scrollIndexes = _.filter( - _.map(this.intKeys(this.sizeOverridesByModelIndex), x => this.modelToReal(x) - this.frozenCount), - x => x >= 0 - ); - this.scrollIndexes.sort(); - let lastScrollIndex = -1; - let lastEndPosition = 0; - this.scrollIndexes.forEach(scrollIndex => { - let modelIndex = this.realToModel(scrollIndex + this.frozenCount); - let size = this.sizeOverridesByModelIndex[modelIndex]; - let item = new SeriesSizeItem(); - item.scrollIndex = scrollIndex; - item.modelIndex = modelIndex; - item.size = size; - item.position = lastEndPosition + (scrollIndex - lastScrollIndex - 1) * this.defaultSize; - this.scrollItems.push(item); - lastScrollIndex = scrollIndex; - lastEndPosition = item.endPosition; - }); - this.positions = _.map(this.scrollItems, x => x.position); - this.frozenItems = []; - let lastpos = 0; - for (let i = 0; i < this.frozenCount; i++) { - let modelIndex = this.frozenModelIndexes[i]; - let size = this.getSizeByModelIndex(modelIndex); - let item = new SeriesSizeItem(); - item.frozenIndex = i; - item.modelIndex = modelIndex; - item.size = size; - item.position = lastpos; - this.frozenItems.push(item); - lastpos += size; - } - } - - getScrollIndexOnPosition(position) { - let itemOrder = _.sortedIndex(this.positions, position); - if (this.positions[itemOrder] == position) return itemOrder; - if (itemOrder == 0) return Math.floor(position / this.defaultSize); - if (position <= this.scrollItems[itemOrder - 1].endPosition) return this.scrollItems[itemOrder - 1].scrollIndex; - return ( - Math.floor((position - this.scrollItems[itemOrder - 1].position) / this.defaultSize) + - this.scrollItems[itemOrder - 1].scrollIndex - ); - } - getFrozenIndexOnPosition(position) { - this.frozenItems.forEach(function (item) { - if (position >= item.position && position <= item.endPosition) return item.frozenIndex; - }); - return -1; - } - // getSizeSum(startScrollIndex, endScrollIndex) { - // let order1 = _.sortedIndexOf(this.scrollIndexes, startScrollIndex); - // let order2 = _.sortedIndexOf(this.scrollIndexes, endScrollIndex); - // let count = endScrollIndex - startScrollIndex; - // if (order1 < 0) - // order1 = ~order1; - // if (order2 < 0) - // order2 = ~order2; - // let result = 0; - // for (let i = order1; i <= order2; i++) { - // if (i < 0) - // continue; - // if (i >= this.scrollItems.length) - // continue; - // let item = this.scrollItems[i]; - // if (item.scrollIndex < startScrollIndex) - // continue; - // if (item.scrollIndex >= endScrollIndex) - // continue; - // result += item.size; - // count--; - // } - // result += count * this.defaultSize; - // return result; - // } - getSizeByModelIndex(modelIndex) { - if (_.has(this.sizeOverridesByModelIndex, modelIndex)) return this.sizeOverridesByModelIndex[modelIndex]; - return this.defaultSize; - } - getSizeByScrollIndex(scrollIndex) { - return this.getSizeByRealIndex(scrollIndex + this.frozenCount); - } - getSizeByRealIndex(realIndex) { - let modelIndex = this.realToModel(realIndex); - return this.getSizeByModelIndex(modelIndex); - } - removeSizeOverride(realIndex) { - let modelIndex = this.realToModel(realIndex); - delete this.sizeOverridesByModelIndex[modelIndex]; - } - getScroll(sourceScrollIndex, targetScrollIndex) { - if (sourceScrollIndex < targetScrollIndex) { - return -_.sum( - _.map(_.range(sourceScrollIndex, targetScrollIndex - sourceScrollIndex), x => this.getSizeByScrollIndex(x)) - ); - } else { - return _.sum( - _.map(_.range(targetScrollIndex, sourceScrollIndex - targetScrollIndex), x => this.getSizeByScrollIndex(x)) - ); - } - } - modelIndexIsInScrollArea(modelIndex) { - let realIndex = this.modelToReal(modelIndex); - return realIndex >= this.frozenCount; - } - getTotalScrollSizeSum() { - let scrollSizeOverrides = _.map( - _.filter(this.intKeys(this.sizeOverridesByModelIndex), x => this.modelIndexIsInScrollArea(x)), - x => this.sizeOverridesByModelIndex[x] - ); - return _.sum(scrollSizeOverrides) + (this.count - scrollSizeOverrides.length) * this.defaultSize; - } - getVisibleScrollSizeSum() { - let scrollSizeOverrides = _.map( - _.filter(this.intKeys(this.sizeOverridesByModelIndex), x => !_.includes(this.hiddenAndFrozenModelIndexes, x)), - x => this.sizeOverridesByModelIndex[x] - ); - return ( - _.sum(scrollSizeOverrides) + - (this.count - this.hiddenModelIndexes.length - scrollSizeOverrides.length) * this.defaultSize - ); - } - intKeys(value) { - return _.keys(value).map(x => _.parseInt(x)); - } - getPositionByRealIndex(realIndex) { - if (realIndex < 0) return 0; - if (realIndex < this.frozenCount) return this.frozenItems[realIndex].position; - return this.getPositionByScrollIndex(realIndex - this.frozenCount); - } - getPositionByScrollIndex(scrollIndex) { - let order = _.sortedIndex(this.scrollIndexes, scrollIndex); - if (this.scrollIndexes[order] == scrollIndex) return this.scrollItems[order].position; - order--; - if (order < 0) return scrollIndex * this.defaultSize; - return ( - this.scrollItems[order].endPosition + (scrollIndex - this.scrollItems[order].scrollIndex - 1) * this.defaultSize - ); - } - getVisibleScrollCount(firstVisibleIndex, viewportSize) { - let res = 0; - let index = firstVisibleIndex; - let count = 0; - while (res < viewportSize && index <= this.scrollCount) { - // console.log('this.getSizeByScrollIndex(index)', this.getSizeByScrollIndex(index)); - res += this.getSizeByScrollIndex(index); - index++; - count++; - } - // console.log('getVisibleScrollCount', firstVisibleIndex, viewportSize, count); - return count; - } - getVisibleScrollCountReversed(lastVisibleIndex, viewportSize) { - let res = 0; - let index = lastVisibleIndex; - let count = 0; - while (res < viewportSize && index >= 0) { - res += this.getSizeByScrollIndex(index); - index--; - count++; - } - return count; - } - invalidateAfterScroll(oldFirstVisible, newFirstVisible, invalidate, viewportSize) { - if (newFirstVisible > oldFirstVisible) { - let oldVisibleCount = this.getVisibleScrollCount(oldFirstVisible, viewportSize); - let newVisibleCount = this.getVisibleScrollCount(newFirstVisible, viewportSize); - for (let i = oldFirstVisible + oldVisibleCount - 1; i <= newFirstVisible + newVisibleCount; i++) { - invalidate(i + this.frozenCount); - } - } else { - for (let i = newFirstVisible; i <= oldFirstVisible; i++) { - invalidate(i + this.frozenCount); - } - } - } - isWholeInView(firstVisibleIndex, index, viewportSize) { - let res = 0; - let testedIndex = firstVisibleIndex; - while (res < viewportSize && testedIndex < this.count) { - res += this.getSizeByScrollIndex(testedIndex); - if (testedIndex == index) return res <= viewportSize; - testedIndex++; - } - return false; - } - scrollInView(firstVisibleIndex, scrollIndex, viewportSize) { - if (this.isWholeInView(firstVisibleIndex, scrollIndex, viewportSize)) { - return firstVisibleIndex; - } - if (scrollIndex < firstVisibleIndex) { - return scrollIndex; - } - let res = 0; - let testedIndex = scrollIndex; - while (res < viewportSize && testedIndex >= 0) { - let size = this.getSizeByScrollIndex(testedIndex); - if (res + size > viewportSize) return testedIndex + 1; - testedIndex--; - res += size; - } - if (res >= viewportSize && testedIndex < scrollIndex) return testedIndex + 1; - return firstVisibleIndex; - } - resize(realIndex, newSize) { - if (realIndex < 0) return; - let modelIndex = this.realToModel(realIndex); - if (modelIndex < 0) return; - this.sizeOverridesByModelIndex[modelIndex] = newSize; - this.buildIndex(); - } - setExtraordinaryIndexes(hidden, frozen) { - //this._hiddenAndFrozenModelIndexes = _.clone(hidden); - hidden = hidden.filter(x => x >= 0); - frozen = frozen.filter(x => x >= 0); - - hidden.sort((a, b) => a - b); - frozen.sort((a, b) => a - b); - this.frozenModelIndexes = _.filter(frozen, x => !_.includes(hidden, x)); - this.hiddenModelIndexes = _.filter(hidden, x => !_.includes(frozen, x)); - this.hiddenAndFrozenModelIndexes = _.concat(hidden, this.frozenModelIndexes); - this.frozenModelIndexes.sort((a, b) => a - b); - if (this.hiddenAndFrozenModelIndexes.length == 0) this.hiddenAndFrozenModelIndexes = null; - if (this.frozenModelIndexes.length == 0) this.frozenModelIndexes = null; - this.buildIndex(); - } - realToModel(realIndex) { - if (this.hiddenAndFrozenModelIndexes == null && this.frozenModelIndexes == null) return realIndex; - if (realIndex < 0) return -1; - if (realIndex < this.frozenCount && this.frozenModelIndexes != null) return this.frozenModelIndexes[realIndex]; - if (this.hiddenAndFrozenModelIndexes == null) return realIndex; - realIndex -= this.frozenCount; - for (let hidItem of this.hiddenAndFrozenModelIndexes) { - if (realIndex < hidItem) return realIndex; - realIndex++; - } - return realIndex; - } - modelToReal(modelIndex) { - if (this.hiddenAndFrozenModelIndexes == null && this.frozenModelIndexes == null) return modelIndex; - if (modelIndex < 0) return -1; - let frozenIndex = this.frozenModelIndexes != null ? _.indexOf(this.frozenModelIndexes, modelIndex) : -1; - if (frozenIndex >= 0) return frozenIndex; - if (this.hiddenAndFrozenModelIndexes == null) return modelIndex; - let hiddenIndex = _.sortedIndex(this.hiddenAndFrozenModelIndexes, modelIndex); - if (this.hiddenAndFrozenModelIndexes[hiddenIndex] == modelIndex) return -1; - if (hiddenIndex >= 0) return modelIndex - hiddenIndex + this.frozenCount; - return modelIndex; - } - getFrozenPosition(frozenIndex) { - return this.frozenItems[frozenIndex].position; - } - hasSizeOverride(modelIndex) { - return _.has(this.sizeOverridesByModelIndex, modelIndex); - } - isVisible(testedRealIndex, firstVisibleScrollIndex, viewportSize) { - if (testedRealIndex < 0) return false; - if (testedRealIndex >= 0 && testedRealIndex < this.frozenCount) return true; - let scrollIndex = testedRealIndex - this.frozenCount; - let onPageIndex = scrollIndex - firstVisibleScrollIndex; - return onPageIndex >= 0 && onPageIndex < this.getVisibleScrollCount(firstVisibleScrollIndex, viewportSize); - } -} diff --git a/packages/web/src/datagrid/SeriesSizes.ts b/packages/web/src/datagrid/SeriesSizes.ts deleted file mode 100644 index f221a4c56..000000000 --- a/packages/web/src/datagrid/SeriesSizes.ts +++ /dev/null @@ -1,341 +0,0 @@ -import _ from 'lodash'; - -export class SeriesSizeItem { - public scrollIndex: number = -1; - public modelIndex: number; - public frozenIndex: number = -1; - public size: number; - public position: number; - public get endPosition(): number { - return this.position + this.size; - } -} - -export class SeriesSizes { - private sizeOverridesByModelIndex: { [id: number]: number } = {}; - public count: number = 0; - public defaultSize: number = 50; - public maxSize: number = 1000; - private hiddenAndFrozenModelIndexes: number[] = []; - private frozenModelIndexes: number[] = []; - private hiddenModelIndexes: number[] = []; - private scrollItems: SeriesSizeItem[] = []; - private positions: number[] = []; - private scrollIndexes: number[] = []; - private frozenItems: SeriesSizeItem[] = []; - - public get scrollCount(): number { - return this.count - (this.hiddenAndFrozenModelIndexes != null ? this.hiddenAndFrozenModelIndexes.length : 0); - } - public get frozenCount(): number { - return this.frozenModelIndexes != null ? this.frozenModelIndexes.length : 0; - } - public get frozenSize(): number { - return _.sumBy(this.frozenItems, x => x.size); - } - public get realCount(): number { - return this.frozenCount + this.scrollCount; - } - - // public clear(): void { - // this.scrollItems = []; - // this.sizeOverridesByModelIndex = {}; - // this.positions = []; - // this.scrollIndexes = []; - // this.frozenItems = []; - // this.hiddenAndFrozenModelIndexes = null; - // this.frozenModelIndexes = null; - // } - public putSizeOverride(modelIndex: number, size: number, sizeByUser = false): void { - if (this.maxSize && size > this.maxSize && !sizeByUser) { - size = this.maxSize; - } - - let currentSize = this.sizeOverridesByModelIndex[modelIndex]; - if (sizeByUser || !currentSize || size > currentSize) { - this.sizeOverridesByModelIndex[modelIndex] = size; - } - // if (!_.has(this.sizeOverridesByModelIndex, modelIndex)) - // this.sizeOverridesByModelIndex[modelIndex] = size; - // if (size > this.sizeOverridesByModelIndex[modelIndex]) - // this.sizeOverridesByModelIndex[modelIndex] = size; - } - public buildIndex(): void { - this.scrollItems = []; - this.scrollIndexes = _.filter( - _.map(_.range(this.count), x => this.modelToReal(x) - this.frozenCount), - // _.map(this.intKeys(_.keys(this.sizeOverridesByModelIndex)), (x) => this.modelToReal(x) - this.frozenCount), - x => x >= 0 - ); - this.scrollIndexes.sort(); - let lastScrollIndex: number = -1; - let lastEndPosition: number = 0; - this.scrollIndexes.forEach(scrollIndex => { - let modelIndex: number = this.realToModel(scrollIndex + this.frozenCount); - let size: number = this.sizeOverridesByModelIndex[modelIndex]; - let item = new SeriesSizeItem(); - item.scrollIndex = scrollIndex; - item.modelIndex = modelIndex; - item.size = size; - item.position = lastEndPosition + (scrollIndex - lastScrollIndex - 1) * this.defaultSize; - this.scrollItems.push(item); - lastScrollIndex = scrollIndex; - lastEndPosition = item.endPosition; - }); - this.positions = _.map(this.scrollItems, x => x.position); - this.frozenItems = []; - let lastpos: number = 0; - for (let i: number = 0; i < this.frozenCount; i++) { - let modelIndex: number = this.frozenModelIndexes[i]; - let size: number = this.getSizeByModelIndex(modelIndex); - let item = new SeriesSizeItem(); - item.frozenIndex = i; - item.modelIndex = modelIndex; - item.size = size; - item.position = lastpos; - this.frozenItems.push(item); - lastpos += size; - } - } - - public getScrollIndexOnPosition(position: number): number { - let itemOrder: number = _.sortedIndex(this.positions, position); - if (this.positions[itemOrder] == position) return itemOrder; - if (itemOrder == 0) return Math.floor(position / this.defaultSize); - if (position <= this.scrollItems[itemOrder - 1].endPosition) return this.scrollItems[itemOrder - 1].scrollIndex; - return ( - Math.floor((position - this.scrollItems[itemOrder - 1].position) / this.defaultSize) + - this.scrollItems[itemOrder - 1].scrollIndex - ); - } - public getFrozenIndexOnPosition(position: number): number { - this.frozenItems.forEach(function (item) { - if (position >= item.position && position <= item.endPosition) return item.frozenIndex; - }); - return -1; - } - // public getSizeSum(startScrollIndex: number, endScrollIndex: number): number { - // let order1: number = _.sortedIndexOf(this.scrollIndexes, startScrollIndex); - // let order2: number = _.sortedIndexOf(this.scrollIndexes, endScrollIndex); - // let count: number = endScrollIndex - startScrollIndex; - // if (order1 < 0) - // order1 = ~order1; - // if (order2 < 0) - // order2 = ~order2; - // let result: number = 0; - // for (let i: number = order1; i <= order2; i++) { - // if (i < 0) - // continue; - // if (i >= this.scrollItems.length) - // continue; - // let item = this.scrollItems[i]; - // if (item.scrollIndex < startScrollIndex) - // continue; - // if (item.scrollIndex >= endScrollIndex) - // continue; - // result += item.size; - // count--; - // } - // result += count * this.defaultSize; - // return result; - // } - public getSizeByModelIndex(modelIndex: number): number { - if (_.has(this.sizeOverridesByModelIndex, modelIndex)) return this.sizeOverridesByModelIndex[modelIndex]; - return this.defaultSize; - } - public getSizeByScrollIndex(scrollIndex: number): number { - return this.getSizeByRealIndex(scrollIndex + this.frozenCount); - } - public getSizeByRealIndex(realIndex: number): number { - let modelIndex: number = this.realToModel(realIndex); - return this.getSizeByModelIndex(modelIndex); - } - public removeSizeOverride(realIndex: number): void { - let modelIndex: number = this.realToModel(realIndex); - delete this.sizeOverridesByModelIndex[modelIndex]; - } - public getScroll(sourceScrollIndex: number, targetScrollIndex: number): number { - if (sourceScrollIndex < targetScrollIndex) { - return -_.sum( - _.map(_.range(sourceScrollIndex, targetScrollIndex - sourceScrollIndex), x => this.getSizeByScrollIndex(x)) - ); - } else { - return _.sum( - _.map(_.range(targetScrollIndex, sourceScrollIndex - targetScrollIndex), x => this.getSizeByScrollIndex(x)) - ); - } - } - public modelIndexIsInScrollArea(modelIndex: number): boolean { - let realIndex = this.modelToReal(modelIndex); - return realIndex >= this.frozenCount; - } - public getTotalScrollSizeSum(): number { - let scrollSizeOverrides = _.map( - _.filter(this.intKeys(this.sizeOverridesByModelIndex), x => this.modelIndexIsInScrollArea(x)), - x => this.sizeOverridesByModelIndex[x] - ); - return _.sum(scrollSizeOverrides) + (this.count - scrollSizeOverrides.length) * this.defaultSize; - } - public getVisibleScrollSizeSum(): number { - let scrollSizeOverrides = _.map( - _.filter(this.intKeys(this.sizeOverridesByModelIndex), x => !_.includes(this.hiddenAndFrozenModelIndexes, x)), - x => this.sizeOverridesByModelIndex[x] - ); - return ( - _.sum(scrollSizeOverrides) + - (this.count - this.hiddenModelIndexes.length - scrollSizeOverrides.length) * this.defaultSize - ); - } - private intKeys(value): number[] { - return _.keys(value).map(x => _.parseInt(x)); - } - public getPositionByRealIndex(realIndex: number): number { - if (realIndex < 0) return 0; - if (realIndex < this.frozenCount) return this.frozenItems[realIndex].position; - return this.getPositionByScrollIndex(realIndex - this.frozenCount); - } - public getPositionByScrollIndex(scrollIndex: number): number { - let order: number = _.sortedIndex(this.scrollIndexes, scrollIndex); - if (this.scrollIndexes[order] == scrollIndex) return this.scrollItems[order].position; - order--; - if (order < 0) return scrollIndex * this.defaultSize; - return ( - this.scrollItems[order].endPosition + (scrollIndex - this.scrollItems[order].scrollIndex - 1) * this.defaultSize - ); - } - public getVisibleScrollCount(firstVisibleIndex: number, viewportSize: number): number { - let res: number = 0; - let index: number = firstVisibleIndex; - let count: number = 0; - while (res < viewportSize && index <= this.scrollCount) { - res += this.getSizeByScrollIndex(index); - index++; - count++; - } - return count; - } - public getVisibleScrollCountReversed(lastVisibleIndex: number, viewportSize: number): number { - let res: number = 0; - let index: number = lastVisibleIndex; - let count: number = 0; - while (res < viewportSize && index >= 0) { - res += this.getSizeByScrollIndex(index); - index--; - count++; - } - return count; - } - public invalidateAfterScroll( - oldFirstVisible: number, - newFirstVisible: number, - invalidate: (_: number) => void, - viewportSize: number - ): void { - if (newFirstVisible > oldFirstVisible) { - let oldVisibleCount: number = this.getVisibleScrollCount(oldFirstVisible, viewportSize); - let newVisibleCount: number = this.getVisibleScrollCount(newFirstVisible, viewportSize); - for (let i: number = oldFirstVisible + oldVisibleCount - 1; i <= newFirstVisible + newVisibleCount; i++) { - invalidate(i + this.frozenCount); - } - } else { - for (let i: number = newFirstVisible; i <= oldFirstVisible; i++) { - invalidate(i + this.frozenCount); - } - } - } - public isWholeInView(firstVisibleIndex: number, index: number, viewportSize: number): boolean { - let res: number = 0; - let testedIndex: number = firstVisibleIndex; - while (res < viewportSize && testedIndex < this.count) { - res += this.getSizeByScrollIndex(testedIndex); - if (testedIndex == index) return res <= viewportSize; - testedIndex++; - } - return false; - } - public scrollInView(firstVisibleIndex: number, scrollIndex: number, viewportSize: number): number { - if (this.isWholeInView(firstVisibleIndex, scrollIndex, viewportSize)) { - return firstVisibleIndex; - } - if (scrollIndex < firstVisibleIndex) { - return scrollIndex; - } - let testedIndex = firstVisibleIndex + 1; - while (testedIndex < this.scrollCount) { - if (this.isWholeInView(testedIndex, scrollIndex, viewportSize)) { - return testedIndex; - } - testedIndex++; - } - return this.scrollCount - 1; - - // let res: number = 0; - // let testedIndex: number = scrollIndex; - // while (res < viewportSize && testedIndex >= 0) { - // let size: number = this.getSizeByScrollIndex(testedIndex); - // if (res + size > viewportSize) return testedIndex + 1; - // testedIndex--; - // res += size; - // } - // if (res >= viewportSize && testedIndex < scrollIndex) return testedIndex + 1; - // return firstVisibleIndex; - } - public resize(realIndex: number, newSize: number): void { - if (realIndex < 0) return; - let modelIndex: number = this.realToModel(realIndex); - if (modelIndex < 0) return; - this.sizeOverridesByModelIndex[modelIndex] = newSize; - this.buildIndex(); - } - public setExtraordinaryIndexes(hidden: number[], frozen: number[]): void { - //this._hiddenAndFrozenModelIndexes = _.clone(hidden); - hidden = hidden.filter(x => x >= 0); - frozen = frozen.filter(x => x >= 0); - - hidden.sort((a, b) => a - b); - frozen.sort((a, b) => a - b); - this.frozenModelIndexes = _.filter(frozen, x => !_.includes(hidden, x)); - this.hiddenModelIndexes = _.filter(hidden, x => !_.includes(frozen, x)); - this.hiddenAndFrozenModelIndexes = _.concat(hidden, this.frozenModelIndexes); - this.frozenModelIndexes.sort((a, b) => a - b); - if (this.hiddenAndFrozenModelIndexes.length == 0) this.hiddenAndFrozenModelIndexes = null; - if (this.frozenModelIndexes.length == 0) this.frozenModelIndexes = null; - this.buildIndex(); - } - public realToModel(realIndex: number): number { - if (this.hiddenAndFrozenModelIndexes == null && this.frozenModelIndexes == null) return realIndex; - if (realIndex < 0) return -1; - if (realIndex < this.frozenCount && this.frozenModelIndexes != null) return this.frozenModelIndexes[realIndex]; - if (this.hiddenAndFrozenModelIndexes == null) return realIndex; - realIndex -= this.frozenCount; - for (let hidItem of this.hiddenAndFrozenModelIndexes) { - if (realIndex < hidItem) return realIndex; - realIndex++; - } - return realIndex; - } - public modelToReal(modelIndex: number): number { - if (this.hiddenAndFrozenModelIndexes == null && this.frozenModelIndexes == null) return modelIndex; - if (modelIndex < 0) return -1; - let frozenIndex: number = this.frozenModelIndexes != null ? _.indexOf(this.frozenModelIndexes, modelIndex) : -1; - if (frozenIndex >= 0) return frozenIndex; - if (this.hiddenAndFrozenModelIndexes == null) return modelIndex; - let hiddenIndex: number = _.sortedIndex(this.hiddenAndFrozenModelIndexes, modelIndex); - if (this.hiddenAndFrozenModelIndexes[hiddenIndex] == modelIndex) return -1; - if (hiddenIndex >= 0) return modelIndex - hiddenIndex + this.frozenCount; - return modelIndex; - } - public getFrozenPosition(frozenIndex: number): number { - return this.frozenItems[frozenIndex].position; - } - public hasSizeOverride(modelIndex: number): boolean { - return _.has(this.sizeOverridesByModelIndex, modelIndex); - } - public isVisible(testedRealIndex: number, firstVisibleScrollIndex: number, viewportSize: number): boolean { - if (testedRealIndex < 0) return false; - if (testedRealIndex >= 0 && testedRealIndex < this.frozenCount) return true; - let scrollIndex: number = testedRealIndex - this.frozenCount; - let onPageIndex: number = scrollIndex - firstVisibleScrollIndex; - return onPageIndex >= 0 && onPageIndex < this.getVisibleScrollCount(firstVisibleScrollIndex, viewportSize); - } -} diff --git a/packages/web/src/datagrid/SqlDataGridCore.js b/packages/web/src/datagrid/SqlDataGridCore.js deleted file mode 100644 index fd3fc60af..000000000 --- a/packages/web/src/datagrid/SqlDataGridCore.js +++ /dev/null @@ -1,177 +0,0 @@ -import React from 'react'; -import axios from '../utility/axios'; -import { useSetOpenedTabs } from '../utility/globalState'; -import DataGridCore from './DataGridCore'; -import useSocket from '../utility/SocketProvider'; -import useShowModal from '../modals/showModal'; -import ImportExportModal from '../modals/ImportExportModal'; -import { changeSetToSql, createChangeSet, getChangeSetInsertedRows } from 'dbgate-datalib'; -import LoadingDataGridCore from './LoadingDataGridCore'; -import ChangeSetGrider from './ChangeSetGrider'; -import { scriptToSql } from 'dbgate-sqltree'; -import useModalState from '../modals/useModalState'; -import ConfirmSqlModal from '../modals/ConfirmSqlModal'; -import ErrorMessageModal from '../modals/ErrorMessageModal'; -import useOpenNewTab from '../utility/useOpenNewTab'; - -/** @param props {import('./types').DataGridProps} */ -async function loadDataPage(props, offset, limit) { - const { display, conid, database } = props; - - const sql = display.getPageQuery(offset, limit); - - const response = await axios.request({ - url: 'database-connections/query-data', - method: 'post', - params: { - conid, - database, - }, - data: { sql }, - }); - - if (response.data.errorMessage) return response.data; - return response.data.rows; -} - -function dataPageAvailable(props) { - const { display } = props; - const sql = display.getPageQuery(0, 1); - return !!sql; -} - -async function loadRowCount(props) { - const { display, conid, database } = props; - - const sql = display.getCountQuery(); - - const response = await axios.request({ - url: 'database-connections/query-data', - method: 'post', - params: { - conid, - database, - }, - data: { sql }, - }); - - return parseInt(response.data.rows[0].count); -} - -/** @param props {import('./types').DataGridProps} */ -export default function SqlDataGridCore(props) { - const { conid, database, display, changeSetState, dispatchChangeSet } = props; - const showModal = useShowModal(); - const openNewTab = useOpenNewTab(); - - const confirmSqlModalState = useModalState(); - const [confirmSql, setConfirmSql] = React.useState(''); - - const changeSet = changeSetState && changeSetState.value; - const changeSetRef = React.useRef(changeSet); - changeSetRef.current = changeSet; - - function exportGrid() { - const initialValues = {}; - initialValues.sourceStorageType = 'query'; - initialValues.sourceConnectionId = conid; - initialValues.sourceDatabaseName = database; - initialValues.sourceSql = display.getExportQuery(); - initialValues.sourceList = display.baseTable ? [display.baseTable.pureName] : []; - showModal(modalState => ); - } - function openActiveChart() { - openNewTab( - { - title: 'Chart #', - icon: 'img chart', - tabComponent: 'ChartTab', - props: { - conid, - database, - }, - }, - { - editor: { - config: { chartType: 'bar' }, - sql: display.getExportQuery(select => { - select.orderBy = null; - }), - }, - } - ); - } - function openQuery() { - openNewTab( - { - title: 'Query #', - icon: 'img sql-file', - tabComponent: 'QueryTab', - props: { - schemaName: display.baseTable.schemaName, - pureName: display.baseTable.pureName, - conid, - database, - }, - }, - { - editor: display.getExportQuery(), - } - ); - } - - function handleSave() { - const script = changeSetToSql(changeSetRef.current, display.dbinfo); - const sql = scriptToSql(display.driver, script); - setConfirmSql(sql); - confirmSqlModalState.open(); - } - - async function handleConfirmSql() { - const resp = await axios.request({ - url: 'database-connections/query-data', - method: 'post', - params: { - conid, - database, - }, - data: { sql: confirmSql }, - }); - const { errorMessage } = resp.data || {}; - if (errorMessage) { - showModal(modalState => ( - - )); - } else { - dispatchChangeSet({ type: 'reset', value: createChangeSet() }); - setConfirmSql(null); - display.reload(); - } - } - - // const grider = React.useMemo(()=>new ChangeSetGrider()) - - return ( - <> - - - - ); -} diff --git a/packages/web/src/datagrid/TableDataGrid.js b/packages/web/src/datagrid/TableDataGrid.js deleted file mode 100644 index e7ab591e8..000000000 --- a/packages/web/src/datagrid/TableDataGrid.js +++ /dev/null @@ -1,232 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import DataGrid from './DataGrid'; -import styled from 'styled-components'; -import { TableGridDisplay, TableFormViewDisplay, createGridConfig, createGridCache } from 'dbgate-datalib'; -import { getFilterValueExpression } from 'dbgate-filterparser'; -import { findEngineDriver } from 'dbgate-tools'; -import { useConnectionInfo, getTableInfo, useDatabaseInfo } from '../utility/metadataLoaders'; -import useSocket from '../utility/SocketProvider'; -import { VerticalSplitter } from '../widgets/Splitter'; -import stableStringify from 'json-stable-stringify'; -import ReferenceHeader from './ReferenceHeader'; -import SqlDataGridCore from './SqlDataGridCore'; -import useExtensions from '../utility/useExtensions'; -import SqlFormView from '../formview/SqlFormView'; - -const ReferenceContainer = styled.div` - position: absolute; - display: flex; - flex-direction: column; - top: 0; - left: 0; - right: 0; - bottom: 0; -`; - -const ReferenceGridWrapper = styled.div` - position: relative; - flex: 1; - display: flex; -`; - -export default function TableDataGrid({ - conid, - database, - schemaName, - pureName, - tabVisible, - toolbarPortalRef, - changeSetState, - dispatchChangeSet, - config = undefined, - setConfig = undefined, - cache = undefined, - setCache = undefined, - masterLoadedTime = undefined, - isDetailView = false, -}) { - // const [childConfig, setChildConfig] = React.useState(createGridConfig()); - const [myCache, setMyCache] = React.useState(createGridCache()); - const [childCache, setChildCache] = React.useState(createGridCache()); - const [refReloadToken, setRefReloadToken] = React.useState(0); - const [myLoadedTime, setMyLoadedTime] = React.useState(0); - const extensions = useExtensions(); - - const { childConfig } = config; - const setChildConfig = (value, reference = undefined) => { - if (_.isFunction(value)) { - setConfig(x => ({ - ...x, - childConfig: value(x.childConfig), - })); - } else { - setConfig(x => ({ - ...x, - childConfig: value, - reference: reference === undefined ? x.reference : reference, - })); - } - }; - const { reference } = config; - - const connection = useConnectionInfo({ conid }); - const dbinfo = useDatabaseInfo({ conid, database }); - // const [reference, setReference] = React.useState(null); - - function createDisplay() { - return connection - ? new TableGridDisplay( - { schemaName, pureName }, - findEngineDriver(connection, extensions), - config, - setConfig, - cache || myCache, - setCache || setMyCache, - dbinfo - ) - : null; - } - - function createFormDisplay() { - return connection - ? new TableFormViewDisplay( - { schemaName, pureName }, - findEngineDriver(connection, extensions), - config, - setConfig, - cache || myCache, - setCache || setMyCache, - dbinfo - ) - : null; - } - - const [display, setDisplay] = React.useState(createDisplay()); - const [formDisplay, setFormDisplay] = React.useState(createFormDisplay()); - - React.useEffect(() => { - setRefReloadToken(v => v + 1); - if (!reference && display && display.isGrouped) display.clearGrouping(); - }, [reference]); - - React.useEffect(() => { - const newDisplay = createDisplay(); - if (!newDisplay) return; - if (display && display.isLoadedCorrectly && !newDisplay.isLoadedCorrectly) return; - setDisplay(newDisplay); - }, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo, extensions]); - - React.useEffect(() => { - const newDisplay = createFormDisplay(); - if (!newDisplay) return; - if (formDisplay && formDisplay.isLoadedCorrectly && !newDisplay.isLoadedCorrectly) return; - setFormDisplay(newDisplay); - }, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo, extensions]); - - const handleDatabaseStructureChanged = React.useCallback(() => { - (setCache || setMyCache)(createGridCache()); - }, []); - - const socket = useSocket(); - - React.useEffect(() => { - if (display && !display.isLoadedCorrectly) { - if (conid && socket) { - socket.on(`database-structure-changed-${conid}-${database}`, handleDatabaseStructureChanged); - return () => { - socket.off(`database-structure-changed-${conid}-${database}`, handleDatabaseStructureChanged); - }; - } - } - }, [conid, database, display]); - - const handleReferenceSourceChanged = React.useCallback( - (selectedRows, loadedTime) => { - setMyLoadedTime(loadedTime); - if (!reference) return; - - const filtersBase = display && display.isGrouped ? config.filters : childConfig.filters; - - const filters = { - ...filtersBase, - ..._.fromPairs( - reference.columns.map(col => [ - col.refName, - selectedRows.map(x => getFilterValueExpression(x[col.baseName], col.dataType)).join(', '), - ]) - ), - }; - if (stableStringify(filters) != stableStringify(childConfig.filters)) { - setChildConfig(cfg => ({ - ...cfg, - filters, - })); - setChildCache(ca => ({ - ...ca, - refreshTime: new Date().getTime(), - })); - } - }, - [childConfig, reference] - ); - - const handleCloseReference = () => { - setChildConfig(null, null); - }; - - if (!display) return null; - - return ( - - setChildConfig(createGridConfig(), reference)} - onReferenceSourceChanged={reference ? handleReferenceSourceChanged : null} - refReloadToken={refReloadToken.toString()} - masterLoadedTime={masterLoadedTime} - GridCore={SqlDataGridCore} - FormView={SqlFormView} - isDetailView={isDetailView} - // tableInfo={ - // dbinfo && dbinfo.tables && dbinfo.tables.find((x) => x.pureName == pureName && x.schemaName == schemaName) - // } - /> - {reference && ( - - - - - - - )} - - ); -} diff --git a/packages/web/src/datagrid/gridutil.ts b/packages/web/src/datagrid/gridutil.ts deleted file mode 100644 index bf64c2a94..000000000 --- a/packages/web/src/datagrid/gridutil.ts +++ /dev/null @@ -1,144 +0,0 @@ -import _ from 'lodash'; -import { SeriesSizes } from './SeriesSizes'; -import { CellAddress } from './selection'; -import { GridDisplay } from 'dbgate-datalib'; -import Grider from './Grider'; - -export function countColumnSizes(grider: Grider, columns, containerWidth, display: GridDisplay) { - const columnSizes = new SeriesSizes(); - if (!grider || !columns) return columnSizes; - - let canvas = document.createElement('canvas'); - let context = canvas.getContext('2d'); - - //return this.context.measureText(txt).width; - - // console.log('countColumnSizes', loadedRows.length, containerWidth); - - columnSizes.maxSize = (containerWidth * 2) / 3; - columnSizes.count = columns.length; - - // columnSizes.setExtraordinaryIndexes(this.getHiddenColumnIndexes(), this.getFrozenColumnIndexes()); - // console.log('display.hiddenColumnIndexes', display.hiddenColumnIndexes) - - columnSizes.setExtraordinaryIndexes(display.hiddenColumnIndexes, []); - - for (let colIndex = 0; colIndex < columns.length; colIndex++) { - //this.columnSizes.PutSizeOverride(col, this.columns[col].Name.length * 8); - const column = columns[colIndex]; - - if (display.config.columnWidths[column.uniqueName]) { - columnSizes.putSizeOverride(colIndex, display.config.columnWidths[column.uniqueName]); - continue; - } - - // if (column.columnClientObject != null && column.columnClientObject.notNull) context.font = "bold 14px Helvetica"; - // else context.font = "14px Helvetica"; - context.font = 'bold 14px Helvetica'; - - const text = column.headerText; - const headerWidth = context.measureText(text).width + 64; - - // if (column.columnClientObject != null && column.columnClientObject.icon != null) headerWidth += 16; - // if (this.getFilterOnColumn(column.uniquePath)) headerWidth += 16; - // if (this.getSortOrder(column.uniquePath)) headerWidth += 16; - - columnSizes.putSizeOverride(colIndex, headerWidth); - } - - // let headerWidth = this.rowHeaderWidthDefault; - // if (this.rowCount) headerWidth = context.measureText(this.rowCount.toString()).width + 8; - // this.rowHeaderWidth = this.rowHeaderWidthDefault; - // if (headerWidth > this.rowHeaderWidth) this.rowHeaderWidth = headerWidth; - - context.font = '14px Helvetica'; - for (let rowIndex = 0; rowIndex < Math.min(grider.rowCount, 20); rowIndex += 1) { - const row = grider.getRowData(rowIndex); - for (let colIndex = 0; colIndex < columns.length; colIndex++) { - const uqName = columns[colIndex].uniqueName; - - if (display.config.columnWidths[uqName]) { - continue; - } - - const text = row[uqName]; - const width = context.measureText(text).width + 8; - // console.log('colName', colName, text, width); - columnSizes.putSizeOverride(colIndex, width); - // let colName = this.columns[colIndex].uniquePath; - // let text: string = row[colName].gridText; - // let width = context.measureText(text).width + 8; - // if (row[colName].dataPrefix) width += context.measureText(row[colName].dataPrefix).width + 3; - // this.columnSizes.putSizeOverride(colIndex, width); - } - } - - // for (let modelIndex = 0; modelIndex < this.columns.length; modelIndex++) { - // let width = getHashValue(this.widthHashPrefix + this.columns[modelIndex].uniquePath); - // if (width) this.columnSizes.putSizeOverride(modelIndex, _.toNumber(width), true); - // } - - columnSizes.buildIndex(); - return columnSizes; -} - -export function countVisibleRealColumns(columnSizes, firstVisibleColumnScrollIndex, gridScrollAreaWidth, columns) { - const visibleColumnCount = columnSizes.getVisibleScrollCount(firstVisibleColumnScrollIndex, gridScrollAreaWidth); - // console.log('visibleColumnCount', visibleColumnCount); - // console.log('gridScrollAreaWidth', gridScrollAreaWidth); - - const visibleRealColumnIndexes = []; - const modelIndexes = {}; - /** @type {(import('dbgate-datalib').DisplayColumn & {widthPx: string; colIndex: number})[]} */ - const realColumns = []; - - // frozen columns - for (let colIndex = 0; colIndex < columnSizes.frozenCount; colIndex++) { - visibleRealColumnIndexes.push(colIndex); - } - // scroll columns - for ( - let colIndex = firstVisibleColumnScrollIndex; - colIndex < firstVisibleColumnScrollIndex + visibleColumnCount; - colIndex++ - ) { - visibleRealColumnIndexes.push(colIndex + columnSizes.frozenCount); - } - - // real columns - for (let colIndex of visibleRealColumnIndexes) { - let modelColumnIndex = columnSizes.realToModel(colIndex); - modelIndexes[colIndex] = modelColumnIndex; - - let col = columns[modelColumnIndex]; - if (!col) continue; - const widthNumber = columnSizes.getSizeByRealIndex(colIndex); - realColumns.push({ - ...col, - colIndex, - widthNumber, - widthPx: `${widthNumber}px`, - }); - } - return realColumns; -} - -export function filterCellForRow(cell, row: number): CellAddress | null { - return cell && (cell[0] == row || _.isString(cell[0])) ? cell : null; -} - -export function filterCellsForRow(cells, row: number): CellAddress[] | null { - const res = (cells || []).filter(x => x[0] == row || _.isString(x[0])); - return res.length > 0 ? res : null; -} - -export function cellIsSelected(row, col, selectedCells) { - if (!selectedCells) return false; - for (const [selectedRow, selectedCol] of selectedCells) { - if (row == selectedRow && col == selectedCol) return true; - if (selectedRow == 'header' && col == selectedCol) return true; - if (row == selectedRow && selectedCol == 'header') return true; - if (selectedRow == 'header' && selectedCol == 'header') return true; - } - return false; -} diff --git a/packages/web/src/datagrid/selection.ts b/packages/web/src/datagrid/selection.ts deleted file mode 100644 index 28ff9384a..000000000 --- a/packages/web/src/datagrid/selection.ts +++ /dev/null @@ -1,69 +0,0 @@ -import _ from 'lodash'; -export type CellAddress = [number | 'header' | 'filter' | undefined, number | 'header' | undefined]; -export type RegularCellAddress = [number, number]; - -export const topLeftCell: CellAddress = [0, 0]; -export const undefinedCell: CellAddress = [undefined, undefined]; -export const nullCell: CellAddress = null; -export const emptyCellArray: CellAddress[] = []; - -export function isRegularCell(cell: CellAddress): cell is RegularCellAddress { - if (!cell) return false; - const [row, col] = cell; - return _.isNumber(row) && _.isNumber(col); -} - -export function getCellRange(a: CellAddress, b: CellAddress): CellAddress[] { - const [rowA, colA] = a; - const [rowB, colB] = b; - - if (_.isNumber(rowA) && _.isNumber(colA) && _.isNumber(rowB) && _.isNumber(colB)) { - const rowMin = Math.min(rowA, rowB); - const rowMax = Math.max(rowA, rowB); - const colMin = Math.min(colA, colB); - const colMax = Math.max(colA, colB); - const res = []; - for (let row = rowMin; row <= rowMax; row++) { - for (let col = colMin; col <= colMax; col++) { - res.push([row, col]); - } - } - return res; - } - if (rowA == 'header' && rowB == 'header' && _.isNumber(colA) && _.isNumber(colB)) { - const colMin = Math.min(colA, colB); - const colMax = Math.max(colA, colB); - const res = []; - for (let col = colMin; col <= colMax; col++) { - res.push(['header', col]); - } - return res; - } - if (colA == 'header' && colB == 'header' && _.isNumber(rowA) && _.isNumber(rowB)) { - const rowMin = Math.min(rowA, rowB); - const rowMax = Math.max(rowA, rowB); - const res = []; - for (let row = rowMin; row <= rowMax; row++) { - res.push([row, 'header']); - } - return res; - } - if (colA == 'header' && colB == 'header' && rowA == 'header' && rowB == 'header') { - return [['header', 'header']]; - } - return []; -} - -export function convertCellAddress(row, col): CellAddress { - const rowNumber = parseInt(row); - const colNumber = parseInt(col); - return [_.isNaN(rowNumber) ? row : rowNumber, _.isNaN(colNumber) ? col : colNumber]; -} - -export function cellFromEvent(event): CellAddress { - const cell = event.target.closest('td'); - if (!cell) return undefinedCell; - const col = cell.getAttribute('data-col'); - const row = cell.getAttribute('data-row'); - return convertCellAddress(row, col); -} diff --git a/packages/web/src/datagrid/types.ts b/packages/web/src/datagrid/types.ts deleted file mode 100644 index c6f0791b9..000000000 --- a/packages/web/src/datagrid/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { GridDisplay, ChangeSet, GridReferenceDefinition } from 'dbgate-datalib'; -import Grider from './Grider'; - -export interface DataGridProps { - display: GridDisplay; - tabVisible?: boolean; - changeSetState?: { value: ChangeSet }; - dispatchChangeSet?: Function; - toolbarPortalRef?: any; - showReferences?: boolean; - onReferenceClick?: (def: GridReferenceDefinition) => void; - onReferenceSourceChanged?: Function; - refReloadToken?: string; - masterLoadedTime?: number; - managerSize?: number; - grider?: Grider; - conid?: string; - database?: string; - jslid?: string; - - [field: string]: any; -} - -// export interface DataGridCoreProps extends DataGridProps { -// rows: any[]; -// loadNextData?: Function; -// exportGrid?: Function; -// openQuery?: Function; -// undo?: Function; -// redo?: Function; - -// errorMessage?: string; -// isLoadedAll?: boolean; -// loadedTime?: any; -// allRowCount?: number; -// conid?: string; -// database?: string; -// insertedRowCount?: number; -// isLoading?: boolean; -// } - -// export interface LoadingDataGridProps extends DataGridProps { -// conid?: string; -// database?: string; -// jslid?: string; -// } diff --git a/packages/web/src/designer/Designer.js b/packages/web/src/designer/Designer.js deleted file mode 100644 index 076799aac..000000000 --- a/packages/web/src/designer/Designer.js +++ /dev/null @@ -1,352 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import DesignerTable from './DesignerTable'; -import uuidv1 from 'uuid/v1'; -import _ from 'lodash'; -import useTheme from '../theme/useTheme'; -import DesignerReference from './DesignerReference'; -import cleanupDesignColumns from './cleanupDesignColumns'; -import { isConnectedByReference } from './designerTools'; -import { getTableInfo } from '../utility/metadataLoaders'; - -const Wrapper = styled.div` - flex: 1; - background-color: ${props => props.theme.designer_background}; - overflow: scroll; -`; - -const Canvas = styled.div` - width: 3000px; - height: 3000px; - position: relative; -`; - -const EmptyInfo = styled.div` - margin: 50px; - font-size: 20px; -`; - -function fixPositions(tables) { - const minLeft = _.min(tables.map(x => x.left)); - const minTop = _.min(tables.map(x => x.top)); - if (minLeft < 0 || minTop < 0) { - const dLeft = minLeft < 0 ? -minLeft : 0; - const dTop = minTop < 0 ? -minTop : 0; - return tables.map(tbl => ({ - ...tbl, - left: tbl.left + dLeft, - top: tbl.top + dTop, - })); - } - return tables; -} - -export default function Designer({ value, onChange, conid, database }) { - const { tables, references } = value || {}; - const theme = useTheme(); - - const [sourceDragColumn, setSourceDragColumn] = React.useState(null); - const [targetDragColumn, setTargetDragColumn] = React.useState(null); - const domTablesRef = React.useRef({}); - const wrapperRef = React.useRef(); - const [changeToken, setChangeToken] = React.useState(0); - - const handleDrop = e => { - var data = e.dataTransfer.getData('app_object_drag_data'); - e.preventDefault(); - if (!data) return; - const rect = e.target.getBoundingClientRect(); - var json = JSON.parse(data); - const { objectTypeField } = json; - if (objectTypeField != 'tables' && objectTypeField != 'views') return; - json.designerId = uuidv1(); - json.left = e.clientX - rect.left; - json.top = e.clientY - rect.top; - - onChange(current => { - const foreignKeys = _.compact([ - ...(json.foreignKeys || []).map(fk => { - const tables = ((current || {}).tables || []).filter( - tbl => fk.refTableName == tbl.pureName && fk.refSchemaName == tbl.schemaName - ); - if (tables.length == 1) - return { - ...fk, - sourceId: json.designerId, - targetId: tables[0].designerId, - }; - return null; - }), - ..._.flatten( - ((current || {}).tables || []).map(tbl => - (tbl.foreignKeys || []).map(fk => { - if (fk.refTableName == json.pureName && fk.refSchemaName == json.schemaName) { - return { - ...fk, - sourceId: tbl.designerId, - targetId: json.designerId, - }; - } - return null; - }) - ) - ), - ]); - - return { - ...current, - tables: [...((current || {}).tables || []), json], - references: - foreignKeys.length == 1 - ? [ - ...((current || {}).references || []), - { - designerId: uuidv1(), - sourceId: foreignKeys[0].sourceId, - targetId: foreignKeys[0].targetId, - joinType: 'INNER JOIN', - columns: foreignKeys[0].columns.map(col => ({ - source: col.columnName, - target: col.refColumnName, - })), - }, - ] - : (current || {}).references, - }; - }); - }; - - const changeTable = React.useCallback( - table => { - onChange(current => ({ - ...current, - tables: fixPositions((current.tables || []).map(x => (x.designerId == table.designerId ? table : x))), - })); - }, - [onChange] - ); - - const bringToFront = React.useCallback( - table => { - onChange( - current => ({ - ...current, - tables: [...(current.tables || []).filter(x => x.designerId != table.designerId), table], - }), - true - ); - }, - [onChange] - ); - - const removeTable = React.useCallback( - table => { - onChange(current => ({ - ...current, - tables: (current.tables || []).filter(x => x.designerId != table.designerId), - references: (current.references || []).filter( - x => x.sourceId != table.designerId && x.targetId != table.designerId - ), - columns: (current.columns || []).filter(x => x.designerId != table.designerId), - })); - }, - [onChange] - ); - - const changeReference = React.useCallback( - ref => { - onChange(current => ({ - ...current, - references: (current.references || []).map(x => (x.designerId == ref.designerId ? ref : x)), - })); - }, - [onChange] - ); - - const removeReference = React.useCallback( - ref => { - onChange(current => ({ - ...current, - references: (current.references || []).filter(x => x.designerId != ref.designerId), - })); - }, - [onChange] - ); - - const handleCreateReference = (source, target) => { - onChange(current => { - const existingReference = (current.references || []).find( - x => - (x.sourceId == source.designerId && x.targetId == target.designerId) || - (x.sourceId == target.designerId && x.targetId == source.designerId) - ); - - return { - ...current, - references: existingReference - ? current.references.map(ref => - ref == existingReference - ? { - ...existingReference, - columns: [ - ...existingReference.columns, - existingReference.sourceId == source.designerId - ? { - source: source.columnName, - target: target.columnName, - } - : { - source: target.columnName, - target: source.columnName, - }, - ], - } - : ref - ) - : [ - ...(current.references || []), - { - designerId: uuidv1(), - sourceId: source.designerId, - targetId: target.designerId, - joinType: isConnectedByReference(current, source, target, null) ? 'CROSS JOIN' : 'INNER JOIN', - columns: [ - { - source: source.columnName, - target: target.columnName, - }, - ], - }, - ], - }; - }); - }; - - const handleAddReferenceByColumn = async (designerId, foreignKey) => { - const toTable = await getTableInfo({ - conid, - database, - pureName: foreignKey.refTableName, - schemaName: foreignKey.refSchemaName, - }); - const newTableDesignerId = uuidv1(); - onChange(current => { - const fromTable = (current.tables || []).find(x => x.designerId == designerId); - if (!fromTable) return; - return { - ...current, - tables: [ - ...(current.tables || []), - { - ...toTable, - left: fromTable.left + 300, - top: fromTable.top + 50, - designerId: newTableDesignerId, - }, - ], - references: [ - ...(current.references || []), - { - designerId: uuidv1(), - sourceId: fromTable.designerId, - targetId: newTableDesignerId, - joinType: 'INNER JOIN', - columns: foreignKey.columns.map(col => ({ - source: col.columnName, - target: col.refColumnName, - })), - }, - ], - }; - }); - }; - - const handleSelectColumn = React.useCallback( - column => { - onChange( - current => ({ - ...current, - columns: (current.columns || []).find( - x => x.designerId == column.designerId && x.columnName == column.columnName - ) - ? current.columns - : [...cleanupDesignColumns(current.columns), _.pick(column, ['designerId', 'columnName'])], - }), - true - ); - }, - [onChange] - ); - - const handleChangeColumn = React.useCallback( - (column, changeFunc) => { - onChange(current => { - const currentColumns = (current || {}).columns || []; - const existing = currentColumns.find( - x => x.designerId == column.designerId && x.columnName == column.columnName - ); - if (existing) { - return { - ...current, - columns: currentColumns.map(x => (x == existing ? changeFunc(existing) : x)), - }; - } else { - return { - ...current, - columns: [ - ...cleanupDesignColumns(currentColumns), - changeFunc(_.pick(column, ['designerId', 'columnName'])), - ], - }; - } - }); - }, - [onChange] - ); - - // React.useEffect(() => { - // setTimeout(() => setChangeToken((x) => x + 1), 100); - // }, [value]); - - return ( - - {(tables || []).length == 0 && Drag & drop tables or views from left panel here} - e.preventDefault()} onDrop={handleDrop} ref={wrapperRef}> - {(references || []).map(ref => ( - - ))} - {(tables || []).map(table => ( - { - domTablesRef.current[table.designerId] = table; - }} - designer={value} - /> - ))} - - - ); -} diff --git a/packages/web/src/designer/DesignerComponentCreator.ts b/packages/web/src/designer/DesignerComponentCreator.ts deleted file mode 100644 index 0f4ad809a..000000000 --- a/packages/web/src/designer/DesignerComponentCreator.ts +++ /dev/null @@ -1,91 +0,0 @@ -import _ from 'lodash'; -import { dumpSqlSelect, Select, JoinType, Condition, Relation, mergeConditions, Source } from 'dbgate-sqltree'; -import { EngineDriver } from 'dbgate-types'; -import { DesignerInfo, DesignerTableInfo, DesignerReferenceInfo, DesignerJoinType } from './types'; -import { findPrimaryTable, findConnectingReference, referenceIsJoin, referenceIsExists } from './designerTools'; - -export class DesignerComponent { - subComponents: DesignerComponent[] = []; - parentComponent: DesignerComponent; - parentReference: DesignerReferenceInfo; - - tables: DesignerTableInfo[] = []; - nonPrimaryReferences: DesignerReferenceInfo[] = []; - - get primaryTable() { - return this.tables[0]; - } - get nonPrimaryTables() { - return this.tables.slice(1); - } - get nonPrimaryTablesAndReferences() { - return _.zip(this.nonPrimaryTables, this.nonPrimaryReferences); - } - get myAndParentTables() { - return [...this.parentTables, ...this.tables]; - } - get parentTables() { - return this.parentComponent ? this.parentComponent.myAndParentTables : []; - } - get thisAndSubComponentsTables() { - return [...this.tables, ..._.flatten(this.subComponents.map(x => x.thisAndSubComponentsTables))]; - } -} - -export class DesignerComponentCreator { - toAdd: DesignerTableInfo[]; - components: DesignerComponent[] = []; - - constructor(public designer: DesignerInfo) { - this.toAdd = [...designer.tables]; - while (this.toAdd.length > 0) { - const component = this.parseComponent(null); - this.components.push(component); - } - } - - parseComponent(root) { - if (root == null) { - root = findPrimaryTable(this.toAdd); - } - if (!root) return null; - _.remove(this.toAdd, x => x == root); - const res = new DesignerComponent(); - res.tables.push(root); - - for (;;) { - let found = false; - for (const test of this.toAdd) { - const ref = findConnectingReference(this.designer, res.tables, [test], referenceIsJoin); - if (ref) { - res.tables.push(test); - res.nonPrimaryReferences.push(ref); - _.remove(this.toAdd, x => x == test); - found = true; - break; - } - } - - if (!found) break; - } - - for (;;) { - let found = false; - for (const test of this.toAdd) { - const ref = findConnectingReference(this.designer, res.tables, [test], referenceIsExists); - if (ref) { - const subComponent = this.parseComponent(test); - res.subComponents.push(subComponent); - subComponent.parentComponent = res; - subComponent.parentReference = ref; - found = true; - break; - } - } - - if (!found) break; - } - - return res; - } -} diff --git a/packages/web/src/designer/DesignerQueryDumper.ts b/packages/web/src/designer/DesignerQueryDumper.ts deleted file mode 100644 index d4c5e4af2..000000000 --- a/packages/web/src/designer/DesignerQueryDumper.ts +++ /dev/null @@ -1,215 +0,0 @@ -import _ from 'lodash'; -import { - dumpSqlSelect, - Select, - JoinType, - Condition, - Relation, - mergeConditions, - Source, - ResultField, -} from 'dbgate-sqltree'; -import { EngineDriver } from 'dbgate-types'; -import { DesignerInfo, DesignerTableInfo, DesignerReferenceInfo, DesignerJoinType } from './types'; -import { DesignerComponent } from './DesignerComponentCreator'; -import { - getReferenceConditions, - referenceIsCrossJoin, - referenceIsConnecting, - mergeSelectsFromDesigner, - findQuerySource, - findDesignerFilterType, -} from './designerTools'; -import { parseFilter } from 'dbgate-filterparser'; - -export class DesignerQueryDumper { - constructor(public designer: DesignerInfo, public components: DesignerComponent[]) {} - - get topLevelTables(): DesignerTableInfo[] { - return _.flatten(this.components.map(x => x.tables)); - } - - dumpComponent(component: DesignerComponent) { - const select: Select = { - commandType: 'select', - from: { - name: component.primaryTable, - alias: component.primaryTable.alias, - relations: [], - }, - }; - - for (const [table, ref] of component.nonPrimaryTablesAndReferences) { - select.from.relations.push({ - name: table, - alias: table.alias, - joinType: ref.joinType as JoinType, - conditions: getReferenceConditions(ref, this.designer), - }); - } - - for (const subComponent of component.subComponents) { - const subQuery = this.dumpComponent(subComponent); - subQuery.selectAll = true; - select.where = mergeConditions(select.where, { - conditionType: subComponent.parentReference.joinType == 'WHERE NOT EXISTS' ? 'notExists' : 'exists', - subQuery, - }); - } - - if (component.parentReference) { - select.where = mergeConditions(select.where, { - conditionType: 'and', - conditions: getReferenceConditions(component.parentReference, this.designer), - }); - - // cross join conditions in subcomponents - for (const ref of this.designer.references || []) { - if (referenceIsCrossJoin(ref) && referenceIsConnecting(ref, component.tables, component.myAndParentTables)) { - select.where = mergeConditions(select.where, { - conditionType: 'and', - conditions: getReferenceConditions(ref, this.designer), - }); - } - } - this.addConditions(select, component.tables); - } - - return select; - } - - addConditions(select: Select, tables: DesignerTableInfo[]) { - for (const column of this.designer.columns || []) { - if (!column.filter) continue; - const table = (this.designer.tables || []).find(x => x.designerId == column.designerId); - if (!table) continue; - if (!tables.find(x => x.designerId == table.designerId)) continue; - - const condition = parseFilter(column.filter, findDesignerFilterType(column, this.designer)); - if (condition) { - select.where = mergeConditions( - select.where, - _.cloneDeepWith(condition, expr => { - if (expr.exprType == 'placeholder') - return { - exprType: 'column', - columnName: column.columnName, - source: findQuerySource(this.designer, column.designerId), - }; - }) - ); - } - } - } - - addGroupConditions(select: Select, tables: DesignerTableInfo[], selectIsGrouped: boolean) { - for (const column of this.designer.columns || []) { - if (!column.groupFilter) continue; - const table = (this.designer.tables || []).find(x => x.designerId == column.designerId); - if (!table) continue; - if (!tables.find(x => x.designerId == table.designerId)) continue; - - const condition = parseFilter(column.groupFilter, findDesignerFilterType(column, this.designer)); - if (condition) { - select.having = mergeConditions( - select.having, - _.cloneDeepWith(condition, expr => { - if (expr.exprType == 'placeholder') { - return this.getColumnOutputExpression(column, selectIsGrouped); - } - }) - ); - } - } - } - - getColumnOutputExpression(col, selectIsGrouped): ResultField { - const source = findQuerySource(this.designer, col.designerId); - const { columnName } = col; - let { alias } = col; - if (selectIsGrouped && !col.isGrouped) { - // use aggregate - const aggregate = col.aggregate == null || col.aggregate == '---' ? 'MAX' : col.aggregate; - if (!alias) alias = `${aggregate}(${columnName})`; - - return { - exprType: 'call', - func: aggregate == 'COUNT DISTINCT' ? 'COUNT' : aggregate, - argsPrefix: aggregate == 'COUNT DISTINCT' ? 'DISTINCT' : null, - alias, - args: [ - { - exprType: 'column', - columnName, - source, - }, - ], - }; - } else { - return { - exprType: 'column', - columnName, - alias, - source, - }; - } - } - - run() { - let res: Select = null; - for (const component of this.components) { - const select = this.dumpComponent(component); - if (res == null) res = select; - else res = mergeSelectsFromDesigner(res, select); - } - - // top level cross join conditions - const topLevelTables = this.topLevelTables; - for (const ref of this.designer.references || []) { - if (referenceIsCrossJoin(ref) && referenceIsConnecting(ref, topLevelTables, topLevelTables)) { - res.where = mergeConditions(res.where, { - conditionType: 'and', - conditions: getReferenceConditions(ref, this.designer), - }); - } - } - - const topLevelColumns = (this.designer.columns || []).filter(col => - topLevelTables.find(tbl => tbl.designerId == col.designerId) - ); - const selectIsGrouped = !!topLevelColumns.find(x => x.isGrouped || (x.aggregate && x.aggregate != '---')); - const outputColumns = topLevelColumns.filter(x => x.isOutput); - if (outputColumns.length == 0) { - res.selectAll = true; - } else { - res.columns = outputColumns.map(col => this.getColumnOutputExpression(col, selectIsGrouped)); - } - - const groupedColumns = topLevelColumns.filter(x => x.isGrouped); - if (groupedColumns.length > 0) { - res.groupBy = groupedColumns.map(col => ({ - exprType: 'column', - columnName: col.columnName, - source: findQuerySource(this.designer, col.designerId), - })); - } - - const orderColumns = _.sortBy( - topLevelColumns.filter(x => x.sortOrder), - x => Math.abs(x.sortOrder) - ); - if (orderColumns.length > 0) { - res.orderBy = orderColumns.map(col => ({ - exprType: 'column', - direction: col.sortOrder < 0 ? 'DESC' : 'ASC', - columnName: col.columnName, - source: findQuerySource(this.designer, col.designerId), - })); - } - - this.addConditions(res, topLevelTables); - this.addGroupConditions(res, topLevelTables, selectIsGrouped); - - return res; - } -} diff --git a/packages/web/src/designer/DesignerReference.js b/packages/web/src/designer/DesignerReference.js deleted file mode 100644 index 60a37bfdf..000000000 --- a/packages/web/src/designer/DesignerReference.js +++ /dev/null @@ -1,177 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import DomTableRef from './DomTableRef'; -import _ from 'lodash'; -import useTheme from '../theme/useTheme'; -import { useShowMenu } from '../modals/showMenu'; -import { DropDownMenuDivider, DropDownMenuItem } from '../modals/DropDownMenu'; -import { isConnectedByReference } from './designerTools'; - -const StyledSvg = styled.svg` - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - width: 100%; - height: 100%; -`; - -const ReferenceWrapper = styled.div` - position: absolute; - border: 1px solid ${props => props.theme.designer_line}; - background-color: ${props => props.theme.designer_background}; - z-index: 900; - border-radius: 10px; - width: 32px; - height: 32px; -`; - -const ReferenceText = styled.span` - position: relative; - float: left; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 900; - white-space: nowrap; - background-color: ${props => props.theme.designer_background}; -`; - -function ReferenceContextMenu({ remove, setJoinType, isConnected }) { - return ( - <> - Remove - {!isConnected && ( - <> - - setJoinType('INNER JOIN')}>Set INNER JOIN - setJoinType('LEFT JOIN')}>Set LEFT JOIN - setJoinType('RIGHT JOIN')}>Set RIGHT JOIN - setJoinType('FULL OUTER JOIN')}>Set FULL OUTER JOIN - setJoinType('CROSS JOIN')}>Set CROSS JOIN - setJoinType('WHERE EXISTS')}>Set WHERE EXISTS - setJoinType('WHERE NOT EXISTS')}>Set WHERE NOT EXISTS - - )} - - ); -} - -export default function DesignerReference({ - domTablesRef, - reference, - changeToken, - onRemoveReference, - onChangeReference, - designer, -}) { - const { designerId, sourceId, targetId, columns, joinType } = reference; - const theme = useTheme(); - const showMenu = useShowMenu(); - const domTables = domTablesRef.current; - /** @type {DomTableRef} */ - const sourceTable = domTables[sourceId]; - /** @type {DomTableRef} */ - const targetTable = domTables[targetId]; - if (!sourceTable || !targetTable) return null; - const sourceRect = sourceTable.getRect(); - const targetRect = targetTable.getRect(); - if (!sourceRect || !targetRect) return null; - - const buswi = 10; - const extwi = 25; - - const possibilities = []; - possibilities.push({ xsrc: sourceRect.left - buswi, dirsrc: -1, xdst: targetRect.left - buswi, dirdst: -1 }); - possibilities.push({ xsrc: sourceRect.left - buswi, dirsrc: -1, xdst: targetRect.right + buswi, dirdst: 1 }); - possibilities.push({ xsrc: sourceRect.right + buswi, dirsrc: 1, xdst: targetRect.left - buswi, dirdst: -1 }); - possibilities.push({ xsrc: sourceRect.right + buswi, dirsrc: 1, xdst: targetRect.right + buswi, dirdst: 1 }); - - let minpos = _.minBy(possibilities, p => Math.abs(p.xsrc - p.xdst)); - - let srcY = _.mean(columns.map(x => sourceTable.getColumnY(x.source))); - let dstY = _.mean(columns.map(x => targetTable.getColumnY(x.target))); - - if (columns.length == 0) { - srcY = sourceTable.getColumnY(''); - dstY = targetTable.getColumnY(''); - } - - const src = { x: minpos.xsrc, y: srcY }; - const dst = { x: minpos.xdst, y: dstY }; - - const lineStyle = { fill: 'none', stroke: theme.designer_line, strokeWidth: 2 }; - - const handleContextMenu = event => { - event.preventDefault(); - showMenu( - event.pageX, - event.pageY, - onRemoveReference({ designerId })} - isConnected={isConnectedByReference(designer, { designerId: sourceId }, { designerId: targetId }, reference)} - setJoinType={joinType => { - onChangeReference({ - ...reference, - joinType, - }); - }} - /> - ); - }; - - return ( - <> - - - {columns.map((col, colIndex) => { - let y1 = sourceTable.getColumnY(col.source); - let y2 = targetTable.getColumnY(col.target); - return ( - - - - - ); - })} - - - - {_.snakeCase(joinType || 'CROSS JOIN') - .replace('_', '\xa0') - .replace('_', '\xa0')} - - - - ); -} diff --git a/packages/web/src/designer/DesignerTable.js b/packages/web/src/designer/DesignerTable.js deleted file mode 100644 index e9b996aa7..000000000 --- a/packages/web/src/designer/DesignerTable.js +++ /dev/null @@ -1,413 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { findForeignKeyForColumn } from 'dbgate-tools'; -import ColumnLabel from '../datagrid/ColumnLabel'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; -import DomTableRef from './DomTableRef'; -import _ from 'lodash'; -import { CheckboxField } from '../utility/inputs'; -import { useShowMenu } from '../modals/showMenu'; -import { DropDownMenuDivider, DropDownMenuItem } from '../modals/DropDownMenu'; -import useShowModal from '../modals/showModal'; -import InputTextModal from '../modals/InputTextModal'; - -const Wrapper = styled.div` - position: absolute; - // background-color: white; - background-color: ${props => props.theme.designtable_background}; - border: 1px solid ${props => props.theme.border}; -`; - -const Header = styled.div` - font-weight: bold; - text-align: center; - padding: 2px; - background: ${props => - // @ts-ignore - props.objectTypeField == 'views' - ? props.theme.designtable_background_magenta[2] - : props.theme.designtable_background_blue[2]}; - border-bottom: 1px solid ${props => props.theme.border}; - cursor: pointer; - display: flex; - justify-content: space-between; -`; - -const ColumnsWrapper = styled.div` - max-height: 400px; - overflow-y: auto; - width: calc(100% - 10px); - padding: 5px; -`; - -const HeaderLabel = styled.div``; - -const CloseWrapper = styled.div` - ${props => - ` - background-color: ${props.theme.toolbar_background} ; - - &:hover { - background-color: ${props.theme.toolbar_background2} ; - } - - &:active:hover { - background-color: ${props.theme.toolbar_background3}; - } - `} -`; - -// &:hover { -// background-color: ${(props) => props.theme.designtable_background_gold[1]}; -// } - -const ColumnLine = styled.div` - ${props => - // @ts-ignore - !props.isDragSource && - // @ts-ignore - !props.isDragTarget && - ` - &:hover { - background-color: ${props.theme.designtable_background_gold[1]}; - } - `} - - ${props => - // @ts-ignore - props.isDragSource && - ` - background-color: ${props.theme.designtable_background_cyan[2]}; - `} - - ${props => - // @ts-ignore - props.isDragTarget && - ` - background-color: ${props.theme.designtable_background_cyan[2]}; - `} -`; - -function TableContextMenu({ remove, setTableAlias, removeTableAlias }) { - return ( - <> - Remove - - Set table alias - {!!removeTableAlias && Remove table alias} - - ); -} - -function ColumnContextMenu({ setSortOrder, addReference }) { - return ( - <> - setSortOrder(1)}>Sort ascending - setSortOrder(-1)}>Sort descending - setSortOrder(0)}>Unsort - {!!addReference && Add reference} - - ); -} - -function ColumnDesignerIcons({ column, designerId, designer }) { - const designerColumn = (designer.columns || []).find( - x => x.designerId == designerId && x.columnName == column.columnName - ); - if (!designerColumn) return null; - return ( - <> - {!!designerColumn.filter && } - {designerColumn.sortOrder > 0 && } - {designerColumn.sortOrder < 0 && } - {!!designerColumn.isGrouped && } - - ); -} - -export default function DesignerTable({ - table, - onChangeTable, - onBringToFront, - onRemoveTable, - onCreateReference, - onAddReferenceByColumn, - onSelectColumn, - onChangeColumn, - sourceDragColumn, - setSourceDragColumn, - targetDragColumn, - setTargetDragColumn, - onChangeDomTable, - wrapperRef, - setChangeToken, - designer, -}) { - const { pureName, columns, left, top, designerId, alias, objectTypeField } = table; - const [movingPosition, setMovingPosition] = React.useState(null); - const movingPositionRef = React.useRef(null); - const theme = useTheme(); - const domObjectsRef = React.useRef({}); - const showMenu = useShowMenu(); - const showModal = useShowModal(); - - const moveStartXRef = React.useRef(null); - const moveStartYRef = React.useRef(null); - - const handleMove = React.useCallback(e => { - let diffX = e.clientX - moveStartXRef.current; - let diffY = e.clientY - moveStartYRef.current; - moveStartXRef.current = e.clientX; - moveStartYRef.current = e.clientY; - - movingPositionRef.current = { - left: (movingPositionRef.current.left || 0) + diffX, - top: (movingPositionRef.current.top || 0) + diffY, - }; - setMovingPosition(movingPositionRef.current); - // setChangeToken((x) => x + 1); - changeTokenDebounced.current(); - // onChangeTable( - // { - // ...props, - // left: (left || 0) + diffX, - // top: (top || 0) + diffY, - // }, - // index - // ); - }, []); - - const changeTokenDebounced = React.useRef( - // @ts-ignore - _.debounce(() => setChangeToken(x => x + 1), 100) - ); - - const handleMoveEnd = React.useCallback( - e => { - if (movingPositionRef.current) { - onChangeTable({ - ...table, - left: movingPositionRef.current.left, - top: movingPositionRef.current.top, - }); - } - - movingPositionRef.current = null; - setMovingPosition(null); - changeTokenDebounced.current(); - // setChangeToken((x) => x + 1); - - // this.props.model.fixPositions(); - - // this.props.designer.changedModel(true); - }, - [onChangeTable, table] - ); - - React.useEffect(() => { - if (movingPosition) { - document.addEventListener('mousemove', handleMove, true); - document.addEventListener('mouseup', handleMoveEnd, true); - return () => { - document.removeEventListener('mousemove', handleMove, true); - document.removeEventListener('mouseup', handleMoveEnd, true); - }; - } - }, [movingPosition == null, handleMove, handleMoveEnd]); - - const headerMouseDown = React.useCallback( - e => { - e.preventDefault(); - moveStartXRef.current = e.clientX; - moveStartYRef.current = e.clientY; - movingPositionRef.current = { left, top }; - setMovingPosition(movingPositionRef.current); - // setIsMoving(true); - }, - [handleMove, handleMoveEnd] - ); - - const dispatchDomColumn = (columnName, dom) => { - domObjectsRef.current[columnName] = dom; - onChangeDomTable(new DomTableRef(table, domObjectsRef.current, wrapperRef.current)); - changeTokenDebounced.current(); - }; - - const handleSetTableAlias = () => { - showModal(modalState => ( - { - onChangeTable({ - ...table, - alias: newAlias, - }); - }} - /> - )); - }; - - const handleHeaderContextMenu = event => { - event.preventDefault(); - showMenu( - event.pageX, - event.pageY, - onRemoveTable({ designerId })} - setTableAlias={handleSetTableAlias} - removeTableAlias={ - alias - ? () => - onChangeTable({ - ...table, - alias: null, - }) - : null - } - /> - ); - }; - - const handleColumnContextMenu = column => event => { - event.preventDefault(); - const foreignKey = findForeignKeyForColumn(table, column); - showMenu( - event.pageX, - event.pageY, - { - onChangeColumn( - { - ...column, - designerId, - }, - col => ({ ...col, sortOrder }) - ); - }} - addReference={ - foreignKey - ? () => { - onAddReferenceByColumn(designerId, foreignKey); - } - : null - } - /> - ); - }; - - return ( - onBringToFront(table)} - ref={dom => dispatchDomColumn('', dom)} - > -
- {alias || pureName} - onRemoveTable(table)} theme={theme}> - - -
- - {(columns || []).map(column => ( - dispatchDomColumn(column.columnName, dom)} - // @ts-ignore - isDragSource={ - sourceDragColumn && - sourceDragColumn.designerId == designerId && - sourceDragColumn.columnName == column.columnName - } - // @ts-ignore - isDragTarget={ - targetDragColumn && - targetDragColumn.designerId == designerId && - targetDragColumn.columnName == column.columnName - } - onDragStart={e => { - const dragData = { - ...column, - designerId, - }; - setSourceDragColumn(dragData); - e.dataTransfer.setData('designer_column_drag_data', JSON.stringify(dragData)); - }} - onDragEnd={e => { - setTargetDragColumn(null); - setSourceDragColumn(null); - }} - onDragOver={e => { - if (sourceDragColumn) { - e.preventDefault(); - setTargetDragColumn({ - ...column, - designerId, - }); - } - }} - onDrop={e => { - var data = e.dataTransfer.getData('designer_column_drag_data'); - e.preventDefault(); - if (!data) return; - onCreateReference(sourceDragColumn, targetDragColumn); - setTargetDragColumn(null); - setSourceDragColumn(null); - }} - onMouseDown={e => - onSelectColumn({ - ...column, - designerId, - }) - } - > - x.designerId == designerId && x.columnName == column.columnName && x.isOutput - ) - } - onChange={e => { - if (e.target.checked) { - onChangeColumn( - { - ...column, - designerId, - }, - col => ({ ...col, isOutput: true }) - ); - } else { - onChangeColumn( - { - ...column, - designerId, - }, - col => ({ ...col, isOutput: false }) - ); - } - }} - /> - - - - ))} - -
- ); -} diff --git a/packages/web/src/designer/DomTableRef.ts b/packages/web/src/designer/DomTableRef.ts deleted file mode 100644 index 53aef90f0..000000000 --- a/packages/web/src/designer/DomTableRef.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { DesignerTableInfo } from './types'; - -export default class DomTableRef { - domTable: Element; - domWrapper: Element; - table: DesignerTableInfo; - designerId: string; - domRefs: { [column: string]: Element }; - - constructor(table: DesignerTableInfo, domRefs, domWrapper: Element) { - this.domTable = domRefs['']; - this.domWrapper = domWrapper; - this.table = table; - this.designerId = table.designerId; - this.domRefs = domRefs; - } - - getRect() { - if (!this.domWrapper) return null; - if (!this.domTable) return null; - - const wrap = this.domWrapper.getBoundingClientRect(); - const rect = this.domTable.getBoundingClientRect(); - return { - left: rect.left - wrap.left, - top: rect.top - wrap.top, - right: rect.right - wrap.left, - bottom: rect.bottom - wrap.top, - }; - } - - getColumnY(columnName: string) { - let col = this.domRefs[columnName]; - if (!col) return null; - const rect = col.getBoundingClientRect(); - const wrap = this.domWrapper.getBoundingClientRect(); - return (rect.top + rect.bottom) / 2 - wrap.top; - } -} diff --git a/packages/web/src/designer/QueryDesignColumns.js b/packages/web/src/designer/QueryDesignColumns.js deleted file mode 100644 index 7ac113f96..000000000 --- a/packages/web/src/designer/QueryDesignColumns.js +++ /dev/null @@ -1,165 +0,0 @@ -import React from 'react'; -import DataFilterControl from '../datagrid/DataFilterControl'; -import { CheckboxField, SelectField, TextField } from '../utility/inputs'; -import TableControl, { TableColumn } from '../utility/TableControl'; -import InlineButton from '../widgets/InlineButton'; -import { findDesignerFilterType } from './designerTools'; - -function getTableDisplayName(column, tables) { - const table = (tables || []).find(x => x.designerId == column.designerId); - if (table) return table.alias || table.pureName; - return ''; -} - -export default function QueryDesignColumns({ value, onChange }) { - const { columns, tables } = value || {}; - - const changeColumn = React.useCallback( - col => { - onChange(current => ({ - ...current, - columns: (current.columns || []).map(x => - x.designerId == col.designerId && x.columnName == col.columnName ? col : x - ), - })); - }, - [onChange] - ); - - const removeColumn = React.useCallback( - col => { - onChange(current => ({ - ...current, - columns: (current.columns || []).filter(x => x.designerId != col.designerId || x.columnName != col.columnName), - })); - }, - [onChange] - ); - - const hasGroupedColumn = !!(columns || []).find(x => x.isGrouped); - - return ( - - - getTableDisplayName(row, tables)} /> - ( - { - if (e.target.checked) changeColumn({ ...row, isOutput: true }); - else changeColumn({ ...row, isOutput: false }); - }} - /> - )} - /> - ( - { - changeColumn({ ...row, alias: e.target.value }); - }} - /> - )} - /> - - ( - { - if (e.target.checked) changeColumn({ ...row, isGrouped: true }); - else changeColumn({ ...row, isGrouped: false }); - }} - /> - )} - /> - - !row.isGrouped && ( - { - changeColumn({ ...row, aggregate: e.target.value }); - }} - > - - - - - - - - - ) - } - /> - ( - { - changeColumn({ ...row, sortOrder: parseInt(e.target.value) }); - }} - > - - - - - - - , - - )} - /> - ( - { - changeColumn({ ...row, filter }); - }} - /> - )} - /> - {hasGroupedColumn && ( - ( - { - changeColumn({ ...row, groupFilter }); - }} - /> - )} - /> - )} - ( - <> - removeColumn(row)}>Remove - - )} - /> - - ); -} diff --git a/packages/web/src/designer/QueryDesignToolbar.js b/packages/web/src/designer/QueryDesignToolbar.js deleted file mode 100644 index c53092e69..000000000 --- a/packages/web/src/designer/QueryDesignToolbar.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import ToolbarButton from '../widgets/ToolbarButton'; - -export default function QueryDesignToolbar({ - execute, - isDatabaseDefined, - busy, - modelState, - dispatchModel, - isConnected, - kill, -}) { - return ( - <> - - Execute - - - Kill - - dispatchModel({ type: 'undo' })} icon="icon undo"> - Undo - - dispatchModel({ type: 'redo' })} icon="icon redo"> - Redo - - - ); -} diff --git a/packages/web/src/designer/QueryDesigner.js b/packages/web/src/designer/QueryDesigner.js deleted file mode 100644 index 031fe40ed..000000000 --- a/packages/web/src/designer/QueryDesigner.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import Designer from './Designer'; - -export default function QueryDesigner({ value, conid, database, engine, onChange }) { - return ; -} diff --git a/packages/web/src/designer/cleanupDesignColumns.js b/packages/web/src/designer/cleanupDesignColumns.js deleted file mode 100644 index 171eefd50..000000000 --- a/packages/web/src/designer/cleanupDesignColumns.js +++ /dev/null @@ -1,5 +0,0 @@ -export default function cleanupDesignColumns(columns) { - return (columns || []).filter( - x => x.isOutput || x.isGrouped || x.alias || (x.aggregate && x.aggregate != '---') || x.sortOrder || x.filter - ); -} diff --git a/packages/web/src/designer/designerTools.ts b/packages/web/src/designer/designerTools.ts deleted file mode 100644 index 5ed0152d8..000000000 --- a/packages/web/src/designer/designerTools.ts +++ /dev/null @@ -1,144 +0,0 @@ -import _ from 'lodash'; -import { dumpSqlSelect, Select, JoinType, Condition, Relation, mergeConditions, Source } from 'dbgate-sqltree'; -import { EngineDriver } from 'dbgate-types'; -import { DesignerInfo, DesignerTableInfo, DesignerReferenceInfo, DesignerJoinType } from './types'; -import { DesignerComponentCreator } from './DesignerComponentCreator'; -import { DesignerQueryDumper } from './DesignerQueryDumper'; -import { getFilterType } from 'dbgate-filterparser'; - -export function referenceIsConnecting( - reference: DesignerReferenceInfo, - tables1: DesignerTableInfo[], - tables2: DesignerTableInfo[] -) { - return ( - (tables1.find(x => x.designerId == reference.sourceId) && tables2.find(x => x.designerId == reference.targetId)) || - (tables1.find(x => x.designerId == reference.targetId) && tables2.find(x => x.designerId == reference.sourceId)) - ); -} - -export function referenceIsJoin(reference) { - return ['INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'FULL OUTER JOIN'].includes(reference.joinType); -} -export function referenceIsExists(reference) { - return ['WHERE EXISTS', 'WHERE NOT EXISTS'].includes(reference.joinType); -} -export function referenceIsCrossJoin(reference) { - return !reference.joinType || reference.joinType == 'CROSS JOIN'; -} - -export function findConnectingReference( - designer: DesignerInfo, - tables1: DesignerTableInfo[], - tables2: DesignerTableInfo[], - additionalCondition: (ref: DesignerReferenceInfo) => boolean -) { - for (const ref of designer.references || []) { - if (additionalCondition(ref) && referenceIsConnecting(ref, tables1, tables2)) { - return ref; - } - } - return null; -} - -export function findQuerySource(designer: DesignerInfo, designerId: string): Source { - const table = designer.tables.find(x => x.designerId == designerId); - if (!table) return null; - return { - name: table, - alias: table.alias, - }; -} - -export function mergeSelectsFromDesigner(select1: Select, select2: Select): Select { - return { - commandType: 'select', - from: { - ...select1.from, - relations: [ - ...select1.from.relations, - { - joinType: 'CROSS JOIN', - name: select2.from.name, - alias: select2.from.alias, - }, - ...select2.from.relations, - ], - }, - where: mergeConditions(select1.where, select2.where), - }; -} - -export function findPrimaryTable(tables: DesignerTableInfo[]) { - return _.minBy(tables, x => x.top); -} - -export function getReferenceConditions(reference: DesignerReferenceInfo, designer: DesignerInfo): Condition[] { - const sourceTable = designer.tables.find(x => x.designerId == reference.sourceId); - const targetTable = designer.tables.find(x => x.designerId == reference.targetId); - - return reference.columns.map(col => ({ - conditionType: 'binary', - operator: '=', - left: { - exprType: 'column', - columnName: col.source, - source: { - name: sourceTable, - alias: sourceTable.alias, - }, - }, - right: { - exprType: 'column', - columnName: col.target, - source: { - name: targetTable, - alias: targetTable.alias, - }, - }, - })); -} - -export function generateDesignedQuery(designer: DesignerInfo, engine: EngineDriver) { - const { tables, columns, references } = designer; - const primaryTable = findPrimaryTable(designer.tables); - if (!primaryTable) return ''; - const componentCreator = new DesignerComponentCreator(designer); - const designerDumper = new DesignerQueryDumper(designer, componentCreator.components); - const select = designerDumper.run(); - - const dmp = engine.createDumper(); - dumpSqlSelect(dmp, select); - return dmp.s; -} - -export function isConnectedByReference( - designer: DesignerInfo, - table1: { designerId: string }, - table2: { designerId: string }, - withoutRef: { designerId: string } -) { - if (!designer.references) return false; - const creator = new DesignerComponentCreator({ - ...designer, - references: withoutRef - ? designer.references.filter(x => x.designerId != withoutRef.designerId) - : designer.references, - }); - const arrays = creator.components.map(x => x.thisAndSubComponentsTables); - const array1 = arrays.find(a => a.find(x => x.designerId == table1.designerId)); - const array2 = arrays.find(a => a.find(x => x.designerId == table2.designerId)); - return array1 == array2; -} - -export function findDesignerFilterType({ designerId, columnName }, designer) { - const table = (designer.tables || []).find(x => x.designerId == designerId); - if (table) { - const column = (table.columns || []).find(x => x.columnName == columnName); - if (column) { - const { dataType } = column; - return getFilterType(dataType); - } - } - return 'string'; -} diff --git a/packages/web/src/designer/types.ts b/packages/web/src/designer/types.ts deleted file mode 100644 index c746f7ac7..000000000 --- a/packages/web/src/designer/types.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { JoinType } from 'dbgate-sqltree'; -import { TableInfo } from 'dbgate-types'; - -export type DesignerTableInfo = TableInfo & { - designerId: string; - alias?: string; - left: number; - top: number; -}; - -export type DesignerJoinType = JoinType | 'WHERE EXISTS' | 'WHERE NOT EXISTS'; - -export type DesignerReferenceInfo = { - designerId: string; - joinType: DesignerJoinType; - sourceId: string; - targetId: string; - columns: { - source: string; - target: string; - }[]; -}; - -export type DesignerColumnInfo = { - designerId: string; - columnName: string; - alias?: string; - isGrouped?: boolean; - aggregate?: string; - isOutput?: boolean; - sortOrder?: number; - filter?: string; - groupFilter?: string; -}; - -export type DesignerInfo = { - tables: DesignerTableInfo[]; - columns: DesignerColumnInfo[]; - references: DesignerReferenceInfo[]; -}; - -// export type DesignerComponent = { -// tables: DesignerTableInfo[]; -// }; diff --git a/packages/web/src/formview/ChangeSetFormer.ts b/packages/web/src/formview/ChangeSetFormer.ts deleted file mode 100644 index 11716145c..000000000 --- a/packages/web/src/formview/ChangeSetFormer.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { - ChangeSet, - changeSetContainsChanges, - changeSetInsertNewRow, - createChangeSet, - deleteChangeSetRows, - findExistingChangeSetItem, - getChangeSetInsertedRows, - TableFormViewDisplay, - revertChangeSetRowChanges, - setChangeSetValue, - ChangeSetRowDefinition, -} from 'dbgate-datalib'; -import Former from './Former'; - -export default class ChangeSetFormer extends Former { - public changeSet: ChangeSet; - public setChangeSet: Function; - private batchChangeSet: ChangeSet; - public rowDefinition: ChangeSetRowDefinition; - public rowStatus; - - constructor( - public sourceRow: any, - public changeSetState, - public dispatchChangeSet, - public display: TableFormViewDisplay - ) { - super(); - this.changeSet = changeSetState && changeSetState.value; - this.setChangeSet = value => dispatchChangeSet({ type: 'set', value }); - this.batchChangeSet = null; - this.rowDefinition = display.getChangeSetRow(sourceRow); - const [matchedField, matchedChangeSetItem] = findExistingChangeSetItem(this.changeSet, this.rowDefinition); - this.rowData = matchedChangeSetItem ? { ...sourceRow, ...matchedChangeSetItem.fields } : sourceRow; - let status = 'regular'; - if (matchedChangeSetItem && matchedField == 'updates') status = 'updated'; - if (matchedField == 'deletes') status = 'deleted'; - this.rowStatus = { - status, - modifiedFields: - matchedChangeSetItem && matchedChangeSetItem.fields ? new Set(Object.keys(matchedChangeSetItem.fields)) : null, - }; - } - - applyModification(changeSetReducer) { - if (this.batchChangeSet) { - this.batchChangeSet = changeSetReducer(this.batchChangeSet); - } else { - this.setChangeSet(changeSetReducer(this.changeSet)); - } - } - - setCellValue(uniqueName: string, value: any) { - const row = this.sourceRow; - const definition = this.display.getChangeSetField(row, uniqueName); - this.applyModification(chs => setChangeSetValue(chs, definition, value)); - } - - deleteRow(index: number) { - this.applyModification(chs => deleteChangeSetRows(chs, this.rowDefinition)); - } - - beginUpdate() { - this.batchChangeSet = this.changeSet; - } - endUpdate() { - this.setChangeSet(this.batchChangeSet); - this.batchChangeSet = null; - } - - revertRowChanges() { - this.applyModification(chs => revertChangeSetRowChanges(chs, this.rowDefinition)); - } - revertAllChanges() { - this.applyModification(chs => createChangeSet()); - } - undo() { - this.dispatchChangeSet({ type: 'undo' }); - } - redo() { - this.dispatchChangeSet({ type: 'redo' }); - } - get canUndo() { - return this.changeSetState.canUndo; - } - get canRedo() { - return this.changeSetState.canRedo; - } - get containsChanges() { - return changeSetContainsChanges(this.changeSet); - } -} diff --git a/packages/web/src/formview/FormView.js b/packages/web/src/formview/FormView.js deleted file mode 100644 index 670610a41..000000000 --- a/packages/web/src/formview/FormView.js +++ /dev/null @@ -1,583 +0,0 @@ -// @ts-nocheck - -import _ from 'lodash'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import ColumnLabel from '../datagrid/ColumnLabel'; -import { findForeignKeyForColumn } from 'dbgate-tools'; -import styled from 'styled-components'; -import useTheme from '../theme/useTheme'; -import useDimensions from '../utility/useDimensions'; -import FormViewToolbar from './FormViewToolbar'; -import { useShowMenu } from '../modals/showMenu'; -import FormViewContextMenu from './FormViewContextMenu'; -import keycodes from '../utility/keycodes'; -import { CellFormattedValue, ShowFormButton } from '../datagrid/DataGridRow'; -import { cellFromEvent } from '../datagrid/selection'; -import InplaceEditor from '../datagrid/InplaceEditor'; -import { copyTextToClipboard } from '../utility/clipboard'; -import { ExpandIcon, FontIcon } from '../icons'; -import openReferenceForm from './openReferenceForm'; -import useOpenNewTab from '../utility/useOpenNewTab'; -import LoadingInfo from '../widgets/LoadingInfo'; - -const Table = styled.table` - border-collapse: collapse; - outline: none; -`; - -const OuterWrapper = styled.div` - position: absolute; - left: 0; - top: 0; - bottom: 0; - right: 0; -`; - -const Wrapper = styled.div` - position: absolute; - left: 0; - top: 0; - bottom: 0; - right: 0; - display: flex; - overflow-x: scroll; -`; - -const TableRow = styled.tr` - background-color: ${props => props.theme.gridbody_background}; - &:nth-child(6n + 3) { - background-color: ${props => props.theme.gridbody_background_alt2}; - } - &:nth-child(6n + 6) { - background-color: ${props => props.theme.gridbody_background_alt3}; - } -`; - -const TableHeaderCell = styled.td` - border: 1px solid ${props => props.theme.border}; - text-align: left; - padding: 2px; - background-color: ${props => props.theme.gridheader_background}; - overflow: hidden; - position: relative; - - ${props => - props.isSelected && - ` - background: initial; - background-color: ${props.theme.gridbody_selection[4]}; - color: ${props.theme.gridbody_invfont1};`} -`; - -const TableBodyCell = styled.td` - font-weight: normal; - border: 1px solid ${props => props.theme.border}; - // border-collapse: collapse; - padding: 2px; - white-space: nowrap; - position: relative; - max-width: 500px; - overflow: hidden; - text-overflow: ellipsis; - - ${props => - props.isSelected && - ` - background: initial; - background-color: ${props.theme.gridbody_selection[4]}; - color: ${props.theme.gridbody_invfont1};`} - - ${props => - !props.isSelected && - props.isModifiedCell && - ` - background-color: ${props.theme.gridbody_background_orange[1]};`} -`; - -const FocusField = styled.input` - // visibility: hidden - position: absolute; - left: -1000px; - top: -1000px; -`; - -const RowCountLabel = styled.div` - position: absolute; - background-color: ${props => props.theme.gridbody_background_yellow[1]}; - right: 40px; - bottom: 20px; -`; - -const HintSpan = styled.span` - color: gray; - margin-left: 5px; - margin-right: 16px; -`; - -const ColumnLabelMargin = styled(ColumnLabel)` - margin-right: 16px; -`; - -function isDataCell(cell) { - return cell[1] % 2 == 1; -} - -export default function FormView(props) { - const { - toolbarPortalRef, - tabVisible, - config, - setConfig, - onNavigate, - former, - onSave, - conid, - database, - onReload, - onReconnect, - allRowCount, - rowCountBefore, - onSelectionChanged, - isLoading, - } = props; - /** @type {import('dbgate-datalib').FormViewDisplay} */ - const formDisplay = props.formDisplay; - const theme = useTheme(); - const [headerRowRef, { height: rowHeight }] = useDimensions(); - const [wrapperRef, { height: wrapperHeight }] = useDimensions(); - const showMenu = useShowMenu(); - const focusFieldRef = React.useRef(null); - const [currentCell, setCurrentCell] = React.useState([0, 0]); - const cellRefs = React.useRef({}); - const openNewTab = useOpenNewTab(); - - const rowCount = Math.floor((wrapperHeight - 20) / rowHeight); - const columnChunks = _.chunk(formDisplay.columns, rowCount); - - const { rowData, rowStatus } = former; - - const handleSwitchToTable = () => { - setConfig(cfg => ({ - ...cfg, - isFormView: false, - formViewKey: null, - })); - }; - - const handleFilterThisValue = isDataCell(currentCell) - ? () => formDisplay.filterCellValue(getCellColumn(currentCell), rowData) - : null; - - const handleContextMenu = event => { - event.preventDefault(); - showMenu( - event.pageX, - event.pageY, - formDisplay.addFilterColumn(getCellColumn(currentCell))} - filterThisValue={handleFilterThisValue} - /> - ); - }; - - const setCellRef = (row, col, element) => { - cellRefs.current[`${row},${col}`] = element; - }; - - React.useEffect(() => { - if (tabVisible) { - if (focusFieldRef.current) focusFieldRef.current.focus(); - } - }, [tabVisible, focusFieldRef.current]); - - React.useEffect(() => { - if (!onSelectionChanged || !rowData) return; - const col = getCellColumn(currentCell); - if (!col) return; - onSelectionChanged(rowData[col.uniqueName]); - }, [onSelectionChanged, currentCell, rowData]); - - const checkMoveCursorBounds = (row, col) => { - if (row < 0) row = 0; - if (col < 0) col = 0; - if (col >= columnChunks.length * 2) col = columnChunks.length * 2 - 1; - const chunk = columnChunks[Math.floor(col / 2)]; - if (chunk && row >= chunk.length) row = chunk.length - 1; - return [row, col]; - }; - - const handleCursorMove = event => { - if (event.ctrlKey) { - switch (event.keyCode) { - case keycodes.leftArrow: - return checkMoveCursorBounds(currentCell[0], 0); - case keycodes.rightArrow: - return checkMoveCursorBounds(currentCell[0], columnChunks.length * 2 - 1); - } - } - switch (event.keyCode) { - case keycodes.leftArrow: - return checkMoveCursorBounds(currentCell[0], currentCell[1] - 1); - case keycodes.rightArrow: - return checkMoveCursorBounds(currentCell[0], currentCell[1] + 1); - case keycodes.upArrow: - return checkMoveCursorBounds(currentCell[0] - 1, currentCell[1]); - case keycodes.downArrow: - return checkMoveCursorBounds(currentCell[0] + 1, currentCell[1]); - case keycodes.pageUp: - return checkMoveCursorBounds(0, currentCell[1]); - case keycodes.pageDown: - return checkMoveCursorBounds(rowCount - 1, currentCell[1]); - case keycodes.home: - return checkMoveCursorBounds(0, 0); - case keycodes.end: - return checkMoveCursorBounds(rowCount - 1, columnChunks.length * 2 - 1); - } - }; - - const handleKeyNavigation = event => { - if (event.ctrlKey) { - switch (event.keyCode) { - case keycodes.upArrow: - return 'previous'; - case keycodes.downArrow: - return 'next'; - case keycodes.home: - return 'begin'; - case keycodes.end: - return 'end'; - } - } - }; - - function handleSave() { - if (inplaceEditorState.cell) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'shouldSave' }); - return; - } - if (onSave) onSave(); - } - - function getCellColumn(cell) { - const chunk = columnChunks[Math.floor(cell[1] / 2)]; - if (!chunk) return; - const column = chunk[cell[0]]; - return column; - } - - function setCellValue(cell, value) { - const column = getCellColumn(cell); - if (!column) return; - former.setCellValue(column.uniqueName, value); - } - - function setNull() { - if (isDataCell(currentCell)) { - setCellValue(currentCell, null); - } - } - - const scrollIntoView = cell => { - const element = cellRefs.current[`${cell[0]},${cell[1]}`]; - if (element) element.scrollIntoView(); - }; - - React.useEffect(() => { - scrollIntoView(currentCell); - }, [rowData]); - - const moveCurrentCell = (row, col) => { - const moved = checkMoveCursorBounds(row, col); - setCurrentCell(moved); - scrollIntoView(moved); - }; - - function copyToClipboard() { - const column = getCellColumn(currentCell); - if (!column) return; - const text = currentCell[1] % 2 == 1 ? rowData[column.uniqueName] : column.columnName; - copyTextToClipboard(text); - } - - const handleKeyDown = event => { - const navigation = handleKeyNavigation(event); - if (navigation) { - event.preventDefault(); - onNavigate(navigation); - return; - } - const moved = handleCursorMove(event); - if (moved) { - setCurrentCell(moved); - scrollIntoView(moved); - event.preventDefault(); - return; - } - if (event.keyCode == keycodes.s && event.ctrlKey) { - event.preventDefault(); - handleSave(); - // this.saveAndFocus(); - } - - if (event.keyCode == keycodes.n0 && event.ctrlKey) { - event.preventDefault(); - setNull(); - } - - if (event.keyCode == keycodes.r && event.ctrlKey) { - event.preventDefault(); - former.revertRowChanges(); - } - - // if (event.keyCode == keycodes.f && event.ctrlKey) { - // event.preventDefault(); - // filterSelectedValue(); - // } - - if (event.keyCode == keycodes.z && event.ctrlKey) { - event.preventDefault(); - former.undo(); - } - - if (event.keyCode == keycodes.y && event.ctrlKey) { - event.preventDefault(); - former.redo(); - } - - if (event.keyCode == keycodes.c && event.ctrlKey) { - event.preventDefault(); - copyToClipboard(); - } - - if (event.keyCode == keycodes.f && event.ctrlKey) { - event.preventDefault(); - if (handleFilterThisValue) handleFilterThisValue(); - } - - if (event.keyCode == keycodes.f5) { - event.preventDefault(); - onReload(); - } - - if (event.keyCode == keycodes.f4) { - event.preventDefault(); - handleSwitchToTable(); - } - - if ( - rowData && - !event.ctrlKey && - !event.altKey && - ((event.keyCode >= keycodes.a && event.keyCode <= keycodes.z) || - (event.keyCode >= keycodes.n0 && event.keyCode <= keycodes.n9) || - event.keyCode == keycodes.dash) - ) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'show', text: event.nativeEvent.key, cell: currentCell }); - return; - } - if (rowData && event.keyCode == keycodes.f2) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'show', cell: currentCell, selectAll: true }); - return; - } - }; - - const handleTableMouseDown = event => { - event.preventDefault(); - if (focusFieldRef.current) focusFieldRef.current.focus(); - - if (event.target.closest('.buttonLike')) return; - if (event.target.closest('.resizeHandleControl')) return; - if (event.target.closest('input')) return; - - // event.target.closest('table').focus(); - event.preventDefault(); - if (focusFieldRef.current) focusFieldRef.current.focus(); - const cell = cellFromEvent(event); - - if (isDataCell(cell) && !_.isEqual(cell, inplaceEditorState.cell) && _.isEqual(cell, currentCell)) { - // @ts-ignore - if (rowData) { - dispatchInsplaceEditor({ type: 'show', cell, selectAll: true }); - } - } else if (!_.isEqual(cell, inplaceEditorState.cell)) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'close' }); - } - - // @ts-ignore - setCurrentCell(cell); - }; - - const getCellWidth = (row, col) => { - const element = cellRefs.current[`${row},${col}`]; - if (element) return element.getBoundingClientRect().width; - return 100; - }; - - const rowCountInfo = React.useMemo(() => { - if (rowData == null) return 'No data'; - if (allRowCount == null || rowCountBefore == null) return 'Loading row count...'; - return `Row: ${(rowCountBefore + 1).toLocaleString()} / ${allRowCount.toLocaleString()}`; - }, [rowCountBefore, allRowCount]); - - const [inplaceEditorState, dispatchInsplaceEditor] = React.useReducer((state, action) => { - switch (action.type) { - case 'show': { - const column = getCellColumn(action.cell); - if (!column) return state; - if (column.uniquePath.length > 1) return state; - - // if (!grider.editable) return {}; - return { - cell: action.cell, - text: action.text, - selectAll: action.selectAll, - }; - } - case 'close': { - const [row, col] = currentCell || []; - if (focusFieldRef.current) focusFieldRef.current.focus(); - // @ts-ignore - if (action.mode == 'enter' && row) setTimeout(() => moveCurrentCell(row + 1, col), 0); - // if (action.mode == 'save') setTimeout(handleSave, 0); - return {}; - } - case 'shouldSave': { - return { - ...state, - shouldSave: true, - }; - } - } - return {}; - }, {}); - - const toolbar = - toolbarPortalRef && - toolbarPortalRef.current && - tabVisible && - ReactDOM.createPortal( - , - toolbarPortalRef.current - ); - - if (isLoading) { - return ( - <> - - {toolbar} - - ); - } - if (!formDisplay || !formDisplay.isLoadedCorrectly) return toolbar; - - return ( - - - {columnChunks.map((chunk, chunkIndex) => ( - - {chunk.map((col, rowIndex) => ( - - setCellRef(rowIndex, chunkIndex * 2, element)} - > - ${col.foreignKey.refTableName}` : null} - /> - - {col.foreignKey && ( - { - e.stopPropagation(); - formDisplay.toggleExpandedColumn(col.uniqueName); - }} - > - - - )} - - setCellRef(rowIndex, chunkIndex * 2 + 1, element)} - > - {inplaceEditorState.cell && - rowIndex == inplaceEditorState.cell[0] && - chunkIndex * 2 + 1 == inplaceEditorState.cell[1] ? ( - { - former.setCellValue(col.uniqueName, value); - }} - // grider={grider} - // rowIndex={rowIndex} - // uniqueName={col.uniqueName} - /> - ) : ( - <> - {rowData && ( - - )} - {!!col.hintColumnName && - rowData && - !(rowStatus.modifiedFields && rowStatus.modifiedFields.has(col.uniqueName)) && ( - {rowData[col.hintColumnName]} - )} - {col.foreignKey && rowData && rowData[col.uniqueName] && ( - { - e.stopPropagation(); - openReferenceForm(rowData, col, openNewTab, conid, database); - }} - > - - - )} - - )} - - - ))} -
- ))} - - - - {toolbar} -
- {rowCountInfo && {rowCountInfo}} -
- ); -} diff --git a/packages/web/src/formview/FormViewContextMenu.js b/packages/web/src/formview/FormViewContextMenu.js deleted file mode 100644 index 2d55a7508..000000000 --- a/packages/web/src/formview/FormViewContextMenu.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu'; - -export default function FormViewContextMenu({ switchToTable, onNavigate, addToFilter, filterThisValue }) { - return ( - <> - - Table view - - {addToFilter && Add to filter} - {filterThisValue && ( - - Filter this value - - )} - - onNavigate('begin')} keyText="Ctrl+Home"> - Navigate to begin - - onNavigate('previous')} keyText="Ctrl+Up"> - Navigate to previous - - onNavigate('next')} keyText="Ctrl+Down"> - Navigate to next - - onNavigate('end')} keyText="Ctrl+End"> - Navigate to end - - - ); -} diff --git a/packages/web/src/formview/FormViewFilters.js b/packages/web/src/formview/FormViewFilters.js deleted file mode 100644 index 2e47b29fa..000000000 --- a/packages/web/src/formview/FormViewFilters.js +++ /dev/null @@ -1,116 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import { ManagerInnerContainer } from '../datagrid/ManagerStyles'; -import styled from 'styled-components'; -import ColumnLabel from '../datagrid/ColumnLabel'; -import { TextField } from '../utility/inputs'; -import { getFilterType } from 'dbgate-filterparser'; -import DataFilterControl from '../datagrid/DataFilterControl'; -import InlineButton from '../widgets/InlineButton'; -import { FontIcon } from '../icons'; -import keycodes from '../utility/keycodes'; - -const ColumnWrapper = styled.div` - margin: 5px; -`; -const ColumnNameWrapper = styled.div` - display: flex; - justify-content: space-between; -`; - -const TextFieldWrapper = styled.div` - display: flex; -`; -const StyledTextField = styled(TextField)` - flex: 1; -`; - -function PrimaryKeyFilterEditor({ column, baseTable, formDisplay }) { - const value = formDisplay.getKeyValue(column.columnName); - const editorRef = React.useRef(null); - - React.useEffect(() => { - if (editorRef.current) { - editorRef.current.value = value; - } - }, [value, editorRef.current]); - - const applyFilter = () => { - formDisplay.requestKeyValue(column.columnName, editorRef.current.value); - }; - - const cancelFilter = () => { - formDisplay.cancelRequestKey(); - formDisplay.reload(); - }; - - const handleKeyDown = ev => { - if (ev.keyCode == keycodes.enter) { - applyFilter(); - } - if (ev.keyCode == keycodes.escape) { - cancelFilter(); - } - }; - - return ( - - -
- - x.columnName == column.columnName)} /> -
- {formDisplay.config.formViewKeyRequested && ( - - - - )} -
- - - -
- ); -} - -export default function FormViewFilters(props) { - const { formDisplay } = props; - if (!formDisplay || !formDisplay.baseTable || !formDisplay.baseTable.primaryKey) return null; - const { baseTable } = formDisplay; - const { formFilterColumns, filters } = formDisplay.config || {}; - - const allFilterNames = _.union(_.keys(filters || {}), formFilterColumns || []); - - return ( - - {baseTable.primaryKey.columns.map(col => ( - - ))} - {allFilterNames.map(uniqueName => { - const column = formDisplay.columns.find(x => x.uniqueName == uniqueName) - // const column = baseTable.columns.find(x => x.columnName == columnName); - if (!column) return null; - return ( - - - - { - formDisplay.removeFilter(column.uniqueName); - }} - > - - - - formDisplay.setFilter(column.uniqueName, value)} - /> - - ); - })} - - ); -} diff --git a/packages/web/src/formview/FormViewToolbar.js b/packages/web/src/formview/FormViewToolbar.js deleted file mode 100644 index d418a684a..000000000 --- a/packages/web/src/formview/FormViewToolbar.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import ToolbarButton from '../widgets/ToolbarButton'; - -export default function FormViewToolbar({ switchToTable, onNavigate, reload, reconnect, former, save }) { - return ( - <> - - Table view - - onNavigate('begin')} icon="icon arrow-begin"> - First - - onNavigate('previous')} icon="icon arrow-left"> - Previous - - onNavigate('next')} icon="icon arrow-right"> - Next - - onNavigate('end')} icon="icon arrow-end"> - Last - - - Refresh - - - Reconnect - - former.undo()} icon="icon undo"> - Undo - - former.redo()} icon="icon redo"> - Redo - - - Save - - former.revertAllChanges()} icon="icon close"> - Revert - - - ); -} diff --git a/packages/web/src/formview/Former.ts b/packages/web/src/formview/Former.ts deleted file mode 100644 index cbd1508d2..000000000 --- a/packages/web/src/formview/Former.ts +++ /dev/null @@ -1,53 +0,0 @@ -// export interface GriderRowStatus { -// status: 'regular' | 'updated' | 'deleted' | 'inserted'; -// modifiedFields?: Set; -// insertedFields?: Set; -// deletedFields?: Set; -// } - -export default abstract class Former { - public rowData: any; - - // getRowStatus(index): GriderRowStatus { - // const res: GriderRowStatus = { - // status: 'regular', - // }; - // return res; - // } - beginUpdate() {} - endUpdate() {} - setCellValue(uniqueName: string, value: any) {} - revertRowChanges() {} - revertAllChanges() {} - undo() {} - redo() {} - get editable() { - return false; - } - get canInsert() { - return false; - } - get allowSave() { - return this.containsChanges; - } - get canUndo() { - return false; - } - get canRedo() { - return false; - } - get containsChanges() { - return false; - } - get disableLoadNextPage() { - return false; - } - get errors() { - return null; - } - updateRow(changeObject) { - for (const key of Object.keys(changeObject)) { - this.setCellValue(key, changeObject[key]); - } - } -} diff --git a/packages/web/src/formview/SqlFormView.js b/packages/web/src/formview/SqlFormView.js deleted file mode 100644 index bc1f6ea66..000000000 --- a/packages/web/src/formview/SqlFormView.js +++ /dev/null @@ -1,294 +0,0 @@ -import { changeSetToSql, createChangeSet, TableFormViewDisplay } from 'dbgate-datalib'; -import { findEngineDriver } from 'dbgate-tools'; -import React from 'react'; -import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders'; -import useExtensions from '../utility/useExtensions'; -import FormView from './FormView'; -import axios from '../utility/axios'; -import ChangeSetFormer from './ChangeSetFormer'; -import ConfirmSqlModal from '../modals/ConfirmSqlModal'; -import ErrorMessageModal from '../modals/ErrorMessageModal'; -import { scriptToSql } from 'dbgate-sqltree'; -import useModalState from '../modals/useModalState'; -import useShowModal from '../modals/showModal'; -import stableStringify from 'json-stable-stringify'; - -async function loadRow(props, sql) { - const { conid, database } = props; - - if (!sql) return null; - - const response = await axios.request({ - url: 'database-connections/query-data', - method: 'post', - params: { - conid, - database, - }, - data: { sql }, - }); - - if (response.data.errorMessage) return response.data; - return response.data.rows[0]; -} - -export default function SqlFormView(props) { - // console.log('SqlFormView', props); - const { - formDisplay, - changeSetState, - dispatchChangeSet, - conid, - database, - onReferenceSourceChanged, - refReloadToken, - } = props; - // const [rowData, setRowData] = React.useState(null); - // const [reloadToken, setReloadToken] = React.useState(0); - // const [rowCountInfo, setRowCountInfo] = React.useState(null); - // const [isLoading, setIsLoading] = React.useState(false); - // const loadedFiltersRef = React.useRef(''); - - const confirmSqlModalState = useModalState(); - const [confirmSql, setConfirmSql] = React.useState(''); - const showModal = useShowModal(); - - const changeSet = changeSetState && changeSetState.value; - const changeSetRef = React.useRef(changeSet); - changeSetRef.current = changeSet; - - const [loadProps, setLoadProps] = React.useState({ - isLoadingData: false, - isLoadedData: false, - rowData: null, - isLoadingCount: false, - isLoadedCount: false, - loadedTime: new Date().getTime(), - allRowCount: null, - rowCountBefore: null, - errorMessage: null, - }); - const { - isLoadingData, - rowData, - isLoadedData, - isLoadingCount, - isLoadedCount, - loadedTime, - allRowCount, - rowCountBefore, - errorMessage, - } = loadProps; - - const handleLoadCurrentRow = async () => { - if (isLoadingData) return; - let newLoadedRow = false; - if (formDisplay.config.formViewKeyRequested || formDisplay.config.formViewKey) { - setLoadProps(oldLoadProps => ({ - ...oldLoadProps, - isLoadingData: true, - })); - const row = await loadRow(props, formDisplay.getCurrentRowQuery()); - setLoadProps(oldLoadProps => ({ - ...oldLoadProps, - isLoadingData: false, - isLoadedData: true, - rowData: row, - loadedTime: new Date().getTime(), - })); - newLoadedRow = row; - } - if (formDisplay.config.formViewKeyRequested && newLoadedRow) { - formDisplay.cancelRequestKey(newLoadedRow); - } - if (!newLoadedRow && !formDisplay.config.formViewKeyRequested) { - await handleNavigate('first'); - } - }; - - const handleLoadRowCount = async () => { - setLoadProps(oldLoadProps => ({ - ...oldLoadProps, - isLoadingCount: true, - })); - const countRow = await loadRow(props, formDisplay.getCountQuery()); - const countBeforeRow = await loadRow(props, formDisplay.getBeforeCountQuery()); - - setLoadProps(oldLoadProps => ({ - ...oldLoadProps, - isLoadedCount: true, - isLoadingCount: false, - allRowCount: countRow ? parseInt(countRow.count) : null, - rowCountBefore: countBeforeRow ? parseInt(countBeforeRow.count) : null, - })); - }; - - const handleNavigate = async command => { - setLoadProps(oldLoadProps => ({ - ...oldLoadProps, - isLoadingData: true, - })); - const row = await loadRow(props, formDisplay.navigateRowQuery(command)); - if (row) { - formDisplay.navigate(row); - } - setLoadProps(oldLoadProps => ({ - ...oldLoadProps, - isLoadingData: false, - isLoadedData: true, - isLoadedCount: false, - allRowCount: null, - rowCountBefore: null, - rowData: row, - loadedTime: new Date().getTime(), - })); - }; - - React.useEffect(() => { - if (onReferenceSourceChanged && rowData) onReferenceSourceChanged([rowData], loadedTime); - }, [onReferenceSourceChanged, rowData, refReloadToken]); - - React.useEffect(() => { - if (!formDisplay.isLoadedCorrectly) return; - if (!isLoadedData && !isLoadingData) handleLoadCurrentRow(); - if (isLoadedData && !isLoadingCount && !isLoadedCount) handleLoadRowCount(); - }); - - // React.useEffect(() => { - // loadedFiltersRef.current = formDisplay ? stableStringify(formDisplay.config) : null; - // }, [rowData]); - - // React.useEffect(() => { - // if (formDisplay) handleLoadCurrentRow(); - // setRowCountInfo(null); - // handleLoadRowCount(); - // }, [reloadToken]); - - // React.useEffect(() => { - // if (!formDisplay.isLoadedCorrectly) return; - - // if ( - // formDisplay && - // (!formDisplay.isLoadedCurrentRow(rowData) || - // loadedFiltersRef.current != stableStringify(formDisplay.config.filters)) - // ) { - // handleLoadCurrentRow(); - // } - // setRowCountInfo(null); - // handleLoadRowCount(); - // }, [formDisplay]); - - const reload = () => { - setLoadProps({ - isLoadingData: false, - isLoadedData: false, - isLoadingCount: false, - isLoadedCount: false, - rowData: null, - loadedTime: new Date().getTime(), - allRowCount: null, - rowCountBefore: null, - errorMessage: null, - }); - }; - - React.useEffect(() => { - if (props.masterLoadedTime && props.masterLoadedTime > loadedTime) { - formDisplay.reload(); - } - if (formDisplay.cache.refreshTime > loadedTime) { - reload(); - } - }); - - const former = React.useMemo(() => new ChangeSetFormer(rowData, changeSetState, dispatchChangeSet, formDisplay), [ - rowData, - changeSetState, - dispatchChangeSet, - formDisplay, - ]); - - function handleSave() { - const script = changeSetToSql(changeSetRef.current, formDisplay.dbinfo); - const sql = scriptToSql(formDisplay.driver, script); - setConfirmSql(sql); - confirmSqlModalState.open(); - } - - async function handleConfirmSql() { - const resp = await axios.request({ - url: 'database-connections/query-data', - method: 'post', - params: { - conid, - database, - }, - data: { sql: confirmSql }, - }); - const { errorMessage } = resp.data || {}; - if (errorMessage) { - showModal(modalState => ( - - )); - } else { - dispatchChangeSet({ type: 'reset', value: createChangeSet() }); - setConfirmSql(null); - formDisplay.reload(); - // setReloadToken((x) => x + 1); - } - } - - // const { config, setConfig, cache, setCache, schemaName, pureName, conid, database } = props; - // const { formViewKey } = config; - - // const [display, setDisplay] = React.useState(null); - - // const connection = useConnectionInfo({ conid }); - // const dbinfo = useDatabaseInfo({ conid, database }); - // const extensions = useExtensions(); - - // console.log('SqlFormView.props', props); - - // React.useEffect(() => { - // const newDisplay = connection - // ? new TableFormViewDisplay( - // { schemaName, pureName }, - // findEngineDriver(connection, extensions), - // config, - // setConfig, - // cache, - // setCache, - // dbinfo - // ) - // : null; - // if (!newDisplay) return; - // if (display && display.isLoadedCorrectly && !newDisplay.isLoadedCorrectly) return; - // setDisplay(newDisplay); - // }, [config, cache, conid, database, schemaName, pureName, dbinfo, extensions]); - - return ( - <> - formDisplay.reload()} - onReconnect={async () => { - await axios.post('database-connections/refresh', { conid, database }); - formDisplay.reload(); - }} - allRowCount={allRowCount} - rowCountBefore={rowCountBefore} - /> - - - ); -} diff --git a/packages/web/src/formview/openReferenceForm.js b/packages/web/src/formview/openReferenceForm.js deleted file mode 100644 index 00bffeffd..000000000 --- a/packages/web/src/formview/openReferenceForm.js +++ /dev/null @@ -1,30 +0,0 @@ -import _ from 'lodash'; - -export default function openReferenceForm(rowData, column, openNewTab, conid, database) { - const formViewKey = _.fromPairs( - column.foreignKey.columns.map(({ refColumnName, columnName }) => [refColumnName, rowData[columnName]]) - ); - openNewTab( - { - title: column.foreignKey.refTableName, - icon: 'img table', - tabComponent: 'TableDataTab', - props: { - schemaName: column.foreignKey.refSchemaName, - pureName: column.foreignKey.refTableName, - conid, - database, - objectTypeField: 'tables', - }, - }, - { - grid: { - isFormView: true, - formViewKey, - }, - }, - { - forceNewTab: true, - } - ); -} diff --git a/packages/web/src/freetable/FreeTableColumnEditor.js b/packages/web/src/freetable/FreeTableColumnEditor.js deleted file mode 100644 index 334f2566c..000000000 --- a/packages/web/src/freetable/FreeTableColumnEditor.js +++ /dev/null @@ -1,183 +0,0 @@ -import _ from 'lodash'; -import React from 'react'; -import styled from 'styled-components'; -import { ManagerInnerContainer } from '../datagrid/ManagerStyles'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; -import keycodes from '../utility/keycodes'; - -const Row = styled.div` - // margin-left: 5px; - // margin-right: 5px; - display: flex; - justify-content: space-between; - // padding: 5px; - cursor: pointer; - &:hover { - background-color: ${props => props.theme.manager_background_blue[1]}; - } -`; -const Name = styled.div` - white-space: nowrap; - margin: 5px; -`; -const Buttons = styled.div` - white-space: nowrap; -`; -const Icon = styled(FontIcon)` - // margin-left: 5px; - position: relative; - top: 5px; - &:hover { - background-color: ${props => props.theme.manager_background3}; - } - padding: 5px; -`; -const EditorInput = styled.input` - width: calc(100% - 10px); - background-color: ${props => - // @ts-ignore - props.isError ? props.theme.manager_background_red[1] : props.theme.manager_background}; -`; - -function ColumnNameEditor({ - onEnter, - onBlur = undefined, - focusOnCreate = false, - blurOnEnter = false, - existingNames, - defaultValue = '', - ...other -}) { - const theme = useTheme(); - const [value, setValue] = React.useState(defaultValue || ''); - const editorRef = React.useRef(null); - const isError = value && existingNames && existingNames.includes(value); - const handleKeyDown = event => { - if (value && event.keyCode == keycodes.enter && !isError) { - onEnter(value); - setValue(''); - if (blurOnEnter) editorRef.current.blur(); - } - if (event.keyCode == keycodes.escape) { - setValue(''); - editorRef.current.blur(); - } - }; - const handleBlur = () => { - if (value && !isError) { - onEnter(value); - setValue(''); - } - if (onBlur) onBlur(); - }; - React.useEffect(() => { - if (focusOnCreate) editorRef.current.focus(); - }, [focusOnCreate]); - return ( - setValue(ev.target.value)} - // @ts-ignore - isError={isError} - {...other} - /> - ); -} - -function exchange(array, i1, i2) { - const i1r = (i1 + array.length) % array.length; - const i2r = (i2 + array.length) % array.length; - const res = [...array]; - [res[i1r], res[i2r]] = [res[i2r], res[i1r]]; - return res; -} - -function ColumnManagerRow({ column, onEdit, onRemove, onUp, onDown }) { - const [isHover, setIsHover] = React.useState(false); - const theme = useTheme(); - return ( - setIsHover(true)} onMouseLeave={() => setIsHover(false)} theme={theme}> - {column.columnName} - - - - - - - - ); -} - -function dispatchChangeColumns(props, func, rowFunc = null) { - const { modelState, dispatchModel } = props; - const model = modelState.value; - - dispatchModel({ - type: 'set', - value: { - rows: rowFunc ? model.rows.map(rowFunc) : model.rows, - structure: { - ...model.structure, - columns: func(model.structure.columns), - }, - }, - }); -} - -export default function FreeTableColumnEditor(props) { - const { modelState, dispatchModel } = props; - const [editingColumn, setEditingColumn] = React.useState(null); - const model = modelState.value; - return ( - <> - - {model.structure.columns.map((column, index) => - index == editingColumn ? ( - { - dispatchChangeColumns( - props, - cols => cols.map((col, i) => (index == i ? { columnName } : col)), - row => _.mapKeys(row, (v, k) => (k == column.columnName ? columnName : k)) - ); - }} - onBlur={() => setEditingColumn(null)} - focusOnCreate - blurOnEnter - existingNames={model.structure.columns.map(x => x.columnName)} - /> - ) : ( - setEditingColumn(index)} - onRemove={() => { - dispatchChangeColumns(props, cols => cols.filter((c, i) => i != index)); - }} - onUp={() => { - dispatchChangeColumns(props, cols => exchange(cols, index, index - 1)); - }} - onDown={() => { - dispatchChangeColumns(props, cols => exchange(cols, index, index + 1)); - }} - /> - ) - )} - { - dispatchChangeColumns(props, cols => [...cols, { columnName }]); - }} - placeholder="New column" - existingNames={model.structure.columns.map(x => x.columnName)} - /> - - - ); -} diff --git a/packages/web/src/freetable/FreeTableGrid.js b/packages/web/src/freetable/FreeTableGrid.js deleted file mode 100644 index 33524ae40..000000000 --- a/packages/web/src/freetable/FreeTableGrid.js +++ /dev/null @@ -1,92 +0,0 @@ -import { runMacro } from 'dbgate-datalib'; -import React from 'react'; -import _ from 'lodash'; -import styled from 'styled-components'; - -import { HorizontalSplitter, VerticalSplitter } from '../widgets/Splitter'; -import FreeTableColumnEditor from './FreeTableColumnEditor'; -import FreeTableGridCore from './FreeTableGridCore'; -import MacroDetail from './MacroDetail'; -import MacroManager from './MacroManager'; -import WidgetColumnBar, { WidgetColumnBarItem } from '../widgets/WidgetColumnBar'; -import useTheme from '../theme/useTheme'; - -const LeftContainer = styled.div` - background-color: ${props => props.theme.manager_background}; - display: flex; - flex: 1; -`; - -const DataGridContainer = styled.div` - position: relative; - flex-grow: 1; -`; - -function extractMacroValuesForMacro(macroValues, macro) { - if (!macro) return {}; - return { - ..._.fromPairs((macro.args || []).filter(x => x.default != null).map(x => [x.name, x.default])), - ..._.mapKeys(macroValues, (v, k) => k.replace(/^.*#/, '')), - }; -} - -export default function FreeTableGrid(props) { - const { modelState, dispatchModel } = props; - const theme = useTheme(); - const [managerSize, setManagerSize] = React.useState(0); - const [selectedMacro, setSelectedMacro] = React.useState(null); - const [macroValues, setMacroValues] = React.useState({}); - const [selectedCells, setSelectedCells] = React.useState([]); - const handleExecuteMacro = () => { - const newModel = runMacro( - selectedMacro, - extractMacroValuesForMacro(macroValues, selectedMacro), - modelState.value, - false, - selectedCells - ); - dispatchModel({ type: 'set', value: newModel }); - setSelectedMacro(null); - }; - // console.log('macroValues', macroValues); - return ( - - - - - - - - - - - - - - - - {!!selectedMacro && ( - - )} - - - - ); -} diff --git a/packages/web/src/freetable/FreeTableGridCore.js b/packages/web/src/freetable/FreeTableGridCore.js deleted file mode 100644 index 7f518a2bf..000000000 --- a/packages/web/src/freetable/FreeTableGridCore.js +++ /dev/null @@ -1,78 +0,0 @@ -import { createGridCache, FreeTableGridDisplay } from 'dbgate-datalib'; -import React from 'react'; -import DataGridCore from '../datagrid/DataGridCore'; -import useShowModal from '../modals/showModal'; -import axios from '../utility/axios'; -import keycodes from '../utility/keycodes'; -import FreeTableGrider from './FreeTableGrider'; -import MacroPreviewGrider from './MacroPreviewGrider'; -import uuidv1 from 'uuid/v1'; -import ImportExportModal from '../modals/ImportExportModal'; - -export default function FreeTableGridCore(props) { - const { - modelState, - dispatchModel, - config, - setConfig, - macroPreview, - macroValues, - onSelectionChanged, - setSelectedMacro, - } = props; - const [cache, setCache] = React.useState(createGridCache()); - const [selectedCells, setSelectedCells] = React.useState([]); - const showModal = useShowModal(); - const grider = React.useMemo( - () => - macroPreview - ? new MacroPreviewGrider(modelState.value, macroPreview, macroValues, selectedCells) - : FreeTableGrider.factory(props), - [ - ...FreeTableGrider.factoryDeps(props), - macroPreview, - macroPreview ? macroValues : null, - macroPreview ? selectedCells : null, - ] - ); - const display = React.useMemo( - () => new FreeTableGridDisplay(grider.model || modelState.value, config, setConfig, cache, setCache), - [modelState.value, config, cache, grider] - ); - - async function exportGrid() { - const jslid = uuidv1(); - await axios.post('jsldata/save-free-table', { jslid, data: modelState.value }); - const initialValues = {}; - initialValues.sourceStorageType = 'jsldata'; - initialValues.sourceJslId = jslid; - initialValues.sourceList = ['editor-data']; - showModal(modalState => ); - } - - const handleSelectionChanged = React.useCallback( - cells => { - if (onSelectionChanged) onSelectionChanged(cells); - setSelectedCells(cells); - }, - [setSelectedCells] - ); - - const handleKeyDown = React.useCallback(event => { - if (event.keyCode == keycodes.escape) { - setSelectedMacro(null); - } - }, []); - - return ( - - ); -} diff --git a/packages/web/src/freetable/FreeTableGrider.ts b/packages/web/src/freetable/FreeTableGrider.ts deleted file mode 100644 index b5b5059bf..000000000 --- a/packages/web/src/freetable/FreeTableGrider.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { FreeTableModel } from 'dbgate-datalib'; -import Grider, { GriderRowStatus } from '../datagrid/Grider'; - -export default class FreeTableGrider extends Grider { - public model: FreeTableModel; - private batchModel: FreeTableModel; - - constructor(public modelState, public dispatchModel) { - super(); - this.model = modelState && modelState.value; - } - getRowData(index: any) { - return this.model.rows[index]; - } - get rowCount() { - return this.model.rows.length; - } - get currentModel(): FreeTableModel { - return this.batchModel || this.model; - } - set currentModel(value) { - if (this.batchModel) this.batchModel = value; - else this.dispatchModel({ type: 'set', value }); - } - setCellValue(index: number, uniqueName: string, value: any) { - const model = this.currentModel; - if (model.rows[index]) - this.currentModel = { - ...model, - rows: model.rows.map((row, i) => (index == i ? { ...row, [uniqueName]: value } : row)), - }; - } - get editable() { - return true; - } - get canInsert() { - return true; - } - get allowSave() { - return true; - } - insertRow(): number { - const model = this.currentModel; - this.currentModel = { - ...model, - rows: [...model.rows, {}], - }; - return this.currentModel.rows.length - 1; - } - deleteRow(index: number) { - const model = this.currentModel; - this.currentModel = { - ...model, - rows: model.rows.filter((row, i) => index != i), - }; - } - beginUpdate() { - this.batchModel = this.model; - } - endUpdate() { - if (this.model != this.batchModel) { - this.dispatchModel({ type: 'set', value: this.batchModel }); - this.batchModel = null; - } - } - - static factory({ modelState, dispatchModel }): FreeTableGrider { - return new FreeTableGrider(modelState, dispatchModel); - } - static factoryDeps({ modelState, dispatchModel }) { - return [modelState, dispatchModel]; - } - undo() { - this.dispatchModel({ type: 'undo' }); - } - redo() { - this.dispatchModel({ type: 'redo' }); - } - get canUndo() { - return this.modelState.canUndo; - } - get canRedo() { - return this.modelState.canRedo; - } -} diff --git a/packages/web/src/freetable/MacroDetail.js b/packages/web/src/freetable/MacroDetail.js deleted file mode 100644 index 078415135..000000000 --- a/packages/web/src/freetable/MacroDetail.js +++ /dev/null @@ -1,121 +0,0 @@ -import React from 'react'; -import ToolbarButton from '../widgets/ToolbarButton'; -import styled from 'styled-components'; -import { TabPage, TabControl } from '../widgets/TabControl'; -import dimensions from '../theme/dimensions'; -import GenericEditor from '../sqleditor/GenericEditor'; -import MacroParameters from './MacroParameters'; -import { WidgetTitle } from '../widgets/WidgetStyles'; -import { FormButton } from '../utility/forms'; -import FormStyledButton from '../widgets/FormStyledButton'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; - -const Container = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - background: ${props => props.theme.gridheader_background_cyan[0]}; - height: ${dimensions.toolBar.height}px; - min-height: ${dimensions.toolBar.height}px; - overflow: hidden; - border-top: 1px solid ${props => props.theme.border}; - border-bottom: 1px solid ${props => props.theme.border}; -`; - -const Header = styled.div` - font-weight: bold; - margin-left: 10px; - display: flex; -`; - -const HeaderText = styled.div` - margin-left: 10px; -`; - -const MacroDetailContainer = styled.div` - position: absolute; - display: flex; - flex-direction: column; - top: 0; - left: 0; - right: 0; - bottom: 0; -`; - -const MacroDetailTabWrapper = styled.div` - display: flex; - overflow-y: auto; -`; - -const MacroSection = styled.div` - margin: 5px; -`; - -const TextWrapper = styled.div` - margin: 5px; -`; - -const Buttons = styled.div` - display: flex; -`; - -function MacroHeader({ selectedMacro, setSelectedMacro, onExecute }) { - const theme = useTheme(); - return ( - -
- - {selectedMacro.title} -
- - - Execute - - setSelectedMacro(null)} patchY={6}> - Close - - -
- ); -} - -export default function MacroDetail({ selectedMacro, setSelectedMacro, onChangeValues, macroValues, onExecute }) { - return ( - - - - - - - Execute - - - - - Parameters - {selectedMacro.args && selectedMacro.args.length > 0 ? ( - - ) : ( - This macro has no parameters - )} - - - Description - {selectedMacro.description} - - - - - - - - - ); -} diff --git a/packages/web/src/freetable/MacroManager.js b/packages/web/src/freetable/MacroManager.js deleted file mode 100644 index cbd288c52..000000000 --- a/packages/web/src/freetable/MacroManager.js +++ /dev/null @@ -1,41 +0,0 @@ -import styled from 'styled-components'; -import _ from 'lodash'; -import React from 'react'; -import { ManagerInnerContainer } from '../datagrid/ManagerStyles'; -import SearchInput from '../widgets/SearchInput'; -import { WidgetTitle } from '../widgets/WidgetStyles'; -import macros from './macros'; -import { AppObjectList } from '../appobj/AppObjectList'; -import MacroAppObject from '../appobj/MacroAppObject'; - -const SearchBoxWrapper = styled.div` - display: flex; - margin-bottom: 5px; -`; - -export default function MacroManager({ managerSize, selectedMacro, setSelectedMacro }) { - const [filter, setFilter] = React.useState(''); - - return ( - <> - - - - - setSelectedMacro(macro)} - getCommonProps={data => ({ - isBold: selectedMacro && selectedMacro.name == data.name, - })} - filter={filter} - groupFunc={data => data.group} - /> - {/* {macros.map((macro) => ( - - ))} */} - - - ); -} diff --git a/packages/web/src/freetable/MacroParameters.js b/packages/web/src/freetable/MacroParameters.js deleted file mode 100644 index 30ad8f885..000000000 --- a/packages/web/src/freetable/MacroParameters.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import FormArgumentList from '../utility/FormArgumentList'; -import { FormProvider } from '../utility/FormProvider'; - -export default function MacroParameters({ args, onChangeValues, macroValues, namePrefix }) { - if (!args || args.length == 0) return null; - const initialValues = { - ..._.fromPairs(args.filter(x => x.default != null).map(x => [`${namePrefix}${x.name}`, x.default])), - ...macroValues, - }; - return ( - - - - ); -} diff --git a/packages/web/src/freetable/MacroPreviewGrider.ts b/packages/web/src/freetable/MacroPreviewGrider.ts deleted file mode 100644 index 6503dc09f..000000000 --- a/packages/web/src/freetable/MacroPreviewGrider.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { FreeTableModel, MacroDefinition, MacroSelectedCell, runMacro } from 'dbgate-datalib'; -import Grider, { GriderRowStatus } from '../datagrid/Grider'; -import _ from 'lodash'; - -function convertToSet(row, field) { - if (!row) return null; - if (!row[field]) return null; - if (_.isSet(row[field])) return row[field]; - return new Set(row[field]); -} - -export default class MacroPreviewGrider extends Grider { - model: FreeTableModel; - _errors: string[] = []; - constructor(model: FreeTableModel, macro: MacroDefinition, macroArgs: {}, selectedCells: MacroSelectedCell[]) { - super(); - this.model = runMacro(macro, macroArgs, model, true, selectedCells, this._errors); - } - - get errors() { - return this._errors; - } - - getRowStatus(index): GriderRowStatus { - const row = this.model.rows[index]; - return { - status: (row && row.__rowStatus) || 'regular', - modifiedFields: convertToSet(row, '__modifiedFields'), - insertedFields: convertToSet(row, '__insertedFields'), - deletedFields: convertToSet(row, '__deletedFields'), - }; - } - - getRowData(index: any) { - return this.model.rows[index]; - } - get rowCount() { - return this.model.rows.length; - } -} diff --git a/packages/web/src/freetable/macros.js b/packages/web/src/freetable/macros.js deleted file mode 100644 index f614dba4b..000000000 --- a/packages/web/src/freetable/macros.js +++ /dev/null @@ -1,273 +0,0 @@ -const macros = [ - { - title: 'Remove diacritics', - name: 'removeDiacritics', - group: 'Text', - description: 'Removes diacritics from selected cells', - type: 'transformValue', - code: `return modules.lodash.deburr(value)`, - }, - { - title: 'Search & replace text', - name: 'stringReplace', - group: 'Text', - description: 'Search & replace text or regular expression', - type: 'transformValue', - args: [ - { - type: 'text', - label: 'Find', - name: 'find', - }, - { - type: 'text', - label: 'Replace with', - name: 'replace', - }, - { - type: 'checkbox', - label: 'Case sensitive', - name: 'caseSensitive', - }, - { - type: 'checkbox', - label: 'Regular expression', - name: 'isRegex', - }, - ], - code: ` -const rtext = args.isRegex ? args.find : modules.lodash.escapeRegExp(args.find); -const rflags = args.caseSensitive ? 'g' : 'ig'; -return value ? value.toString().replace(new RegExp(rtext, rflags), args.replace || '') : value - `, - }, - { - title: 'Change text case', - name: 'changeTextCase', - group: 'Text', - description: 'Uppercase, lowercase and other case functions', - type: 'transformValue', - args: [ - { - type: 'select', - options: ['toUpper', 'toLower', 'lowerCase', 'upperCase', 'kebabCase', 'snakeCase', 'camelCase', 'startCase'], - label: 'Type', - name: 'type', - default: 'toUpper', - }, - ], - code: `return modules.lodash[args.type](value)`, - }, - { - title: 'Row index', - name: 'rowIndex', - group: 'Tools', - description: 'Index of row from 1 (autoincrement)', - type: 'transformValue', - code: `return rowIndex + 1`, - }, - { - title: 'Generate UUID', - name: 'uuidv1', - group: 'Tools', - description: 'Generate unique identifier', - type: 'transformValue', - args: [ - { - type: 'select', - options: [ - { value: 'uuidv1', name: 'V1 - from timestamp' }, - { value: 'uuidv4', name: 'V4 - random generated' }, - ], - label: 'Version', - name: 'version', - default: 'uuidv1', - }, - ], - code: `return modules[args.version]()`, - }, - { - title: 'Current date', - name: 'currentDate', - group: 'Tools', - description: 'Gets current date', - type: 'transformValue', - args: [ - { - type: 'text', - label: 'Format', - name: 'format', - default: 'YYYY-MM-DD HH:mm:ss', - }, - ], - code: `return modules.moment().format(args.format)`, - }, - { - title: 'Duplicate rows', - name: 'duplicateRows', - group: 'Tools', - description: 'Duplicate selected rows', - type: 'transformRows', - code: ` -const selectedRowIndexes = modules.lodash.uniq(selectedCells.map(x => x.row)); -const selectedRows = modules.lodash.groupBy(selectedCells, 'row'); -const maxIndex = modules.lodash.max(selectedRowIndexes); -return [ - ...rows.slice(0, maxIndex + 1), - ...selectedRowIndexes.map(index => ({ - ...modules.lodash.pick(rows[index], selectedRows[index].map(x => x.column)), - __rowStatus: 'inserted', - })), - ...rows.slice(maxIndex + 1), -] - `, - }, - { - title: 'Delete empty rows', - name: 'deleteEmptyRows', - group: 'Tools', - description: 'Delete empty rows - rows with all values null or empty string', - type: 'transformRows', - code: ` -return rows.map(row => { - if (cols.find(col => row[col])) return row; - return { - ...row, - __rowStatus: 'deleted', - }; -}) -`, - }, - { - title: 'Duplicate columns', - name: 'duplicateColumns', - group: 'Tools', - description: 'Duplicate selected columns', - type: 'transformData', - code: ` -const selectedColumnNames = modules.lodash.uniq(selectedCells.map(x => x.column)); -const selectedRowIndexes = modules.lodash.uniq(selectedCells.map(x => x.row)); -const addedColumnNames = selectedColumnNames.map(col => (args.prefix || '') + col + (args.postfix || '')); -const resultRows = rows.map((row, rowIndex) => ({ - ...row, - ...(selectedRowIndexes.includes(rowIndex) ? modules.lodash.fromPairs(selectedColumnNames.map(col => [(args.prefix || '') + col + (args.postfix || ''), row[col]])) : {}), - __insertedFields: addedColumnNames, -})); -const resultCols = [ - ...cols, - ...addedColumnNames, -]; -return { - rows: resultRows, - cols: resultCols, -} - `, - args: [ - { - type: 'text', - label: 'Prefix', - name: 'prefix', - }, - { - type: 'text', - label: 'Postfix', - name: 'postfix', - default: '_copy', - }, - ], - }, - { - title: 'Extract date fields', - name: 'extractDateFields', - group: 'Tools', - description: 'Extract yaear, month, day and other date/time fields from selection and adds it as new columns', - type: 'transformData', - code: ` -const selectedColumnNames = modules.lodash.uniq(selectedCells.map(x => x.column)); -const selectedRowIndexes = modules.lodash.uniq(selectedCells.map(x => x.row)); -const addedColumnNames = modules.lodash.compact([args.year, args.month, args.day, args.hour, args.minute, args.second]); -const selectedRows = modules.lodash.groupBy(selectedCells, 'row'); -const resultRows = rows.map((row, rowIndex) => { - if (!selectedRowIndexes.includes(rowIndex)) return { - ...row, - __insertedFields: addedColumnNames, - }; - let mom = null; - for(const cell of selectedRows[rowIndex]) { - const m = modules.moment(row[cell.column]); - if (m.isValid()) { - mom = m; - break; - } - } - if (!mom) return { - ...row, - __insertedFields: addedColumnNames, - }; - - const fields = { - [args.year]: mom.year(), - [args.month]: mom.month() + 1, - [args.day]: mom.day(), - [args.hour]: mom.hour(), - [args.minute]: mom.minute(), - [args.second]: mom.second(), - }; - - return { - ...row, - ...modules.lodash.pick(fields, addedColumnNames), - __insertedFields: addedColumnNames, - } -}); -const resultCols = [ - ...cols, - ...addedColumnNames, -]; -return { - rows: resultRows, - cols: resultCols, -} - `, - args: [ - { - type: 'text', - label: 'Year name', - name: 'year', - default: 'year', - }, - { - type: 'text', - label: 'Month name', - name: 'month', - default: 'month', - }, - { - type: 'text', - label: 'Day name', - name: 'day', - default: 'day', - }, - { - type: 'text', - label: 'Hour name', - name: 'hour', - default: 'hour', - }, - { - type: 'text', - label: 'Minute name', - name: 'minute', - default: 'minute', - }, - { - type: 'text', - label: 'Second name', - name: 'second', - default: 'second', - }, - ], - }, -]; - -export default macros; diff --git a/packages/web/src/freetable/useNewFreeTable.js b/packages/web/src/freetable/useNewFreeTable.js deleted file mode 100644 index 97830a607..000000000 --- a/packages/web/src/freetable/useNewFreeTable.js +++ /dev/null @@ -1,14 +0,0 @@ -import _ from 'lodash'; -import useOpenNewTab from '../utility/useOpenNewTab'; - -export default function useNewFreeTable() { - const openNewTab = useOpenNewTab(); - - return ({ title = undefined, ...props } = {}) => - openNewTab({ - title: title || 'Data #', - icon: 'img free-table', - tabComponent: 'FreeTableTab', - props, - }); -} diff --git a/packages/web/src/icons.js b/packages/web/src/icons.js deleted file mode 100644 index fac6562c7..000000000 --- a/packages/web/src/icons.js +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react'; - -const iconNames = { - 'icon minus-box': 'mdi mdi-minus-box-outline', - 'icon plus-box': 'mdi mdi-plus-box-outline', - 'icon invisible-box': 'mdi mdi-minus-box-outline icon-invisible', - 'icon cloud-upload': 'mdi mdi-cloud-upload', - 'icon import': 'mdi mdi-application-import', - 'icon export': 'mdi mdi-application-export', - 'icon new-connection': 'mdi mdi-database-plus', - 'icon tables': 'mdi mdi-table-multiple', - 'icon favorite': 'mdi mdi-star', - 'icon share': 'mdi mdi-share-variant', - 'icon add': 'mdi mdi-plus-circle', - 'icon connection': 'mdi mdi-connection', - - 'icon database': 'mdi mdi-database', - 'icon server': 'mdi mdi-server', - 'icon table': 'mdi mdi-table', - 'icon archive': 'mdi mdi-archive', - 'icon file': 'mdi mdi-file', - 'icon loading': 'mdi mdi-loading mdi-spin', - 'icon close': 'mdi mdi-close', - 'icon filter': 'mdi mdi-filter', - 'icon filter-off': 'mdi mdi-filter-off', - 'icon reload': 'mdi mdi-reload', - 'icon undo': 'mdi mdi-undo', - 'icon redo': 'mdi mdi-redo', - 'icon save': 'mdi mdi-content-save', - 'icon account': 'mdi mdi-account', - 'icon sql-file': 'mdi mdi-file', - 'icon web': 'mdi mdi-web', - 'icon home': 'mdi mdi-home', - 'icon query-design': 'mdi mdi-vector-polyline-edit', - 'icon form': 'mdi mdi-form-select', - - 'icon edit': 'mdi mdi-pencil', - 'icon delete': 'mdi mdi-delete', - 'icon arrow-up': 'mdi mdi-arrow-up', - 'icon arrow-down': 'mdi mdi-arrow-down', - 'icon arrow-left': 'mdi mdi-arrow-left', - 'icon arrow-begin': 'mdi mdi-arrow-collapse-left', - 'icon arrow-end': 'mdi mdi-arrow-collapse-right', - 'icon arrow-right': 'mdi mdi-arrow-right', - 'icon format-code': 'mdi mdi-code-tags-check', - 'icon show-wizard': 'mdi mdi-comment-edit', - 'icon disconnected': 'mdi mdi-lan-disconnect', - 'icon theme': 'mdi mdi-brightness-6', - 'icon error': 'mdi mdi-close-circle', - 'icon ok': 'mdi mdi-check-circle', - 'icon markdown': 'mdi mdi-application', - 'icon preview': 'mdi mdi-file-find', - 'icon eye': 'mdi mdi-eye', - - 'icon run': 'mdi mdi-play', - 'icon chevron-down': 'mdi mdi-chevron-down', - 'icon chevron-left': 'mdi mdi-chevron-left', - 'icon chevron-right': 'mdi mdi-chevron-right', - 'icon chevron-up': 'mdi mdi-chevron-up', - 'icon plugin': 'mdi mdi-toy-brick', - - 'img ok': 'mdi mdi-check-circle color-green-8', - 'img alert': 'mdi mdi-alert-circle color-blue-6', - 'img error': 'mdi mdi-close-circle color-red-7', - 'img warn': 'mdi mdi-alert color-gold-7', - // 'img statusbar-ok': 'mdi mdi-check-circle color-on-statusbar-green', - - 'img archive': 'mdi mdi-table color-gold-7', - 'img archive-folder': 'mdi mdi-database-outline color-green-7', - 'img autoincrement': 'mdi mdi-numeric-1-box-multiple-outline', - 'img column': 'mdi mdi-table-column', - 'img server': 'mdi mdi-server color-blue-7', - 'img primary-key': 'mdi mdi-key-star color-yellow-7', - 'img foreign-key': 'mdi mdi-key-link', - 'img sql-file': 'mdi mdi-file', - 'img shell': 'mdi mdi-flash color-blue-7', - 'img chart': 'mdi mdi-chart-bar color-magenta-7', - 'img markdown': 'mdi mdi-application color-red-7', - 'img preview': 'mdi mdi-file-find color-red-7', - 'img favorite': 'mdi mdi-star color-yellow-7', - 'img query-design': 'mdi mdi-vector-polyline-edit color-red-7', - - 'img free-table': 'mdi mdi-table color-green-7', - 'img macro': 'mdi mdi-hammer-wrench', - - 'img database': 'mdi mdi-database color-gold-7', - 'img table': 'mdi mdi-table color-blue-7', - 'img view': 'mdi mdi-table color-magenta-7', - 'img procedure': 'mdi mdi-cog color-blue-7', - 'img function': 'mdi mdi-function-variant', - - 'img sort-asc': 'mdi mdi-sort-alphabetical-ascending color-green', - 'img sort-desc': 'mdi mdi-sort-alphabetical-descending color-green', - - 'img reference': 'mdi mdi-link-box', - 'img link': 'mdi mdi-link', - 'img filter': 'mdi mdi-filter', - 'img group': 'mdi mdi-group', -}; - -export function FontIcon({ icon, className = '', ...other }) { - if (!icon) return null; - let cls = icon; - if (icon.startsWith('icon ') || icon.startsWith('img ')) { - cls = iconNames[icon]; - if (!cls) return null; - } - return ; -} - -export function ExpandIcon({ isBlank = false, isExpanded = false, ...other }) { - if (isBlank) { - return ; - } - return ; -} - -export function ChevronExpandIcon({ isBlank = false, isExpanded = false, ...other }) { - if (isBlank) { - return ; - } - return ; -} diff --git a/packages/web/src/impexp/ImportExportConfigurator.js b/packages/web/src/impexp/ImportExportConfigurator.js deleted file mode 100644 index 17c843aac..000000000 --- a/packages/web/src/impexp/ImportExportConfigurator.js +++ /dev/null @@ -1,580 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import FormStyledButton from '../widgets/FormStyledButton'; -import styled from 'styled-components'; -import { - FormReactSelect, - FormConnectionSelect, - FormDatabaseSelect, - FormTablesSelect, - FormSchemaSelect, - FormArchiveFolderSelect, - FormArchiveFilesSelect, -} from '../utility/forms'; -import { useArchiveFiles, useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders'; -import TableControl, { TableColumn } from '../utility/TableControl'; -import { TextField, SelectField, CheckboxField } from '../utility/inputs'; -import { createPreviewReader, getActionOptions, getTargetName } from './createImpExpScript'; -import getElectron from '../utility/getElectron'; -import ErrorInfo from '../widgets/ErrorInfo'; -import getAsArray from '../utility/getAsArray'; -import LoadingInfo from '../widgets/LoadingInfo'; -import SqlEditor from '../sqleditor/SqlEditor'; -import { useUploadsProvider } from '../utility/UploadsProvider'; -import { FontIcon } from '../icons'; -import useTheme from '../theme/useTheme'; -import { findFileFormat, getFileFormatDirections } from '../utility/fileformats'; -import FormArgumentList from '../utility/FormArgumentList'; -import useExtensions from '../utility/useExtensions'; -import UploadButton from '../utility/UploadButton'; -import useShowModal from '../modals/showModal'; -import ChangeDownloadUrlModal from '../modals/ChangeDownloadUrlModal'; -import { useForm } from '../utility/FormProvider'; - -const Container = styled.div` - // max-height: 50vh; - // overflow-y: scroll; - flex: 1; -`; - -const Wrapper = styled.div` - display: flex; -`; - -const SourceListWrapper = styled.div` - margin: 10px; -`; - -const Column = styled.div` - margin: 10px; - flex: 1; -`; - -const Label = styled.div` - margin: 5px; - margin-top: 15px; - color: ${props => props.theme.modal_font2}; -`; - -const SourceNameWrapper = styled.div` - display: flex; - justify-content: space-between; -`; - -const SourceNameButtons = styled.div` - display: flex; -`; - -const IconButtonWrapper = styled.div` - &:hover { - background-color: ${props => props.theme.modal_background2}; - } - cursor: pointer; - color: ${props => props.theme.modal_font_blue[7]}; - margin-left: 5px; -`; - -const SqlWrapper = styled.div` - position: relative; - height: 100px; - width: 20vw; -`; - -const DragWrapper = styled.div` - padding: 10px; - background: ${props => props.theme.modal_background2}; -`; - -const ArrowWrapper = styled.div` - font-size: 30px; - color: ${props => props.theme.modal_font_blue[7]}; - align-self: center; -`; - -const Title = styled.div` - font-size: 20px; - text-align: center; - margin: 10px 0px; -`; - -const ButtonsLine = styled.div` - display: flex; -`; - -function getFileFilters(extensions, storageType) { - const res = []; - const format = findFileFormat(extensions, storageType); - if (format) res.push({ name: format.name, extensions: [format.extension] }); - res.push({ name: 'All Files', extensions: ['*'] }); - return res; -} - -async function addFileToSourceListDefault({ fileName, shortName, isDownload }, newSources, newValues) { - const sourceName = shortName; - newSources.push(sourceName); - newValues[`sourceFile_${sourceName}`] = { - fileName, - isDownload, - }; -} - -async function addFilesToSourceList(extensions, files, values, setValues, preferedStorageType, setPreviewSource) { - const newSources = []; - const newValues = {}; - const storage = preferedStorageType || values.sourceStorageType; - for (const file of getAsArray(files)) { - const format = findFileFormat(extensions, storage); - if (format) { - await (format.addFileToSourceList || addFileToSourceListDefault)(file, newSources, newValues); - } - } - newValues['sourceList'] = [...(values.sourceList || []).filter(x => !newSources.includes(x)), ...newSources]; - if (preferedStorageType && preferedStorageType != values.sourceStorageType) { - newValues['sourceStorageType'] = preferedStorageType; - } - setValues({ - ...values, - ...newValues, - }); - if (setPreviewSource && newSources.length == 1) { - setPreviewSource(newSources[0]); - } -} - -function ElectronFilesInput() { - const { values, setValues } = useForm(); - const electron = getElectron(); - const [isLoading, setIsLoading] = React.useState(false); - const extensions = useExtensions(); - - const handleClick = async () => { - const files = electron.remote.dialog.showOpenDialogSync(electron.remote.getCurrentWindow(), { - properties: ['openFile', 'multiSelections'], - filters: getFileFilters(extensions, values.sourceStorageType), - }); - if (files) { - const path = window.require('path'); - try { - setIsLoading(true); - await addFilesToSourceList( - extensions, - files.map(full => ({ - fileName: full, - shortName: path.parse(full).name, - })), - values, - setValues - ); - } finally { - setIsLoading(false); - } - } - }; - - return ( - <> - - {isLoading && } - - ); -} - -function extractUrlName(url, values) { - const match = url.match(/\/([^/]+)($|\?)/); - if (match) { - const res = match[1]; - if (res.includes('.')) { - return res.slice(0, res.indexOf('.')); - } - return res; - } - return `url${values && values.sourceList ? values.sourceList.length + 1 : '1'}`; -} - -function FilesInput({ setPreviewSource = undefined }) { - const theme = useTheme(); - const electron = getElectron(); - const showModal = useShowModal(); - const { values, setValues } = useForm(); - const extensions = useExtensions(); - const doAddUrl = url => { - addFilesToSourceList( - extensions, - [ - { - fileName: url, - shortName: extractUrlName(url, values), - isDownload: true, - }, - ], - values, - setValues, - null, - setPreviewSource - ); - }; - const handleAddUrl = () => - showModal(modalState => ); - return ( - <> - - {electron ? : } - - - Drag & drop imported files here - - ); -} - -function SourceTargetConfig({ - direction, - storageTypeField, - connectionIdField, - databaseNameField, - archiveFolderField, - schemaNameField, - tablesField = undefined, - engine = undefined, - setPreviewSource = undefined, -}) { - const extensions = useExtensions(); - const theme = useTheme(); - const { values, setFieldValue } = useForm(); - const types = - values[storageTypeField] == 'jsldata' - ? [{ value: 'jsldata', label: 'Query result data', directions: ['source'] }] - : [ - { value: 'database', label: 'Database', directions: ['source', 'target'] }, - ...extensions.fileFormats.map(format => ({ - value: format.storageType, - label: `${format.name} files(s)`, - directions: getFileFormatDirections(format), - })), - { value: 'query', label: 'SQL Query', directions: ['source'] }, - { value: 'archive', label: 'Archive', directions: ['source', 'target'] }, - ]; - const storageType = values[storageTypeField]; - const dbinfo = useDatabaseInfo({ conid: values[connectionIdField], database: values[databaseNameField] }); - const archiveFiles = useArchiveFiles({ folder: values[archiveFolderField] }); - const format = findFileFormat(extensions, storageType); - return ( - - {direction == 'source' && ( - - <FontIcon icon="icon import" /> Source configuration - - )} - {direction == 'target' && ( - - <FontIcon icon="icon export" /> Target configuration - - )} - x.directions.includes(direction))} name={storageTypeField} /> - {(storageType == 'database' || storageType == 'query') && ( - <> - - - - - - )} - {storageType == 'database' && ( - <> - - - {tablesField && ( - <> - - -
- - setFieldValue( - 'sourceList', - _.uniq([...(values.sourceList || []), ...(dbinfo && dbinfo.tables.map(x => x.pureName))]) - ) - } - /> - - setFieldValue( - 'sourceList', - _.uniq([...(values.sourceList || []), ...(dbinfo && dbinfo.views.map(x => x.pureName))]) - ) - } - /> - setFieldValue('sourceList', [])} /> -
- - )} - - )} - {storageType == 'query' && ( - <> - - - setFieldValue('sourceSql', value)} - engine={engine} - focusOnCreate - /> - - - )} - - {storageType == 'archive' && ( - <> - - - - )} - - {storageType == 'archive' && direction == 'source' && ( - <> - - -
- - setFieldValue( - 'sourceList', - _.uniq([...(values.sourceList || []), ...(archiveFiles && archiveFiles.map(x => x.name))]) - ) - } - /> - setFieldValue('sourceList', [])} /> -
- - )} - - {!!format && direction == 'source' && } - - {format && format.args && ( - !arg.direction || arg.direction == direction)} - namePrefix={`${direction}_${format.storageType}_`} - /> - )} -
- ); -} - -function SourceName({ name }) { - const { values, setFieldValue } = useForm(); - const theme = useTheme(); - const showModal = useShowModal(); - const obj = values[`sourceFile_${name}`]; - const handleDelete = () => { - setFieldValue( - 'sourceList', - values.sourceList.filter(x => x != name) - ); - }; - const doChangeUrl = url => { - setFieldValue(`sourceFile_${name}`, { fileName: url, isDownload: true }); - }; - const handleChangeUrl = () => { - showModal(modalState => ( - - )); - }; - - return ( - -
{name}
- - {obj && !!obj.isDownload && ( - - - - )} - - - - -
- ); -} - -export default function ImportExportConfigurator({ - uploadedFile = undefined, - openedFile = undefined, - onChangePreview = undefined, -}) { - const { values, setFieldValue, setValues } = useForm(); - const targetDbinfo = useDatabaseInfo({ conid: values.targetConnectionId, database: values.targetDatabaseName }); - const sourceConnectionInfo = useConnectionInfo({ conid: values.sourceConnectionId }); - const { engine: sourceEngine } = sourceConnectionInfo || {}; - const { sourceList } = values; - const { setUploadListener } = useUploadsProvider(); - const theme = useTheme(); - const [previewSource, setPreviewSource] = React.useState(null); - const extensions = useExtensions(); - - const handleUpload = React.useCallback( - file => { - addFilesToSourceList( - extensions, - [ - { - fileName: file.filePath, - shortName: file.shortName, - }, - ], - values, - setValues, - !sourceList || sourceList.length == 0 ? file.storageType : null, - setPreviewSource - ); - // setFieldValue('sourceList', [...(sourceList || []), file.originalName]); - }, - [extensions, setFieldValue, sourceList, values] - ); - - React.useEffect(() => { - setUploadListener(() => handleUpload); - return () => { - setUploadListener(null); - }; - }, [handleUpload]); - - React.useEffect(() => { - if (uploadedFile) { - handleUpload(uploadedFile); - } - if (openedFile) { - addFilesToSourceList( - extensions, - [ - { - fileName: openedFile.filePath, - shortName: openedFile.shortName, - }, - ], - values, - setValues, - !sourceList || sourceList.length == 0 ? openedFile.storageType : null, - setPreviewSource - ); - } - }, []); - - const supportsPreview = - !!findFileFormat(extensions, values.sourceStorageType) || values.sourceStorageType == 'archive'; - const previewFileName = - previewSource && values[`sourceFile_${previewSource}`] && values[`sourceFile_${previewSource}`].fileName; - - const handleChangePreviewSource = async () => { - if (previewSource && supportsPreview) { - const reader = await createPreviewReader(extensions, values, previewSource); - if (onChangePreview) onChangePreview(reader); - } else { - onChangePreview(null); - } - }; - - React.useEffect(() => { - handleChangePreviewSource(); - }, [previewSource, supportsPreview, previewFileName]); - - const oldValues = React.useRef({}); - React.useEffect(() => { - const changed = _.pickBy( - values, - (v, k) => k.startsWith(`source_${values.sourceStorageType}_`) && oldValues.current[k] != v - ); - if (!_.isEmpty(changed)) { - handleChangePreviewSource(); - } - oldValues.current = values; - }, [values]); - - return ( - - - - - - - - - - - <FontIcon icon="icon tables" /> Map source tables/files - - - } /> - ( - setFieldValue(`actionType_${row}`, e.target.value)} - /> - )} - /> - ( - setFieldValue(`targetName_${row}`, e.target.value)} - /> - )} - /> - - supportsPreview ? ( - { - if (e.target.checked) setPreviewSource(row); - else setPreviewSource(null); - }} - /> - ) : null - } - /> - - {(sourceList || []).length == 0 && } - - - ); -} diff --git a/packages/web/src/impexp/PreviewDataGrid.js b/packages/web/src/impexp/PreviewDataGrid.js deleted file mode 100644 index 301348093..000000000 --- a/packages/web/src/impexp/PreviewDataGrid.js +++ /dev/null @@ -1,60 +0,0 @@ -import { createGridCache, createGridConfig, FreeTableGridDisplay } from 'dbgate-datalib'; -import React from 'react'; -import DataGridCore from '../datagrid/DataGridCore'; -import RowsArrayGrider from '../datagrid/RowsArrayGrider'; -import axios from '../utility/axios'; -import ErrorInfo from '../widgets/ErrorInfo'; -import LoadingInfo from '../widgets/LoadingInfo'; - -export default function PreviewDataGrid({ reader, ...other }) { - const [isLoading, setIsLoading] = React.useState(false); - const [errorMessage, setErrorMessage] = React.useState(null); - const [model, setModel] = React.useState(null); - const [config, setConfig] = React.useState(createGridConfig()); - const [cache, setCache] = React.useState(createGridCache()); - const [grider, setGrider] = React.useState(null); - - const handleLoadInitialData = async () => { - try { - if (!reader) { - setModel(null); - setGrider(null); - return; - } - setErrorMessage(null); - setIsLoading(true); - const resp = await axios.post('runners/load-reader', reader); - // @ts-ignore - setModel(resp.data); - setGrider(new RowsArrayGrider(resp.data.rows)); - setIsLoading(false); - } catch (err) { - setIsLoading(false); - const errorMessage = (err && err.response && err.response.data && err.response.data.error) || 'Loading failed'; - setErrorMessage(errorMessage); - console.error(err.response); - } - }; - - React.useEffect(() => { - handleLoadInitialData(); - }, [reader]); - - const display = React.useMemo(() => new FreeTableGridDisplay(model, config, setConfig, cache, setCache), [ - model, - config, - cache, - grider, - ]); - - if (isLoading) { - return ; - } - if (errorMessage) { - return ; - } - - if (!grider) return null; - - return ; -} diff --git a/packages/web/src/impexp/ScriptWriter.js b/packages/web/src/impexp/ScriptWriter.js deleted file mode 100644 index faf33f909..000000000 --- a/packages/web/src/impexp/ScriptWriter.js +++ /dev/null @@ -1,49 +0,0 @@ -import _ from 'lodash'; -import { extractShellApiFunctionName, extractShellApiPlugins } from 'dbgate-tools'; - -export default class ScriptWriter { - constructor(varCount = '0') { - this.s = ''; - this.packageNames = []; - // this.engines = []; - this.varCount = parseInt(varCount) || 0; - } - - allocVariable(prefix = 'var') { - this.varCount += 1; - return `${prefix}${this.varCount}`; - } - - put(s = '') { - this.s += s; - this.s += '\n'; - } - - assign(variableName, functionName, props) { - this.put(`const ${variableName} = await ${extractShellApiFunctionName(functionName)}(${JSON.stringify(props)});`); - this.packageNames.push(...extractShellApiPlugins(functionName, props)); - } - - requirePackage(packageName) { - this.packageNames.push(packageName); - } - - copyStream(sourceVar, targetVar) { - this.put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar});`); - } - - comment(s) { - this.put(`// ${s}`); - } - - getScript(schedule = null) { - const packageNames = this.packageNames; - let prefix = _.uniq(packageNames) - .map(packageName => `// @require ${packageName}\n`) - .join(''); - if (schedule) prefix += `// @schedule ${schedule}`; - if (prefix) prefix += '\n'; - - return prefix + this.s; - } -} diff --git a/packages/web/src/impexp/createImpExpScript.js b/packages/web/src/impexp/createImpExpScript.js deleted file mode 100644 index b1ff82614..000000000 --- a/packages/web/src/impexp/createImpExpScript.js +++ /dev/null @@ -1,240 +0,0 @@ -import _ from 'lodash'; -import ScriptWriter from './ScriptWriter'; -import getAsArray from '../utility/getAsArray'; -import { getConnectionInfo } from '../utility/metadataLoaders'; -import { findEngineDriver, findObjectLike } from 'dbgate-tools'; -import { findFileFormat } from '../utility/fileformats'; - -export function getTargetName(extensions, source, values) { - const key = `targetName_${source}`; - if (values[key]) return values[key]; - const format = findFileFormat(extensions, values.targetStorageType); - if (format) { - const res = format.getDefaultOutputName ? format.getDefaultOutputName(source, values) : null; - if (res) return res; - return `${source}.${format.extension}`; - } - return source; -} - -function extractApiParameters(values, direction, format) { - const pairs = (format.args || []) - .filter(arg => arg.apiName) - .map(arg => [arg.apiName, values[`${direction}_${format.storageType}_${arg.name}`]]) - .filter(x => x[1] != null); - return _.fromPairs(pairs); -} - -async function getConnection(extensions, storageType, conid, database) { - if (storageType == 'database' || storageType == 'query') { - const conn = await getConnectionInfo({ conid }); - const driver = findEngineDriver(conn, extensions); - return [ - { - ..._.omit(conn, ['_id', 'displayName']), - database, - }, - driver, - ]; - } - return [null, null]; -} - -function getSourceExpr(extensions, sourceName, values, sourceConnection, sourceDriver) { - const { sourceStorageType } = values; - if (sourceStorageType == 'database') { - const fullName = { schemaName: values.sourceSchemaName, pureName: sourceName }; - return [ - 'tableReader', - { - connection: sourceConnection, - ...fullName, - }, - ]; - } - if (sourceStorageType == 'query') { - return [ - 'queryReader', - { - connection: sourceConnection, - sql: values.sourceSql, - }, - ]; - } - if (findFileFormat(extensions, sourceStorageType)) { - const sourceFile = values[`sourceFile_${sourceName}`]; - const format = findFileFormat(extensions, sourceStorageType); - if (format && format.readerFunc) { - return [ - format.readerFunc, - { - ..._.omit(sourceFile, ['isDownload']), - ...extractApiParameters(values, 'source', format), - }, - ]; - } - } - if (sourceStorageType == 'jsldata') { - return ['jslDataReader', { jslid: values.sourceJslId }]; - } - if (sourceStorageType == 'archive') { - return [ - 'archiveReader', - { - folderName: values.sourceArchiveFolder, - fileName: sourceName, - }, - ]; - } - throw new Error(`Unknown source storage type: ${sourceStorageType}`); -} - -function getFlagsFroAction(action) { - switch (action) { - case 'dropCreateTable': - return { - createIfNotExists: true, - dropIfExists: true, - }; - case 'truncate': - return { - createIfNotExists: true, - truncate: true, - }; - } - - return { - createIfNotExists: true, - }; -} - -function getTargetExpr(extensions, sourceName, values, targetConnection, targetDriver) { - const { targetStorageType } = values; - const format = findFileFormat(extensions, targetStorageType); - if (format && format.writerFunc) { - const outputParams = format.getOutputParams && format.getOutputParams(sourceName, values); - return [ - format.writerFunc, - { - ...(outputParams - ? outputParams - : { - fileName: getTargetName(extensions, sourceName, values), - }), - ...extractApiParameters(values, 'target', format), - }, - ]; - } - if (targetStorageType == 'database') { - return [ - 'tableWriter', - { - connection: targetConnection, - schemaName: values.targetSchemaName, - pureName: getTargetName(extensions, sourceName, values), - ...getFlagsFroAction(values[`actionType_${sourceName}`]), - }, - ]; - } - if (targetStorageType == 'archive') { - return [ - 'archiveWriter', - { - folderName: values.targetArchiveFolder, - fileName: getTargetName(extensions, sourceName, values), - }, - ]; - } - - throw new Error(`Unknown target storage type: ${targetStorageType}`); -} - -export default async function createImpExpScript(extensions, values, addEditorInfo = true) { - const script = new ScriptWriter(values.startVariableIndex || 0); - - const [sourceConnection, sourceDriver] = await getConnection( - extensions, - values.sourceStorageType, - values.sourceConnectionId, - values.sourceDatabaseName - ); - const [targetConnection, targetDriver] = await getConnection( - extensions, - values.targetStorageType, - values.targetConnectionId, - values.targetDatabaseName - ); - - const sourceList = getAsArray(values.sourceList); - for (const sourceName of sourceList) { - const sourceVar = script.allocVariable(); - // @ts-ignore - script.assign(sourceVar, ...getSourceExpr(extensions, sourceName, values, sourceConnection, sourceDriver)); - - const targetVar = script.allocVariable(); - // @ts-ignore - script.assign(targetVar, ...getTargetExpr(extensions, sourceName, values, targetConnection, targetDriver)); - - script.copyStream(sourceVar, targetVar); - script.put(); - } - if (addEditorInfo) { - script.comment('@ImportExportConfigurator'); - script.comment(JSON.stringify(values)); - } - return script.getScript(values.schedule); -} - -export function getActionOptions(extensions, source, values, targetDbinfo) { - const res = []; - const targetName = getTargetName(extensions, source, values); - if (values.targetStorageType == 'database') { - let existing = findObjectLike( - { schemaName: values.targetSchemaName, pureName: targetName }, - targetDbinfo, - 'tables' - ); - if (existing) { - res.push({ - label: 'Append data', - value: 'appendData', - }); - res.push({ - label: 'Truncate and import', - value: 'truncate', - }); - res.push({ - label: 'Drop and create table', - value: 'dropCreateTable', - }); - } else { - res.push({ - label: 'Create table', - value: 'createTable', - }); - } - } else { - res.push({ - label: 'Create file', - value: 'createFile', - }); - } - return res; -} - -export async function createPreviewReader(extensions, values, sourceName) { - const [sourceConnection, sourceDriver] = await getConnection( - extensions, - values.sourceStorageType, - values.sourceConnectionId, - values.sourceDatabaseName - ); - const [functionName, props] = getSourceExpr(extensions, sourceName, values, sourceConnection, sourceDriver); - return { - functionName, - props: { - ...props, - limitRows: 100, - }, - }; -} diff --git a/packages/web/src/index.css b/packages/web/src/index.css deleted file mode 100644 index 18ce2e20b..000000000 --- a/packages/web/src/index.css +++ /dev/null @@ -1,52 +0,0 @@ -body { - font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, HelveticaNeue-Light, Ubuntu, Droid Sans, - sans-serif; - font-size: 14px; - /* font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; -} - -.RactModalOverlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #00000080; -} - -.icon-invisible { - visibility: hidden; -} - -.largeFormMarker input[type='text'] { - width: 100%; - padding: 10px 10px; - font-size: 14px; - box-sizing: border-box; - border-radius: 4px; -} - -.largeFormMarker input[type='password'] { - width: 100%; - padding: 10px 10px; - font-size: 14px; - box-sizing: border-box; - border-radius: 4px; -} - -.largeFormMarker select { - width: 100%; - padding: 10px 10px; - font-size: 14px; - box-sizing: border-box; - border-radius: 4px; -} diff --git a/packages/web/src/index.js b/packages/web/src/index.js deleted file mode 100644 index 6cb89510d..000000000 --- a/packages/web/src/index.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import _ from 'lodash'; -import './index.css'; -import '@mdi/font/css/materialdesignicons.css'; -import App from './App'; -import * as serviceWorker from './serviceWorker'; - -import 'ace-builds/src-noconflict/mode-sql'; -import 'ace-builds/src-noconflict/mode-mysql'; -import 'ace-builds/src-noconflict/mode-pgsql'; -import 'ace-builds/src-noconflict/mode-sqlserver'; -import 'ace-builds/src-noconflict/mode-json'; -import 'ace-builds/src-noconflict/mode-javascript'; -import 'ace-builds/src-noconflict/mode-markdown'; -import 'ace-builds/src-noconflict/theme-github'; -import 'ace-builds/src-noconflict/theme-twilight'; -import 'ace-builds/src-noconflict/ext-searchbox'; -import 'ace-builds/src-noconflict/ext-language_tools'; -import localStorageGarbageCollector from './utility/localStorageGarbageCollector'; -// import 'ace-builds/src-noconflict/snippets/sqlserver'; -// import 'ace-builds/src-noconflict/snippets/pgsql'; -// import 'ace-builds/src-noconflict/snippets/mysql'; - -localStorageGarbageCollector(); -window['dbgate_tabExports'] = {}; -window['dbgate_getCurrentTabCommands'] = () => { - const tabid = window['dbgate_activeTabId']; - return _.mapValues(window['dbgate_tabExports'][tabid] || {}, v => !!v); -}; -window['dbgate_tabCommand'] = cmd => { - const tabid = window['dbgate_activeTabId']; - const commands = window['dbgate_tabExports'][tabid]; - const func = (commands || {})[cmd]; - if (func) func(); -}; - -ReactDOM.render(, document.getElementById('root')); - -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); diff --git a/packages/web/src/markdown/MarkdownExtendedView.js b/packages/web/src/markdown/MarkdownExtendedView.js deleted file mode 100644 index 12190d12b..000000000 --- a/packages/web/src/markdown/MarkdownExtendedView.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import Markdown from 'markdown-to-jsx'; -import styled from 'styled-components'; -import OpenChartLink from './OpenChartLink'; -import MarkdownLink from './MarkdownLink'; -import OpenSqlLink from './OpenSqlLink'; - -const Wrapper = styled.div` - padding: 10px; - overflow: auto; - flex: 1; -`; - -export default function MarkdownExtendedView({ children }) { - return ( - - - {children || ''} - - - ); -} diff --git a/packages/web/src/markdown/MarkdownLink.js b/packages/web/src/markdown/MarkdownLink.js deleted file mode 100644 index d201cf64e..000000000 --- a/packages/web/src/markdown/MarkdownLink.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import useTheme from '../theme/useTheme'; -import { StyledThemedLink } from '../widgets/FormStyledButton'; - -export default function MarkdownLink({ href, title, children }) { - const theme = useTheme(); - - return ( - - {children} - - ); -} diff --git a/packages/web/src/markdown/MarkdownToolbar.js b/packages/web/src/markdown/MarkdownToolbar.js deleted file mode 100644 index 7c87dab63..000000000 --- a/packages/web/src/markdown/MarkdownToolbar.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import ToolbarButton from '../widgets/ToolbarButton'; - -export default function MarkdownToolbar({ showPreview }) { - return ( - <> - - Preview - - - ); -} diff --git a/packages/web/src/markdown/OpenChartLink.js b/packages/web/src/markdown/OpenChartLink.js deleted file mode 100644 index 481bb446e..000000000 --- a/packages/web/src/markdown/OpenChartLink.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import { useCurrentDatabase } from '../utility/globalState'; -import axios from '../utility/axios'; -import useTheme from '../theme/useTheme'; -import { StyledThemedLink } from '../widgets/FormStyledButton'; -import useOpenNewTab from '../utility/useOpenNewTab'; - -export default function OpenChartLink({ file, children }) { - const openNewTab = useOpenNewTab(); - const currentDb = useCurrentDatabase(); - const theme = useTheme(); - - const handleClick = async () => { - const resp = await axios.post('files/load', { folder: 'charts', file, format: 'json' }); - openNewTab( - { - title: file, - icon: 'img chart', - tabComponent: 'ChartTab', - props: { - conid: currentDb && currentDb.connection && currentDb.connection._id, - database: currentDb && currentDb.name, - savedFile: file, - }, - }, - { editor: resp.data } - ); - }; - - return ( - - {children} - - ); -} diff --git a/packages/web/src/markdown/OpenSqlLink.js b/packages/web/src/markdown/OpenSqlLink.js deleted file mode 100644 index cce857f89..000000000 --- a/packages/web/src/markdown/OpenSqlLink.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import axios from '../utility/axios'; -import useTheme from '../theme/useTheme'; -import { StyledThemedLink } from '../widgets/FormStyledButton'; -import useNewQuery from '../query/useNewQuery'; - -export default function OpenSqlLink({ file, children }) { - const newQuery = useNewQuery(); - const theme = useTheme(); - - const handleClick = async () => { - const resp = await axios.post('files/load', { folder: 'sql', file, format: 'text' }); - newQuery({ - title: file, - initialData: resp.data, - // @ts-ignore - savedFile: file, - }); - }; - - return ( - - {children} - - ); -} diff --git a/packages/web/src/modals/AboutModal.js b/packages/web/src/modals/AboutModal.js deleted file mode 100644 index 152e346be..000000000 --- a/packages/web/src/modals/AboutModal.js +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; -import ModalBase from './ModalBase'; -import ModalHeader from './ModalHeader'; -import ModalContent from './ModalContent'; -import ModalFooter from './ModalFooter'; -import { useConfig } from '../utility/metadataLoaders'; -import FormStyledButton from '../widgets/FormStyledButton'; -import moment from 'moment'; -import styled from 'styled-components'; -import getElectron from '../utility/getElectron'; -import useTheme from '../theme/useTheme'; -import { StyledThemedLink } from '../widgets/FormStyledButton'; - -const Container = styled.div` - display: flex; -`; - -const TextContainer = styled.div``; - -const StyledLine = styled.div` - margin: 5px; -`; - -const StyledValue = styled.span` - font-weight: bold; -`; - -function Line({ label, children }) { - return ( - - {label}: {children} - - ); -} - -function Link({ label, children, href }) { - const electron = getElectron(); - const theme = useTheme(); - return ( - - {label}:{' '} - {electron ? ( - electron.shell.openExternal(href)}> - {children} - - ) : ( - - {children} - - )} - - ); -} - -export default function AboutModal({ modalState }) { - const config = useConfig(); - const { version, buildTime } = config || {}; - return ( - - About DbGate - - - - - {version} - {moment(buildTime).format('YYYY-MM-DD')} - - dbgate.org - - - github - - - docker hub - - - demo.dbgate.org - - - npmjs.com - - - - - - modalState.close()} /> - - - ); -} diff --git a/packages/web/src/modals/ChangeDownloadUrlModal.js b/packages/web/src/modals/ChangeDownloadUrlModal.js deleted file mode 100644 index ce0b08e70..000000000 --- a/packages/web/src/modals/ChangeDownloadUrlModal.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import ModalBase from './ModalBase'; -import { FormButton, FormSubmit, FormTextField } from '../utility/forms'; -import ModalHeader from './ModalHeader'; -import ModalContent from './ModalContent'; -import ModalFooter from './ModalFooter'; -import FormStyledButton from '../widgets/FormStyledButton'; -import { FormProvider } from '../utility/FormProvider'; - -export default function ChangeDownloadUrlModal({ modalState, url = '', onConfirm = undefined }) { - // const textFieldRef = React.useRef(null); - // React.useEffect(() => { - // if (textFieldRef.current) textFieldRef.current.focus(); - // }, [textFieldRef.current]); - - // const handleSubmit = () => async (values) => { - // onConfirm(values.url); - // modalState.close(); - // }; - - const handleSubmit = React.useCallback( - async values => { - onConfirm(values.url); - modalState.close(); - }, - [modalState, onConfirm] - ); - return ( - - Download imported file from web - - - - - - - modalState.close()} /> - - - - ); -} diff --git a/packages/web/src/modals/ConfirmModal.js b/packages/web/src/modals/ConfirmModal.js deleted file mode 100644 index c6e9db5c6..000000000 --- a/packages/web/src/modals/ConfirmModal.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import ModalBase from './ModalBase'; -import FormStyledButton from '../widgets/FormStyledButton'; -import ModalFooter from './ModalFooter'; -import ModalContent from './ModalContent'; -import { FormSubmit } from '../utility/forms'; -import { FormProvider } from '../utility/FormProvider'; - -export default function ConfirmModal({ message, modalState, onConfirm }) { - return ( - - - {message} - - - { - modalState.close(); - onConfirm(); - }} - /> - - - - - ); -} diff --git a/packages/web/src/modals/ConfirmSqlModal.js b/packages/web/src/modals/ConfirmSqlModal.js deleted file mode 100644 index 0b66bb278..000000000 --- a/packages/web/src/modals/ConfirmSqlModal.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import ModalBase from './ModalBase'; -import FormStyledButton from '../widgets/FormStyledButton'; -import SqlEditor from '../sqleditor/SqlEditor'; -import styled from 'styled-components'; -import keycodes from '../utility/keycodes'; -import ModalHeader from './ModalHeader'; -import ModalContent from './ModalContent'; -import ModalFooter from './ModalFooter'; - -const SqlWrapper = styled.div` - position: relative; - height: 30vh; - width: 40vw; -`; - -export default function ConfirmSqlModal({ modalState, sql, engine, onConfirm }) { - const handleKeyDown = (data, hash, keyString, keyCode, event) => { - if (keyCode == keycodes.enter) { - event.preventDefault(); - modalState.close(); - onConfirm(); - } - }; - return ( - - Save changes - - - - - - - - { - modalState.close(); - onConfirm(); - }} - /> - - - - ); -} diff --git a/packages/web/src/modals/ConnectionModal.js b/packages/web/src/modals/ConnectionModal.js deleted file mode 100644 index a1b1bcfc9..000000000 --- a/packages/web/src/modals/ConnectionModal.js +++ /dev/null @@ -1,349 +0,0 @@ -import React from 'react'; -import axios from '../utility/axios'; -import ModalBase from './ModalBase'; -import { - FormButton, - FormTextField, - FormSelectField, - FormSubmit, - FormPasswordField, - FormCheckboxField, - FormElectronFileSelector, -} from '../utility/forms'; -import ModalHeader from './ModalHeader'; -import ModalFooter from './ModalFooter'; -import ModalContent from './ModalContent'; -import useExtensions from '../utility/useExtensions'; -import LoadingInfo from '../widgets/LoadingInfo'; -import { FontIcon } from '../icons'; -import { FormProvider, useForm } from '../utility/FormProvider'; -import { TabControl, TabPage } from '../widgets/TabControl'; -import { usePlatformInfo } from '../utility/metadataLoaders'; -import getElectron from '../utility/getElectron'; -import { FormFieldTemplateLarge, FormRowLarge } from '../utility/formStyle'; -import styled from 'styled-components'; -import { FlexCol3, FlexCol6, FlexCol9 } from '../utility/flexGrid'; -// import FormikForm from '../utility/FormikForm'; - -const FlexContainer = styled.div` - display: flex; -`; - -const TestResultContainer = styled.div` - margin-left: 10px; - align-self: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -const ButtonsContainer = styled.div` - flex-shrink: 0; -`; - -const AgentInfoWrap = styled.div` - margin-left: 20px; - margin-bottom: 20px; -`; - -function DriverFields({ extensions }) { - const { values, setFieldValue } = useForm(); - const { authType, engine } = values; - const driver = extensions.drivers.find(x => x.engine == engine); - // const { authTypes } = driver || {}; - const [authTypes, setAuthTypes] = React.useState(null); - const currentAuthType = authTypes && authTypes.find(x => x.name == authType); - - const loadAuthTypes = async () => { - const resp = await axios.post('plugins/auth-types', { engine }); - setAuthTypes(resp.data); - if (resp.data && !currentAuthType) { - setFieldValue('authType', resp.data[0].name); - } - }; - - React.useEffect(() => { - setAuthTypes(null); - loadAuthTypes(); - }, [values.engine]); - - if (!driver) return null; - const disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || []; - - return ( - <> - {!!authTypes && ( - - {authTypes.map(auth => ( - - ))} - - )} - - - - - - - - - - - - - - - - - - {!disabledFields.includes('password') && ( - - - - - )} - - ); -} - -function SshTunnelFields() { - const { values, setFieldValue } = useForm(); - const { useSshTunnel, sshMode, sshPort, sshKeyfile } = values; - const platformInfo = usePlatformInfo(); - const electron = getElectron(); - - React.useEffect(() => { - if (useSshTunnel && !sshMode) { - setFieldValue('sshMode', 'userPassword'); - } - if (useSshTunnel && !sshPort) { - setFieldValue('sshPort', '22'); - } - if (useSshTunnel && sshMode == 'keyFile' && !sshKeyfile) { - setFieldValue('sshKeyfile', platformInfo.defaultKeyFile); - } - }, [useSshTunnel, sshMode]); - - return ( - <> - - - - - - - - - - - - - - - {!!electron && } - - - {sshMode != 'userPassword' && } - - {sshMode == 'userPassword' && ( - - - - - - - - - )} - - {sshMode == 'keyFile' && ( - - - - - - - - - )} - - {useSshTunnel && sshMode == 'agent' && ( - - {platformInfo.sshAuthSock ? ( -
- SSH Agent found -
- ) : ( -
- SSH Agent not found -
- )} -
- )} - - ); -} - -function SslFields() { - const { values } = useForm(); - const { useSsl } = values; - const electron = getElectron(); - - return ( - <> - - - - - - - ); -} - -export default function ConnectionModal({ modalState, connection = undefined }) { - const [sqlConnectResult, setSqlConnectResult] = React.useState(null); - const extensions = useExtensions(); - const [isTesting, setIsTesting] = React.useState(false); - const testIdRef = React.useRef(0); - - const handleTest = async values => { - setIsTesting(true); - testIdRef.current += 1; - const testid = testIdRef.current; - const resp = await axios.post('connections/test', values); - if (testIdRef.current != testid) return; - - setIsTesting(false); - setSqlConnectResult(resp.data); - }; - - const handleCancel = async () => { - testIdRef.current += 1; // invalidate current test - setIsTesting(false); - }; - - const handleSubmit = async values => { - axios.post('connections/save', values); - modalState.close(); - }; - return ( - - {connection ? 'Edit connection' : 'Add connection'} - - - - - - - {extensions.drivers.map(driver => ( - - ))} - {/* - - */} - - - - - - - - - - - - - - - - - {isTesting ? ( - - ) : ( - - )} - - - - - - {!isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'connected' && ( -
- Connected: {sqlConnectResult.version} -
- )} - {!isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'error' && ( -
- Connect failed: {sqlConnectResult.error} -
- )} - {isTesting && ( -
- Testing connection -
- )} -
-
-
-
-
- ); -} diff --git a/packages/web/src/modals/CreateDatabaseModal.js b/packages/web/src/modals/CreateDatabaseModal.js deleted file mode 100644 index bb7aed9cf..000000000 --- a/packages/web/src/modals/CreateDatabaseModal.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import axios from '../utility/axios'; -import ModalBase from './ModalBase'; -import { FormButton, FormSubmit, FormTextField } from '../utility/forms'; -import ModalHeader from './ModalHeader'; -import ModalContent from './ModalContent'; -import ModalFooter from './ModalFooter'; -import { FormProvider } from '../utility/FormProvider'; - -export default function CreateDatabaseModal({ modalState, conid }) { - const handleSubmit = async values => { - const { name } = values; - axios.post('server-connections/create-database', { conid, name }); - - modalState.close(); - }; - return ( - - Create database - - - - - - - - - - ); -} diff --git a/packages/web/src/modals/DropDownMenu.js b/packages/web/src/modals/DropDownMenu.js deleted file mode 100644 index 6c71df6ea..000000000 --- a/packages/web/src/modals/DropDownMenu.js +++ /dev/null @@ -1,106 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { sleep } from '../utility/common'; -import useDocumentClick from '../utility/useDocumentClick'; -import { useHideMenu } from './showMenu'; - -const ContextMenuStyled = styled.ul` - position: absolute; - list-style: none; - background-color: #fff; - border-radius: 4px; - border: 1px solid rgba(0, 0, 0, 0.15); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - padding: 5px 0; - margin: 2px 0 0; - font-size: 14px; - text-align: left; - min-width: 160px; - z-index: 1050; - cursor: default; -`; - -const KeyTextSpan = styled.span` - font-style: italic; - font-weight: bold; - text-align: right; - margin-left: 16px; -`; - -const StyledLink = styled.a` - padding: 3px 20px; - line-height: 1.42; - display: block; - white-space: nop-wrap; - color: #262626; - - &:hover { - background-color: #f5f5f5; - text-decoration: none; - color: #262626; - } -`; - -export const DropDownMenuDivider = styled.li` - margin: 9px 0px 9px 0px; - border-top: 1px solid #f2f2f2; - border-bottom: 1px solid #fff; -`; - -export function DropDownMenuItem({ children, keyText = undefined, onClick }) { - const handleMouseEnter = () => { - // if (this.context.parentMenu) this.context.parentMenu.closeSubmenu(); - }; - - return ( -
  • - - {children} - {keyText && {keyText}} - -
  • - ); -} - -export function ContextMenu({ left, top, children }) { - const hideMenu = useHideMenu(); - useDocumentClick(async () => { - await sleep(0); - hideMenu(); - }); - const menuRef = React.useRef(null); - React.useEffect(() => { - if (menuRef.current) fixPopupPlacement(menuRef.current); - }, [menuRef.current]); - return ( - - {children} - - ); -} - -function getElementOffset(element) { - var de = document.documentElement; - var box = element.getBoundingClientRect(); - var top = box.top + window.pageYOffset - de.clientTop; - var left = box.left + window.pageXOffset - de.clientLeft; - return { top: top, left: left }; -} - -function fixPopupPlacement(element) { - const { width, height } = element.getBoundingClientRect(); - let offset = getElementOffset(element); - - let newLeft = null; - let newTop = null; - - if (offset.left + width > window.innerWidth) { - newLeft = offset.left - width; - } - if (offset.top + height > window.innerHeight) { - newTop = offset.top - height; - } - - if (newLeft != null) element.style.left = `${newLeft}px`; - if (newTop != null) element.style.top = `${newTop}px`; -} diff --git a/packages/web/src/modals/ErrorMessageModal.js b/packages/web/src/modals/ErrorMessageModal.js deleted file mode 100644 index fa60839d7..000000000 --- a/packages/web/src/modals/ErrorMessageModal.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import ModalBase from './ModalBase'; -import FormStyledButton from '../widgets/FormStyledButton'; -import styled from 'styled-components'; -import ModalHeader from './ModalHeader'; -import ModalFooter from './ModalFooter'; -import ModalContent from './ModalContent'; -import { FontIcon } from '../icons'; - -const Wrapper = styled.div` -display:flex -align-items:center -`; - -const IconWrapper = styled.div` - margin-right: 10px; - font-size: 20pt; -`; - -export default function ErrorMessageModal({ modalState, title = 'Error', message }) { - return ( - - {title} - - - - - - {message} - - - - - - - ); -} diff --git a/packages/web/src/modals/FavoriteModal.js b/packages/web/src/modals/FavoriteModal.js deleted file mode 100644 index 6594e7134..000000000 --- a/packages/web/src/modals/FavoriteModal.js +++ /dev/null @@ -1,162 +0,0 @@ -import React from 'react'; -import ModalBase from './ModalBase'; -import { - FormTextField, - FormSubmit, - FormButton, - FormCheckboxField, - FormFieldTemplate, - FormCondition, - FormSelectField, -} from '../utility/forms'; -import ModalHeader from './ModalHeader'; -import ModalContent from './ModalContent'; -import ModalFooter from './ModalFooter'; -import { FormProvider, useForm } from '../utility/FormProvider'; -import axios from '../utility/axios'; -import uuidv1 from 'uuid/v1'; -import { FontIcon } from '../icons'; -import useHasPermission from '../utility/useHasPermission'; -import _ from 'lodash'; -import getElectron from '../utility/getElectron'; -import { copyTextToClipboard } from '../utility/clipboard'; -import localforage from 'localforage'; - -function FontIconPreview() { - const { values } = useForm(); - return ; -} - -export default function FavoriteModal({ modalState, editingData = undefined, savingTab = undefined }) { - const hasPermission = useHasPermission(); - const electron = getElectron(); - const savedProperties = ['title', 'icon', 'showInToolbar', 'openOnStartup', 'urlPath']; - const initialValues = React.useMemo(() => { - if (savingTab) { - return { - title: savingTab.title, - icon: savingTab.icon, - urlPath: _.kebabCase(_.deburr(savingTab.title)), - }; - } - if (editingData) { - return _.pick(editingData, savedProperties); - } - }, []); - - const savedFile = savingTab && savingTab.props && savingTab.props.savedFile; - - const canWriteFavorite = hasPermission('files/favorites/write'); - - const getTabSaveData = async values => { - const tabdata = {}; - const skipEditor = !!savedFile && values.whatToSave != 'content'; - - const re = new RegExp(`tabdata_(.*)_${savingTab.tabid}`); - for (const key of await localforage.keys()) { - const match = key.match(re); - if (!match) continue; - if (skipEditor && match[1] == 'editor') continue; - tabdata[match[1]] = await localforage.getItem(key); - } - for (const key in localStorage) { - const match = key.match(re); - if (!match) continue; - if (skipEditor && match[1] == 'editor') continue; - tabdata[match[1]] = JSON.parse(localStorage.getItem(key)); - } - console.log('tabdata', tabdata, skipEditor, savingTab.tabid); - - return { - props: - values.whatToSave == 'content' && savingTab.props - ? _.omit(savingTab.props, ['savedFile', 'savedFormat', 'savedFolder']) - : savingTab.props, - tabComponent: savingTab.tabComponent, - tabdata, - ..._.pick(values, savedProperties), - }; - }; - - const saveTab = async values => { - const data = await getTabSaveData(values); - - axios.post('files/save', { - folder: 'favorites', - file: uuidv1(), - format: 'json', - data, - }); - }; - - const saveFile = async values => { - const oldDataResp = await axios.post('files/load', { - folder: 'favorites', - file: editingData.file, - format: 'json', - }); - - axios.post('files/save', { - folder: 'favorites', - file: editingData.file, - format: 'json', - data: { - ...oldDataResp.data, - ...values, - }, - }); - }; - - const handleSubmit = async values => { - modalState.close(); - if (savingTab) { - saveTab(values); - } - if (editingData) { - saveFile(values); - } - }; - - const handleCopyLink = async values => { - const tabdata = await getTabSaveData(values); - copyTextToClipboard(`${document.location.origin}#tabdata=${encodeURIComponent(JSON.stringify(tabdata))}`); - }; - - return ( - - {editingData ? 'Edit favorite' : 'Share / add to favorites'} - - - - - - - - - {!!savingTab && !electron && canWriteFavorite && ( - - )} - !values.shareAsLink && canWriteFavorite}> - - - - {!!savingTab && !!savedFile && ( - - - - - )} - - - !values.shareAsLink && canWriteFavorite}> - - - values.shareAsLink || !canWriteFavorite}> - - - modalState.close()} /> - - - - ); -} diff --git a/packages/web/src/modals/FilterMultipleValuesModal.js b/packages/web/src/modals/FilterMultipleValuesModal.js deleted file mode 100644 index bc41a69c9..000000000 --- a/packages/web/src/modals/FilterMultipleValuesModal.js +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import ModalBase from './ModalBase'; -import FormStyledButton from '../widgets/FormStyledButton'; -import styled from 'styled-components'; -import ModalHeader from './ModalHeader'; -import ModalFooter from './ModalFooter'; -import ModalContent from './ModalContent'; - -const Wrapper = styled.div` - display: flex; -`; - -const OptionsWrapper = styled.div` - margin-left: 10px; -`; - -function RadioGroupItem({ text, value, defaultChecked = undefined, setMode }) { - return ( -
    - setMode(value)} - /> - -
    - ); -} - -export default function FilterMultipleValuesModal({ modalState, onFilter }) { - const editorRef = React.useRef(null); - const [text, setText] = React.useState(''); - const [mode, setMode] = React.useState('is'); - React.useEffect(() => { - setTimeout(() => { - if (editorRef.current) editorRef.current.focus(); - }, 1); - }, []); - - const handleOk = () => { - onFilter(mode, text); - modalState.close(); - }; - - return ( - - Filter multiple values - - -