mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-27 17:16:01 +00:00
context menu, editing connection
This commit is contained in:
@@ -14,7 +14,7 @@ module.exports = {
|
|||||||
const dir = await datadir();
|
const dir = await datadir();
|
||||||
this.datastore = nedb.create(path.join(dir, 'connections.jsonl'));
|
this.datastore = nedb.create(path.join(dir, 'connections.jsonl'));
|
||||||
},
|
},
|
||||||
|
|
||||||
list_meta: 'get',
|
list_meta: 'get',
|
||||||
async list() {
|
async list() {
|
||||||
return this.datastore.find();
|
return this.datastore.find();
|
||||||
@@ -32,7 +32,15 @@ module.exports = {
|
|||||||
|
|
||||||
save_meta: 'post',
|
save_meta: 'post',
|
||||||
async save(connection) {
|
async save(connection) {
|
||||||
const res = await this.datastore.insert(connection);
|
if (connection._id) {
|
||||||
return res;
|
return await this.datastore.update(_.pick(connection, '_id'), connection);
|
||||||
|
} else {
|
||||||
|
return await this.datastore.insert(connection);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
delete_meta: 'post',
|
||||||
|
async delete(connection) {
|
||||||
|
return await this.datastore.remove(_.pick(connection, '_id'));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
41
web/src/appobj/AppObjects.js
Normal file
41
web/src/appobj/AppObjects.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { showMenu } from '../modals/DropDownMenu';
|
||||||
|
|
||||||
|
const AppObjectDiv = styled.div`
|
||||||
|
margin: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const IconWrap = styled.span`
|
||||||
|
margin-right: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function AppObjectCore({ title, Icon, Menu, data, makeAppObj }) {
|
||||||
|
const handleContextMenu = event => {
|
||||||
|
if (!Menu) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
showMenu(event.pageX, event.pageY, <Menu data={data} makeAppObj={makeAppObj} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppObjectDiv onContextMenu={handleContextMenu}>
|
||||||
|
<IconWrap>
|
||||||
|
<Icon />
|
||||||
|
</IconWrap>
|
||||||
|
{title}
|
||||||
|
</AppObjectDiv>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppObjectControl({ data, makeAppObj }) {
|
||||||
|
const appobj = makeAppObj(data);
|
||||||
|
return <AppObjectCore {...appobj} data={data} makeAppObj={makeAppObj} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppObjectList({ list, makeAppObj }) {
|
||||||
|
return (list || []).map(x => {
|
||||||
|
const appobj = makeAppObj(x);
|
||||||
|
return <AppObjectCore key={appobj.key} {...appobj} data={x} makeAppObj={makeAppObj} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
43
web/src/appobj/connectionAppObject.js
Normal file
43
web/src/appobj/connectionAppObject.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MicrosoftIcon, SqliteIcon, PostgreSqlIcon, MySqlIcon, ServerIcon } from '../icons';
|
||||||
|
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||||
|
import showModal from '../modals/showModal';
|
||||||
|
import ConnectionModal from '../modals/ConnectionModal';
|
||||||
|
import axios from '../utility/axios';
|
||||||
|
|
||||||
|
function getIcon(engine) {
|
||||||
|
switch (engine) {
|
||||||
|
case 'mssql':
|
||||||
|
return MicrosoftIcon;
|
||||||
|
case 'sqlite':
|
||||||
|
return SqliteIcon;
|
||||||
|
case 'postgres':
|
||||||
|
return PostgreSqlIcon;
|
||||||
|
case 'mysql':
|
||||||
|
return MySqlIcon;
|
||||||
|
}
|
||||||
|
return ServerIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Menu({ data, makeAppObj }) {
|
||||||
|
const handleEdit = () => {
|
||||||
|
showModal(modalState => <ConnectionModal modalState={modalState} connection={data} />);
|
||||||
|
};
|
||||||
|
const handleDelete = () => {
|
||||||
|
axios.post('connections/delete', data);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropDownMenuItem onClick={handleEdit}>Edit</DropDownMenuItem>
|
||||||
|
<DropDownMenuItem onClick={handleDelete}>Delete</DropDownMenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function connectionAppObject({ _id, server, displayName, engine }) {
|
||||||
|
const title = displayName || server;
|
||||||
|
const key = _id;
|
||||||
|
const Icon = getIcon(engine);
|
||||||
|
|
||||||
|
return { title, key, Icon, Menu };
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import axios from 'axios';
|
import axios from '../utility/axios';
|
||||||
import ModalBase from './ModalBase';
|
import ModalBase from './ModalBase';
|
||||||
import { FormRow, FormButton, FormTextField, FormSelectField, FormSubmit } from '../utility/forms';
|
import { FormRow, FormButton, FormTextField, FormSelectField, FormSubmit } from '../utility/forms';
|
||||||
import { TextField } from '../utility/inputs';
|
import { TextField } from '../utility/inputs';
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
// import FormikForm from '../utility/FormikForm';
|
// import FormikForm from '../utility/FormikForm';
|
||||||
|
|
||||||
export default function ConnectionModal({ modalState }) {
|
export default function ConnectionModal({ modalState, connection }) {
|
||||||
const [sqlConnectResult, setSqlConnectResult] = React.useState('Not connected');
|
const [sqlConnectResult, setSqlConnectResult] = React.useState('Not connected');
|
||||||
|
|
||||||
const handleTest = async values => {
|
const handleTest = async values => {
|
||||||
const resp = await axios.post('http://localhost:3000/connections/test', values);
|
const resp = await axios.post('connections/test', values);
|
||||||
console.log('resp.data', resp.data);
|
console.log('resp.data', resp.data);
|
||||||
const { error, version } = resp.data;
|
const { error, version } = resp.data;
|
||||||
|
|
||||||
@@ -20,20 +20,19 @@ export default function ConnectionModal({ modalState }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async values => {
|
const handleSubmit = async values => {
|
||||||
const resp = await axios.post('http://localhost:3000/connections/save', values);
|
const resp = await axios.post('connections/save', values);
|
||||||
console.log('resp.data', resp.data);
|
|
||||||
|
|
||||||
// modalState.close();
|
// modalState.close();
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ModalBase modalState={modalState}>
|
<ModalBase modalState={modalState}>
|
||||||
<h2>Add connection</h2>
|
<h2>{connection ? 'Edit connection' : 'Add connection'}</h2>
|
||||||
<Formik onSubmit={handleSubmit} initialValues={{ server: 'localhost', engine: 'mssql' }}>
|
<Formik onSubmit={handleSubmit} initialValues={connection || { server: 'localhost', engine: 'mssql' }}>
|
||||||
<Form>
|
<Form>
|
||||||
<FormSelectField label="Database engine" name="engine">
|
<FormSelectField label="Database engine" name="engine">
|
||||||
<option value="mssql">Microsoft SQL Server</option>
|
<option value="mssql">Microsoft SQL Server</option>
|
||||||
<option value="mysql">MySQL</option>
|
<option value="mysql">MySQL</option>
|
||||||
<option value="postgre">Postgre SQL</option>
|
<option value="postgres">Postgre SQL</option>
|
||||||
</FormSelectField>
|
</FormSelectField>
|
||||||
<FormTextField label="Server" name="server" />
|
<FormTextField label="Server" name="server" />
|
||||||
<FormTextField label="Port" name="port" />
|
<FormTextField label="Port" name="port" />
|
||||||
|
|||||||
291
web/src/modals/DropDownMenu.js
Normal file
291
web/src/modals/DropDownMenu.js
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { LoadingToken, sleep } from '../utility/common';
|
||||||
|
|
||||||
|
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;
|
||||||
|
`;
|
||||||
|
|
||||||
|
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 function DropDownMenuItem({ children, keyText, onClick }) {
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
// if (this.context.parentMenu) this.context.parentMenu.closeSubmenu();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li onMouseEnter={handleMouseEnter}>
|
||||||
|
<StyledLink onClick={onClick}>
|
||||||
|
{children}
|
||||||
|
{keyText && <KeyTextSpan>{keyText}</KeyTextSpan>}
|
||||||
|
</StyledLink>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (DropDownMenuItem as any).contextTypes = {
|
||||||
|
// parentMenu: PropTypes.any
|
||||||
|
// };
|
||||||
|
|
||||||
|
// interface IDropDownMenuLinkProps {
|
||||||
|
// href: string;
|
||||||
|
// keyText?: string;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export class DropDownMenuLink extends React.Component<IDropDownMenuLinkProps> {
|
||||||
|
// render() {
|
||||||
|
// return <li onMouseEnter={this.handleMouseEnter.bind(this)}><Link forceSimpleLink href={this.props.href}>{this.props.children}{this.props.keyText && <span className='context_menu_key_text'>{this.props.keyText}</span>}</Link></li>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// handleMouseEnter() {
|
||||||
|
// if (this.context.parentMenu) this.context.parentMenu.closeSubmenu();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// (DropDownMenuLink as any).contextTypes = {
|
||||||
|
// parentMenu: PropTypes.any
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // export function DropDownMenu(props: { children?: any }) {
|
||||||
|
// // return <div className="btn-group">
|
||||||
|
// // <button type="button" className="btn btn-default dropdown-toggle btn-xs" data-toggle="dropdown"
|
||||||
|
// // aria-haspopup="true" aria-expanded="false" tabIndex={-1}>
|
||||||
|
// // <span className="caret"></span>
|
||||||
|
// // </button>
|
||||||
|
// // <ul className="dropdown-menu">
|
||||||
|
// // {props.children}
|
||||||
|
// // </ul>
|
||||||
|
// // </div>
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// export function DropDownMenuDivider(props: {}) {
|
||||||
|
// return <li className="dropdown-divider"></li>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export class DropDownSubmenuItem extends React.Component<IDropDownSubmenuItemProps> {
|
||||||
|
// menuInstance: ContextMenu;
|
||||||
|
// domObject: Element;
|
||||||
|
|
||||||
|
// render() {
|
||||||
|
// return <li onMouseEnter={this.handleMouseEnter.bind(this)} ref={x => this.domObject = x}><Link onClick={() => null}>{this.props.title} <IconSpan icon='fa-caret-right' /></Link></li>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// closeSubmenu() {
|
||||||
|
// if (this.menuInstance != null) {
|
||||||
|
// this.menuInstance.close();
|
||||||
|
// this.menuInstance = null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (this.context.parentMenu) this.context.parentMenu.submenu = null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// closeOtherSubmenu() {
|
||||||
|
// if (this.context.parentMenu) this.context.parentMenu.closeSubmenu();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// handleMouseEnter() {
|
||||||
|
// this.closeOtherSubmenu();
|
||||||
|
|
||||||
|
// let offset = $(this.domObject).offset();
|
||||||
|
// let width = $(this.domObject).width();
|
||||||
|
|
||||||
|
// this.menuInstance = showMenuCore(offset.left + width, offset.top, this);
|
||||||
|
// if (this.context.parentMenu) this.context.parentMenu.submenu = this;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// (DropDownSubmenuItem as any).contextTypes = {
|
||||||
|
// parentMenu: PropTypes.any
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export class DropDownMenu extends React.Component<IDropDownMenuProps, IDropDownMenuState> {
|
||||||
|
// domButton: Element;
|
||||||
|
|
||||||
|
// constructor(props) {
|
||||||
|
// super(props);
|
||||||
|
// this.state = {
|
||||||
|
// isExpanded: false,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// render() {
|
||||||
|
// let className = this.props.classOverride || ('btn btn-xs btn-default drop_down_menu_button ' + (this.props.className || ''));
|
||||||
|
// return <button id={this.props.buttonElementId} type="button" className={className} tabIndex={-1} onClick={this.menuButtonClick} ref={x => this.domButton = x}>
|
||||||
|
// { this.props.title }
|
||||||
|
// { this.props.iconSpan || <span className="caret"></span>}
|
||||||
|
// </button>
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @autobind
|
||||||
|
// menuButtonClick() {
|
||||||
|
// if (this.state.isExpanded) {
|
||||||
|
// hideMenu();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// let offset = $(this.domButton).offset();
|
||||||
|
// let height = $(this.domButton).height();
|
||||||
|
// this.setState({ isExpanded: true })
|
||||||
|
// showMenu(offset.left, offset.top + height + 5, this, () => this.setState({ isExpanded: false }));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
export function ContextMenu({ left, top, children }) {
|
||||||
|
return <ContextMenuStyled style={{ left: `${left}px`, top: `${top}px` }}>{children}</ContextMenuStyled>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export class ContextMenu extends React.Component<IContextMenuProps> {
|
||||||
|
// domObject: Element;
|
||||||
|
// submenu: DropDownSubmenuItem;
|
||||||
|
|
||||||
|
// render() {
|
||||||
|
// return <ul className='context_menu' style={{ left: `${this.props.left}px`, top: `${this.props.top}px` }} ref={x => this.domObject = x} onContextMenu={e => e.preventDefault()}>
|
||||||
|
// {this.props.children}
|
||||||
|
// </ul>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// componentDidMount() {
|
||||||
|
// fixPopupPlacement(this.domObject);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// getChildContext() {
|
||||||
|
// return { parentMenu: this };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// closeSubmenu() {
|
||||||
|
// if (this.submenu) {
|
||||||
|
// this.submenu.closeSubmenu();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// close() {
|
||||||
|
// this.props.container.remove();
|
||||||
|
// this.closeSubmenu();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// (ContextMenu as any).childContextTypes = {
|
||||||
|
// parentMenu: PropTypes.any
|
||||||
|
// };
|
||||||
|
|
||||||
|
let menuHandle = null;
|
||||||
|
let hideToken = null;
|
||||||
|
|
||||||
|
function showMenuCore(left, top, contentHolder, closeCallback = null) {
|
||||||
|
let container = document.createElement('div');
|
||||||
|
let handle = {
|
||||||
|
container,
|
||||||
|
closeCallback,
|
||||||
|
close() {
|
||||||
|
this.container.remove();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
document.body.appendChild(container);
|
||||||
|
ReactDOM.render(
|
||||||
|
<ContextMenu left={left} top={top} container={container} closeCallback={closeCallback}>
|
||||||
|
{contentHolder}
|
||||||
|
</ContextMenu>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showMenu(left, top, contentHolder, closeCallback = null) {
|
||||||
|
hideMenu();
|
||||||
|
if (hideToken) hideToken.cancel();
|
||||||
|
menuHandle = showMenuCore(left, top, contentHolder, closeCallback);
|
||||||
|
captureMouseDownEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
function captureMouseDownEvents() {
|
||||||
|
document.addEventListener('mousedown', mouseDownListener, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseMouseDownEvents() {
|
||||||
|
document.removeEventListener('mousedown', mouseDownListener, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function captureMouseUpEvents() {
|
||||||
|
document.addEventListener('mouseup', mouseUpListener, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseMouseUpEvents() {
|
||||||
|
document.removeEventListener('mouseup', mouseUpListener, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mouseDownListener(e) {
|
||||||
|
captureMouseUpEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mouseUpListener(e) {
|
||||||
|
let token = new LoadingToken();
|
||||||
|
hideToken = token;
|
||||||
|
await sleep(0);
|
||||||
|
if (token.isCanceled) return;
|
||||||
|
hideMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideMenu() {
|
||||||
|
if (menuHandle == null) return;
|
||||||
|
menuHandle.close();
|
||||||
|
if (menuHandle.closeCallback) menuHandle.closeCallback();
|
||||||
|
menuHandle = null;
|
||||||
|
releaseMouseDownEvents();
|
||||||
|
releaseMouseUpEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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`;
|
||||||
|
}
|
||||||
17
web/src/modals/showModal.js
Normal file
17
web/src/modals/showModal.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import useModalState from './useModalState';
|
||||||
|
|
||||||
|
function ShowModalComponent({ renderModal, container }) {
|
||||||
|
const modalState = useModalState(true);
|
||||||
|
if (!modalState.isOpen) {
|
||||||
|
container.remove();
|
||||||
|
}
|
||||||
|
return renderModal(modalState);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function showModal(renderModal) {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
ReactDOM.render(<ShowModalComponent renderModal={renderModal} container={container} />, container);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export default function useModalState() {
|
export default function useModalState(isOpenDefault = false) {
|
||||||
const [isOpen, setOpen] = React.useState(false);
|
const [isOpen, setOpen] = React.useState(isOpenDefault);
|
||||||
const close = () => setOpen(false);
|
const close = () => setOpen(false);
|
||||||
const open = () => setOpen(true);
|
const open = () => setOpen(true);
|
||||||
return { isOpen, open, close };
|
return { isOpen, open, close };
|
||||||
|
|||||||
5
web/src/utility/axios.js
Normal file
5
web/src/utility/axios.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export default axios.create({
|
||||||
|
baseURL: 'http://localhost:3000',
|
||||||
|
});
|
||||||
13
web/src/utility/common.js
Normal file
13
web/src/utility/common.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export class LoadingToken {
|
||||||
|
constructor() {
|
||||||
|
this.isCanceled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.isCanceled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sleep(milliseconds) {
|
||||||
|
return new Promise(resolve => window.setTimeout(() => resolve(null), milliseconds));
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import axios from 'axios'
|
import axios from './axios';
|
||||||
|
|
||||||
export default function useFetch(url, defValue) {
|
export default function useFetch(url, defValue) {
|
||||||
const [value, setValue] = React.useState(defValue);
|
const [value, setValue] = React.useState(defValue);
|
||||||
|
|
||||||
async function loadValue() {
|
async function loadValue() {
|
||||||
setValue(await axios.get(url));
|
const resp = await axios.get(url);
|
||||||
|
setValue(resp.data);
|
||||||
}
|
}
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
loadValue();
|
loadValue();
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import useModalState from '../modals/useModalState';
|
import useModalState from '../modals/useModalState';
|
||||||
import ConnectionModal from '../modals/ConnectionModal';
|
import ConnectionModal from '../modals/ConnectionModal';
|
||||||
|
import useFetch from '../utility/useFetch';
|
||||||
|
import { AppObjectList } from '../appobj/AppObjects';
|
||||||
|
import connectionAppObject from '../appobj/connectionAppObject';
|
||||||
|
|
||||||
export default function DatabaseWidget() {
|
export default function DatabaseWidget() {
|
||||||
const modalState = useModalState();
|
const modalState = useModalState();
|
||||||
|
const connections = useFetch('connections/list', []);
|
||||||
|
console.log(connections);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConnectionModal modalState={modalState} />
|
<ConnectionModal modalState={modalState} />
|
||||||
<button onClick={modalState.open}>Add connection</button>
|
<button onClick={modalState.open}>Add connection</button>
|
||||||
|
<AppObjectList list={connections} makeAppObj={connectionAppObject} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user