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 dd0ba6487..000000000 Binary files a/packages/web/public/favicon.ico and /dev/null differ 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 56d4efb81..000000000 Binary files a/packages/web/public/logo192.png and /dev/null differ diff --git a/packages/web/public/logo512.png b/packages/web/public/logo512.png deleted file mode 100644 index 44ece81d3..000000000 Binary files a/packages/web/public/logo512.png and /dev/null differ diff --git a/packages/web/public/manifest.json b/packages/web/public/manifest.json deleted file mode 100644 index 63c397bd1..000000000 --- a/packages/web/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "DbGate", - "name": "DbGate database tool", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/packages/web/public/robots.txt b/packages/web/public/robots.txt deleted file mode 100644 index 01b0f9a10..000000000 --- a/packages/web/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * diff --git a/packages/web/public/splash.css b/packages/web/public/splash.css deleted file mode 100644 index 3bc0c720f..000000000 --- a/packages/web/public/splash.css +++ /dev/null @@ -1,16 +0,0 @@ -body { - background: #666; -} - -div { - color: white; - font-size: 25pt; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - text-align: center; - font-family: Arial, Helvetica, sans-serif; - margin-top: 40px; -} diff --git a/packages/web/public/splash.html b/packages/web/public/splash.html deleted file mode 100644 index 486dce245..000000000 --- a/packages/web/public/splash.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - -
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 - - -