mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-29 19:13:59 +00:00
query - result tabs
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"http": "^0.0.0",
|
"http": "^0.0.0",
|
||||||
|
"line-reader": "^0.4.0",
|
||||||
"mssql": "^6.0.1",
|
"mssql": "^6.0.1",
|
||||||
"mysql": "^2.17.1",
|
"mysql": "^2.17.1",
|
||||||
"nedb-promises": "^4.0.1",
|
"nedb-promises": "^4.0.1",
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { fork } = require('child_process');
|
const { fork } = require('child_process');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const nedb = require('nedb-promises');
|
const nedb = require('nedb-promises');
|
||||||
|
|
||||||
const datadir = require('../utility/datadir');
|
const { datadir } = require('../utility/directories');
|
||||||
const socket = require('../utility/socket');
|
const socket = require('../utility/socket');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -28,7 +27,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
test(req, res) {
|
test(req, res) {
|
||||||
const subprocess = fork(process.argv[1], ['connectProcess']);
|
const subprocess = fork(process.argv[1], ['connectProcess']);
|
||||||
subprocess.on('message', resp => res.json(resp));
|
subprocess.on('message', (resp) => res.json(resp));
|
||||||
subprocess.send(req.body);
|
subprocess.send(req.body);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
77
packages/api/src/controllers/jsldata.js
Normal file
77
packages/api/src/controllers/jsldata.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const lineReader = require('line-reader');
|
||||||
|
const { jsldir } = require('../utility/directories');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
openedReaders: {},
|
||||||
|
|
||||||
|
closeReader(jslid) {
|
||||||
|
if (!this.openedReaders[jslid]) return Promise.resolve();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.openedReaders[jslid].reader.close((err) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve();
|
||||||
|
delete this.openedReaders[jslid];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
readLine(jslid) {
|
||||||
|
if (!this.openedReaders[jslid]) return Promise.reject();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const { reader } = this.openedReaders[jslid];
|
||||||
|
if (!reader.hasNextLine()) return Promise.resolve(null);
|
||||||
|
reader.nextLine((err, line) => {
|
||||||
|
this.openedReaders[jslid].readedCount += 1;
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(line);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
openReader(jslid) {
|
||||||
|
const file = path.join(jsldir(), `${jslid}.jsonl`);
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
lineReader.open(file, function (err, reader) {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve();
|
||||||
|
this.openedReaders[jslid] = {
|
||||||
|
reader,
|
||||||
|
readedCount: 0,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
async ensureReader(jslid, offset) {
|
||||||
|
if (this.openedReaders[jslid] && this.openedReaders[jslid].readedCount > offset) {
|
||||||
|
await this.closeReader();
|
||||||
|
}
|
||||||
|
if (!this.openedReaders[jslid]) {
|
||||||
|
await this.openReader();
|
||||||
|
}
|
||||||
|
while (this.openedReaders[jslid].readedCount < offset) {
|
||||||
|
await this.readLine(jslid);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getInfo_meta: 'get',
|
||||||
|
getInfo(jslid) {
|
||||||
|
const file = path.join(jsldir(), `${jslid}.jsonl.info`);
|
||||||
|
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
||||||
|
},
|
||||||
|
|
||||||
|
getRows_meta: 'get',
|
||||||
|
async getRows(jslid, offset, limit) {
|
||||||
|
await this.ensureReader(jslid, offset);
|
||||||
|
const res = [];
|
||||||
|
for (let i = 0; i < limit; i += 1) {
|
||||||
|
const line = await this.readLine(jslid);
|
||||||
|
if (line == null) break;
|
||||||
|
res.push(JSON.parse(line));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -24,6 +24,15 @@ module.exports = {
|
|||||||
socket.emit(`session-info-${sesid}`, info);
|
socket.emit(`session-info-${sesid}`, info);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handle_done(sesid) {
|
||||||
|
socket.emit(`session-done-${sesid}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
handle_recordset(sesid, props) {
|
||||||
|
const { jslid } = props;
|
||||||
|
socket.emit(`session-recordset-${sesid}`, { jslid });
|
||||||
|
},
|
||||||
|
|
||||||
create_meta: 'post',
|
create_meta: 'post',
|
||||||
async create({ conid, database }) {
|
async create({ conid, database }) {
|
||||||
const sesid = uuidv1();
|
const sesid = uuidv1();
|
||||||
|
|||||||
@@ -1,10 +1,55 @@
|
|||||||
const engines = require('@dbgate/engines');
|
const engines = require('@dbgate/engines');
|
||||||
|
const uuidv1 = require('uuid/v1');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
const driverConnect = require('../utility/driverConnect');
|
const driverConnect = require('../utility/driverConnect');
|
||||||
|
const { jsldir } = require('../utility/directories');
|
||||||
|
|
||||||
let systemConnection;
|
let systemConnection;
|
||||||
let storedConnection;
|
let storedConnection;
|
||||||
let afterConnectCallbacks = [];
|
let afterConnectCallbacks = [];
|
||||||
|
|
||||||
|
class StreamHandler {
|
||||||
|
constructor() {
|
||||||
|
this.recordset = this.recordset.bind(this);
|
||||||
|
this.row = this.row.bind(this);
|
||||||
|
this.error = this.error.bind(this);
|
||||||
|
this.done = this.done.bind(this);
|
||||||
|
this.info = this.info.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeCurrentStream() {
|
||||||
|
if (this.currentStream) {
|
||||||
|
this.currentStream.end();
|
||||||
|
this.currentStream = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recordset(columns) {
|
||||||
|
this.closeCurrentStream();
|
||||||
|
this.jslid = uuidv1();
|
||||||
|
this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
|
||||||
|
this.currentStream = fs.createWriteStream(this.currentFile);
|
||||||
|
fs.writeFileSync(`${this.currentFile}.info`, JSON.stringify(columns));
|
||||||
|
process.send({ msgtype: 'recordset', jslid: this.jslid });
|
||||||
|
}
|
||||||
|
row(row) {
|
||||||
|
// console.log('ACCEPT ROW', row);
|
||||||
|
this.currentStream.write(JSON.stringify(row) + '\n');
|
||||||
|
}
|
||||||
|
error(error) {
|
||||||
|
process.send({ msgtype: 'error', error });
|
||||||
|
}
|
||||||
|
done(result) {
|
||||||
|
this.closeCurrentStream();
|
||||||
|
process.send({ msgtype: 'done', result });
|
||||||
|
}
|
||||||
|
info(info) {
|
||||||
|
process.send({ msgtype: 'info', info });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleConnect(connection) {
|
async function handleConnect(connection) {
|
||||||
storedConnection = connection;
|
storedConnection = connection;
|
||||||
|
|
||||||
@@ -27,23 +72,8 @@ async function handleExecuteQuery({ sql }) {
|
|||||||
await waitConnected();
|
await waitConnected();
|
||||||
const driver = engines(storedConnection);
|
const driver = engines(storedConnection);
|
||||||
|
|
||||||
await driver.stream(systemConnection, sql, {
|
const handler = new StreamHandler();
|
||||||
recordset: (columns) => {
|
await driver.stream(systemConnection, sql, handler);
|
||||||
process.send({ msgtype: 'recordset', columns });
|
|
||||||
},
|
|
||||||
row: (row) => {
|
|
||||||
process.send({ msgtype: 'row', row });
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
process.send({ msgtype: 'error', error });
|
|
||||||
},
|
|
||||||
done: (result) => {
|
|
||||||
process.send({ msgtype: 'done', result });
|
|
||||||
},
|
|
||||||
info: (info) => {
|
|
||||||
process.send({ msgtype: 'info', info });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageHandlers = {
|
const messageHandlers = {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
const os = require('os');
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
let created = false;
|
|
||||||
|
|
||||||
module.exports = function datadir() {
|
|
||||||
const dir = path.join(os.homedir(), 'dbgate-data');
|
|
||||||
if (!created) {
|
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
console.log(`Creating data directory ${dir}`)
|
|
||||||
fs.mkdirSync(dir);
|
|
||||||
}
|
|
||||||
created = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dir;
|
|
||||||
};
|
|
||||||
37
packages/api/src/utility/directories.js
Normal file
37
packages/api/src/utility/directories.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
let createdDatadir = false;
|
||||||
|
let createdJsldir = false;
|
||||||
|
|
||||||
|
function datadir() {
|
||||||
|
const dir = path.join(os.homedir(), 'dbgate-data');
|
||||||
|
if (!createdDatadir) {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
console.log(`Creating data directory ${dir}`);
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
}
|
||||||
|
createdDatadir = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsldir() {
|
||||||
|
const dir = path.join(datadir(), 'jsl');
|
||||||
|
if (!createdJsldir) {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
console.log(`Creating jsl directory ${dir}`);
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
}
|
||||||
|
createdJsldir = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
datadir,
|
||||||
|
jsldir,
|
||||||
|
};
|
||||||
@@ -13,6 +13,10 @@ const dialect = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function extractColumns(columns) {
|
||||||
|
return _.sortBy(_.values(columns), 'index')
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {import('@dbgate/types').EngineDriver} */
|
/** @type {import('@dbgate/types').EngineDriver} */
|
||||||
const driver = {
|
const driver = {
|
||||||
async connect(nativeModules, { server, port, user, password, database }) {
|
async connect(nativeModules, { server, port, user, password, database }) {
|
||||||
@@ -36,7 +40,7 @@ const driver = {
|
|||||||
const res = {};
|
const res = {};
|
||||||
|
|
||||||
if (resp.recordset) {
|
if (resp.recordset) {
|
||||||
res.columns = _.sortBy(_.values(resp.recordset.columns), 'index');
|
res.columns = extractColumns(resp.recordset.columns);
|
||||||
res.rows = resp.recordset;
|
res.rows = resp.recordset;
|
||||||
}
|
}
|
||||||
if (resp.rowsAffected) {
|
if (resp.rowsAffected) {
|
||||||
@@ -58,15 +62,20 @@ const driver = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDone = (result) => {
|
const handleDone = (result) => {
|
||||||
console.log('RESULT', result);
|
// console.log('RESULT', result);
|
||||||
|
options.done(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRow = (row) => {
|
const handleRow = (row) => {
|
||||||
console.log('ROW', row);
|
options.row(row);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRecordset = (columns) => {
|
||||||
|
options.recordset(extractColumns(columns));
|
||||||
};
|
};
|
||||||
|
|
||||||
request.stream = true;
|
request.stream = true;
|
||||||
request.on('recordset', options.recordset);
|
request.on('recordset', handleRecordset);
|
||||||
request.on('row', handleRow);
|
request.on('row', handleRow);
|
||||||
request.on('error', options.error);
|
request.on('error', options.error);
|
||||||
request.on('done', handleDone);
|
request.on('done', handleDone);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { GridDisplay, ChangeSet } from '@dbgate/datalib';
|
import { GridDisplay, ChangeSet } from '@dbgate/datalib';
|
||||||
|
|
||||||
export interface DataGridProps {
|
export interface DataGridProps {
|
||||||
conid: number;
|
conid?: number;
|
||||||
database: string;
|
database?: string;
|
||||||
display: GridDisplay;
|
display: GridDisplay;
|
||||||
tabVisible?: boolean;
|
tabVisible?: boolean;
|
||||||
changeSetState: { value: ChangeSet };
|
changeSetState?: { value: ChangeSet };
|
||||||
dispatchChangeSet: Function;
|
dispatchChangeSet?: Function;
|
||||||
toolbarPortalRef: any;
|
toolbarPortalRef?: any;
|
||||||
}
|
}
|
||||||
|
|||||||
8
packages/web/src/sqleditor/JslDataGrid.js
Normal file
8
packages/web/src/sqleditor/JslDataGrid.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import DataGrid from '../datagrid/DataGrid';
|
||||||
|
|
||||||
|
export default function JslDataGrid({ jslid }) {
|
||||||
|
return <div>{jslid}</div>;
|
||||||
|
// const display=React.useMemo(()=>)
|
||||||
|
// return <DataGrid />;
|
||||||
|
}
|
||||||
34
packages/web/src/sqleditor/ResultTabs.js
Normal file
34
packages/web/src/sqleditor/ResultTabs.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TabPage, TabControl } from '../widgets/TabControl';
|
||||||
|
import useSocket from '../utility/SocketProvider';
|
||||||
|
import JslDataGrid from './JslDataGrid';
|
||||||
|
|
||||||
|
export default function ResultTabs({ children, sessionId }) {
|
||||||
|
const socket = useSocket();
|
||||||
|
const [resultIds, setResultIds] = React.useState([]);
|
||||||
|
|
||||||
|
const handleResultSet = (props) => {
|
||||||
|
const { jslid } = props;
|
||||||
|
setResultIds((ids) => [...ids, jslid]);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (sessionId && socket) {
|
||||||
|
socket.on(`session-recordset-${sessionId}`, handleResultSet);
|
||||||
|
return () => {
|
||||||
|
socket.off(`session-recordset-${sessionId}`, handleResultSet);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [sessionId, socket]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabControl>
|
||||||
|
{children}
|
||||||
|
{resultIds.map((jslid, index) => (
|
||||||
|
<TabPage label={`Result ${index + 1}`} key={index}>
|
||||||
|
<JslDataGrid jslid={jslid} />
|
||||||
|
</TabPage>
|
||||||
|
))}
|
||||||
|
</TabControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ export default function SqlEditor({
|
|||||||
readOnly = false,
|
readOnly = false,
|
||||||
onChange = undefined,
|
onChange = undefined,
|
||||||
tabVisible = false,
|
tabVisible = false,
|
||||||
|
onKeyDown = undefined,
|
||||||
}) {
|
}) {
|
||||||
const [containerRef, { height, width }] = useDimensions();
|
const [containerRef, { height, width }] = useDimensions();
|
||||||
const editorRef = React.useRef(null);
|
const editorRef = React.useRef(null);
|
||||||
@@ -35,6 +36,16 @@ export default function SqlEditor({
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (tabVisible && editorRef.current && editorRef.current.editor) editorRef.current.editor.focus();
|
if (tabVisible && editorRef.current && editorRef.current.editor) editorRef.current.editor.focus();
|
||||||
}, [tabVisible]);
|
}, [tabVisible]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (onKeyDown && editorRef.current) {
|
||||||
|
editorRef.current.editor.keyBinding.addKeyboardHandler(onKeyDown);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
editorRef.current.editor.keyBinding.removeKeyboardHandler(onKeyDown);
|
||||||
|
};
|
||||||
|
}, [onKeyDown]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper ref={containerRef}>
|
<Wrapper ref={containerRef}>
|
||||||
<AceEditor
|
<AceEditor
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import { useUpdateDatabaseForTab } from '../utility/globalState';
|
|||||||
import QueryToolbar from '../query/QueryToolbar';
|
import QueryToolbar from '../query/QueryToolbar';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import SessionMessagesView from '../query/SessionMessagesView';
|
import SessionMessagesView from '../query/SessionMessagesView';
|
||||||
|
import { TabPage, TabControl } from '../widgets/TabControl';
|
||||||
|
import getResultTabs from '../sqleditor/ResultTabs';
|
||||||
|
import ResultTabs from '../sqleditor/ResultTabs';
|
||||||
|
|
||||||
const MainContainer = styled.div``;
|
const MainContainer = styled.div``;
|
||||||
|
|
||||||
@@ -65,6 +68,8 @@ export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPo
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e) => {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
<EditorContainer>
|
<EditorContainer>
|
||||||
@@ -73,6 +78,7 @@ export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPo
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
tabVisible={tabVisible}
|
tabVisible={tabVisible}
|
||||||
engine={connection && connection.engine}
|
engine={connection && connection.engine}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{toolbarPortalRef &&
|
{toolbarPortalRef &&
|
||||||
@@ -84,7 +90,11 @@ export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPo
|
|||||||
)}
|
)}
|
||||||
</EditorContainer>
|
</EditorContainer>
|
||||||
<MessagesContainer>
|
<MessagesContainer>
|
||||||
<SessionMessagesView sessionId={sessionId} />
|
<ResultTabs sessionId={sessionId}>
|
||||||
|
<TabPage label="Messages">
|
||||||
|
<SessionMessagesView sessionId={sessionId} />
|
||||||
|
</TabPage>
|
||||||
|
</ResultTabs>
|
||||||
</MessagesContainer>
|
</MessagesContainer>
|
||||||
</MainContainer>
|
</MainContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
56
packages/web/src/widgets/TabControl.js
Normal file
56
packages/web/src/widgets/TabControl.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import theme from '../theme';
|
||||||
|
|
||||||
|
const TabItem = styled.div`
|
||||||
|
border-right: 1px solid white;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: ${theme.tabsPanel.hoverFont};
|
||||||
|
}
|
||||||
|
background-color: ${(props) =>
|
||||||
|
// @ts-ignore
|
||||||
|
props.selected ? theme.mainArea.background : 'inherit'};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TabNameWrapper = styled.span`
|
||||||
|
margin-left: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TabContainer = styled.div``;
|
||||||
|
|
||||||
|
const TabsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
height: ${theme.tabsPanel.height}px;
|
||||||
|
right: 0;
|
||||||
|
background-color: ${theme.tabsPanel.background};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function TabPage({ label = undefined, children }) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TabControl({ children }) {
|
||||||
|
const [value, setValue] = React.useState(0);
|
||||||
|
const childrenArray = (_.isArray(children) ? _.flatten(children) : [children]).filter((x) => x);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TabsContainer>
|
||||||
|
{childrenArray
|
||||||
|
.filter((x) => x.props)
|
||||||
|
.map((tab, index) => (
|
||||||
|
// @ts-ignore
|
||||||
|
<TabItem key={index} onClick={() => setValue(index)} selected={value == index}>
|
||||||
|
<TabNameWrapper>{tab.props.label}</TabNameWrapper>
|
||||||
|
</TabItem>
|
||||||
|
))}
|
||||||
|
</TabsContainer>
|
||||||
|
{<TabContainer key={value}>{childrenArray[value] && childrenArray[value].props.children}</TabContainer>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6978,6 +6978,11 @@ lie@3.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
immediate "~3.0.5"
|
immediate "~3.0.5"
|
||||||
|
|
||||||
|
line-reader@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/line-reader/-/line-reader-0.4.0.tgz#17e44818da0ac335675ba300954f94ef670e66fd"
|
||||||
|
integrity sha1-F+RIGNoKwzVnW6MAlU+U72cOZv0=
|
||||||
|
|
||||||
lines-and-columns@^1.1.6:
|
lines-and-columns@^1.1.6:
|
||||||
version "1.1.6"
|
version "1.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
||||||
|
|||||||
Reference in New Issue
Block a user