aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Barnett <james.barnett@fivium.co.uk>2018-02-21 21:42:55 +0000
committerGitHub <noreply@github.com>2018-02-21 21:42:55 +0000
commit11e98009906651acb110cd3b1625a771b1e2f472 (patch)
treede0a90c201f1949e701c969b2a3a94d7015f4006
parent6e21916395fac6861783c2b930b47242ac0f4c09 (diff)
parent703f78867653ed9c53a745f9808eb96ae8a89dc7 (diff)
downloadsql-plus-plus-11e98009906651acb110cd3b1625a771b1e2f472.tar.xz
sql-plus-plus-11e98009906651acb110cd3b1625a771b1e2f472.zip
Merge branch tabbed-interface into master
Support multiple, configurable connections Each connection is displayed in its own tab in the main UI, and gets its own query executor. New connections can be added dynamically through the new connection dialog.
-rw-r--r--editor-instance.html45
-rw-r--r--editor-instance.js (renamed from renderer.js)65
-rw-r--r--index.html36
-rw-r--r--instance-manager.js40
-rw-r--r--main.js106
-rw-r--r--new-connection.html72
-rw-r--r--new-connection.js46
-rw-r--r--package-lock.json12
-rw-r--r--package.json5
-rw-r--r--query-executor.js45
-rw-r--r--styles/editor-instance.css (renamed from styles/style.css)0
-rw-r--r--styles/instance-manager.css35
-rw-r--r--styles/new-connection.css3
13 files changed, 425 insertions, 85 deletions
diff --git a/editor-instance.html b/editor-instance.html
new file mode 100644
index 0000000..454c6ce
--- /dev/null
+++ b/editor-instance.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="UTF-8">
+ <title>SQL++</title>
+
+ <link rel="stylesheet" href="node_modules/codemirror/lib/codemirror.css"></link>
+ <link rel="stylesheet" href="node_modules/codemirror/theme/dracula.css"></link>
+ <link rel="stylesheet" href="node_modules/codemirror/addon/hint/show-hint.css"></link>
+ <link rel="stylesheet" href="node_modules/jquery.tabulator/dist/css/tabulator.css"></link>
+ <link rel="stylesheet" type="text/css" href="./styles/editor-instance.css"></link>
+ <link rel="stylesheet" type="text/css" href="./styles/table-style.css"></link>
+
+</head>
+
+<body>
+
+ <div class="flex-wrapper">
+ <div class="row header">
+ <input id="run-query" type="button" value="Run"></input>
+ </div>
+
+
+ <div class="row content">
+ <div id="editor" class="editor-row split-row"></div>
+
+ <div class="results-row split-row">
+ <div id="result-table"></div>
+ <div id="result-error"></div>
+ </div>
+ </div>
+
+ <div class="row footer">
+ <div id="execution-status" class="exec-idle">Idle</div>
+ <div id="execution-time"></div>
+ <div id="cursor-coords"></div>
+ </div>
+ </div>
+ <script>
+ require("./editor-instance.js");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/renderer.js b/editor-instance.js
index 9fe1be7..8ed6674 100644
--- a/renderer.js
+++ b/editor-instance.js
@@ -6,10 +6,14 @@ require("jquery-ui");
require("jquery.tabulator");
const cm = require("codemirror");
require("codemirror/mode/sql/sql");
-require("codemirror/addon/hint/show-hint.js")
-require("codemirror/addon/hint/sql-hint.js")
+require("codemirror/addon/hint/show-hint.js");
+require("codemirror/addon/hint/sql-hint.js");
const Split = require("split.js");
+const editorInstanceId = require('uuid/v1')();
+
+let queryExecutorId;
+
const editorContext = cm(document.getElementById("editor"), {
value: "select *\nfrom information_schema.tables\n/\nselect now()\n/\nselect *\nfrom foo",
mode: "text/x-sql",
@@ -24,7 +28,20 @@ editorContext.on("cursorActivity", (instance) => {
$("#cursor-coords").text("Ln " + (parseInt(coords.line) + 1) + ", Col " + (parseInt(coords.ch) + 1));
});
-ipcRenderer.send("queryExecutor.queryTableMetadata");
+const statementDelimiter = "/";
+
+let dataTable;
+let execStartTime;
+let execTimerInterval;
+let execElapsedTime;
+let queryMark;
+
+ipcRenderer.on("editorInstance.registerQueryExecutor", (event, payload) => {
+ queryExecutorId = payload;
+ console.log(queryExecutorId);
+ ipcRenderer.send("queryExecutor.queryTableMetadata", _generateIpcPayload());
+})
+
ipcRenderer.on("queryExecutor.queryTableMetadataComplete", (event, response) => {
console.log(response);
cm.commands.autocomplete = function (cmInstance) {
@@ -34,22 +51,15 @@ ipcRenderer.on("queryExecutor.queryTableMetadataComplete", (event, response) =>
}
});
-const statementDelimiter = "/";
-
-let dataTable;
-let execStartTime;
-let execTimerInterval;
-let execElapsedTime;
-let queryMark;
-
function runQuery() {
_setExecutionStatusIndicator("RUNNING");
_startExecTimer();
- let query = findQuery();
+ let payload = _generateIpcPayload();
+ payload.query = findQuery();
- ipcRenderer.send("queryExecutor.runQuery", query);
+ ipcRenderer.send("queryExecutor.runQuery", payload);
}
/**
@@ -126,15 +136,26 @@ function _clearQueryMarks() {
editorContext.clearGutter("statement-pointer");
}
-ipcRenderer.on("queryExecutor.runQueryComplete", (event, response) => {
- _stopExecTimer();
- if (response.error === undefined) {
- handleResult(response.result);
- }
- else {
- handleError(response.error);
+function _generateIpcPayload() {
+ return {
+ editorInstanceId: editorInstanceId,
+ queryExecutorId: queryExecutorId
}
+}
+ipcRenderer.on("queryExecutor.runQueryComplete", (event, response) => {
+ // TODO - new instances should register with the instance manager, and the manager should proxy IPC messages only to
+ // the webview which sent the message. Rather than sending to all instances and the instance having to check
+ // if they were the intended recipient
+ if (response.editorInstanceId === editorInstanceId) {
+ _stopExecTimer();
+ if (response.error === undefined) {
+ handleResult(response.result);
+ }
+ else {
+ handleError(response.error);
+ }
+ }
});
function _startExecTimer() {
@@ -154,7 +175,7 @@ function _stopExecTimer() {
function handleError(err) {
_stopExecTimer();
_destroyDataTable();
- $("#result-error").removeAttr("style").text("Error (" + err.code + ") - " + err.message);
+ $("#result-error").removeAttr("style").text("Error (" + err.code + ") - " + err.cause);
_setExecutionStatusIndicator("ERROR");
$("#execution-time").text("failed after " + execElapsedTime + " ms");
}
@@ -202,7 +223,7 @@ function _setExecutionStatusIndicator(status) {
}
function _destroyDataTable() {
- if(dataTable) {
+ if (dataTable) {
_resultTable().tabulator("destroy");
_resultTable().removeAttr("style").empty();
dataTable = undefined;
diff --git a/index.html b/index.html
index 0de4296..2d8dbee 100644
--- a/index.html
+++ b/index.html
@@ -4,37 +4,23 @@
<meta charset="UTF-8">
<title>SQL++</title>
- <link rel="stylesheet" href="node_modules/codemirror/lib/codemirror.css"></link>
- <link rel="stylesheet" href="node_modules/codemirror/theme/dracula.css"></link>
- <link rel="stylesheet" href="node_modules/codemirror/addon/hint/show-hint.css"></link>
- <link rel="stylesheet" href="node_modules/jquery.tabulator/dist/css/tabulator.css"></link>
- <link rel="stylesheet" type="text/css" href="./styles/style.css"></link>
- <link rel="stylesheet" type="text/css" href="./styles/table-style.css"></link>
+ <link rel="stylesheet" href="node_modules/electron-tabs/electron-tabs.css"></link>
+ <link rel="stylesheet" href="./styles/instance-manager.css"></link>
+
</head>
<body>
- <div class="flex-wrapper">
- <div class="row header">
- <input id="run-query" type="button" value="Run"></input>
- </div>
-
- <div class="row content">
- <div id="editor" class="editor-row split-row"></div>
-
- <div class="results-row split-row">
- <div id="result-table"></div>
- <div id="result-error"></div>
- </div>
- </div>
-
- <div class="row footer">
- <div id="execution-status" class="exec-idle">Idle</div>
- <div id="execution-time"></div>
- <div id="cursor-coords"></div>
+ <div class="etabs-tabgroup">
+ <div class="etabs-tabs"></div>
+ <div class="etabs-buttons">
+ <button id="new-connection">New connection</button>
</div>
</div>
+ <div class="etabs-tabgroup-footer"></div>
+ <div class="etabs-views"></div>
+
<script>
- require("./renderer.js");
+ require("./instance-manager.js");
</script>
</body>
</html> \ No newline at end of file
diff --git a/instance-manager.js b/instance-manager.js
new file mode 100644
index 0000000..7d27a7e
--- /dev/null
+++ b/instance-manager.js
@@ -0,0 +1,40 @@
+"use strict";
+
+const { ipcRenderer } = require('electron');
+const TabGroup = require("electron-tabs");
+const $ = window.jQuery = require("jquery");
+
+const tabGroup = new TabGroup();
+
+function createNewConnection() {
+ ipcRenderer.send("instanceManager.openNewConnectionDialog");
+}
+
+function registerNewInstance(payload) {
+ tabGroup.addTab({
+ title: payload.connectionName,
+ src: "file://" + __dirname + "/editor-instance.html",
+ visible: true,
+ active: true,
+ webviewAttributes: {"nodeintegration":true},
+ ready: tab => {
+ let webview = tab.webview;
+ if (!!webview) {
+ webview.addEventListener("dom-ready", () => {
+ webview.send("editorInstance.registerQueryExecutor", payload.assignedQueryExecutorId);
+ })
+ }
+ }
+ });
+}
+
+ipcRenderer.on("instanceManager.registerNewInstance", (event, payload) => {
+ registerNewInstance(payload);
+});
+
+
+$(document).ready(() => {
+ $("#new-connection").click(() => {
+ createNewConnection();
+ })
+}) \ No newline at end of file
diff --git a/main.js b/main.js
index cc89b11..493d066 100644
--- a/main.js
+++ b/main.js
@@ -4,7 +4,8 @@ const path = require("path");
const url = require("url");
let uiWindow;
-let queryExecutorProcess;
+let newConnectionDialog;
+let queryExecutors = [];
function createMainWindow() {
uiWindow = new BrowserWindow({
@@ -23,26 +24,8 @@ function createMainWindow() {
});
}
-function createQueryExecutorProcess() {
- queryExecutorProcess = new BrowserWindow({
- show: false
- });
-
- queryExecutorProcess.loadURL(url.format({
- pathname: path.join(__dirname, "./query-executor-wrapper.html"),
- protocol: "file:",
- slashes: true
- }));
-
- queryExecutorProcess.on("closed", () => {
- queryExecutorProcess = null;
- });
-
-}
-
app.on("ready", () => {
createMainWindow();
- createQueryExecutorProcess();
});
app.on("window-all-closed", () => {
@@ -57,8 +40,85 @@ app.on("activate", () => {
}
});
-ipcMain.on("queryExecutor.runQueryComplete", (event, payload) => uiWindow.webContents.send("queryExecutor.runQueryComplete", payload));
-ipcMain.on("queryExecutor.runQuery", (event, payload) => queryExecutorProcess.webContents.send("queryExecutor.runQuery", payload));
+function createNewConnectionDialog() {
+ newConnectionDialog = new BrowserWindow({
+ width: 400,
+ height: 540
+ });
+ newConnectionDialog.loadURL(url.format({
+ pathname: path.join(__dirname, "new-connection.html"),
+ protocol: "file:",
+ slashes: true
+ }));
+
+ newConnectionDialog.on("closed", () => {
+ newConnectionDialog = null;
+ });
+}
+
+ipcMain.on("instanceManager.openNewConnectionDialog", (event, payload) => {
+ createNewConnectionDialog();
+});
+
+function createQueryExecutor(payload) {
+ let executor = new BrowserWindow({
+ show: false,
+ });
+
+ executor.connectionConfig = payload;
+
+ executor.loadURL(url.format({
+ pathname: path.join(__dirname, "./query-executor-wrapper.html"),
+ protocol: "file:",
+ slashes: true
+ }));
+
+ queryExecutors.push(executor);
+
+ return executor;
+}
+
+ipcMain.on("newConnection.createConnection", (event, payload) => {
+ createQueryExecutor(payload);
+});
+
+
+ipcMain.on("queryExecutor.initialiseConnectionCallback", (event, executorId) => {
+ // TODO - handle connection initialisation errors
+
+ // Bit of a hack, cant guarantee this was the executor which just got initalised.executor
+ // Should pass it back from the executor via the payload
+ let connectionName = queryExecutors[queryExecutors.length -1].connectionConfig.name;
+
+ uiWindow.webContents.send("instanceManager.registerNewInstance", {assignedQueryExecutorId: executorId, connectionName: connectionName});
+ newConnectionDialog.close();
+});
+
+
+const { webContents } = require('electron');
+
+// TODO - only send messages to instance manager which will route request to correct webView, rather than
+// sending to all webViews
+ipcMain.on("queryExecutor.runQueryComplete", (event, payload) => {
+ webContents.getAllWebContents().forEach((w) => {
+ w.send("queryExecutor.runQueryComplete", payload);
+ })
+});
-ipcMain.on("queryExecutor.queryTableMetadataComplete", (event, payload) => uiWindow.webContents.send("queryExecutor.queryTableMetadataComplete", payload));
-ipcMain.on("queryExecutor.queryTableMetadata", (event, payload) => queryExecutorProcess.webContents.send("queryExecutor.queryTableMetadata", payload)); \ No newline at end of file
+ipcMain.on("queryExecutor.runQuery", (event, payload) => {
+ queryExecutors.forEach((executor) => {
+ executor.webContents.send("queryExecutor.runQuery", payload);
+ });
+});
+
+ipcMain.on("queryExecutor.queryTableMetadataComplete", (event, payload) => {
+ webContents.getAllWebContents().forEach((w) => {
+ w.send("queryExecutor.queryTableMetadataComplete", payload);
+ })
+});
+;
+ipcMain.on("queryExecutor.queryTableMetadata", (event, payload) => {
+ queryExecutors.forEach((executor) => {
+ executor.webContents.send("queryExecutor.queryTableMetadata", payload);
+ });
+}); \ No newline at end of file
diff --git a/new-connection.html b/new-connection.html
new file mode 100644
index 0000000..1ac6c89
--- /dev/null
+++ b/new-connection.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>SQL++ - New connection</title>
+ <link rel="stylesheet" href="node_modules/bulma/css/bulma.css"></link>
+ <link rel="stylesheet" href="./styles/new-connection.css"></link>
+ </head>
+ <body>
+
+ <h4 class="title is-4" >Add new connection</h4>
+ <form>
+ <div class="field">
+ <label class="label is-small" for="name">Connection name</label>
+ <div class="control">
+ <input name="name" class="input is-small" type="text"/>
+ </div>
+ <p class="help">This is shown as the title of the connection tab</p>
+ </div>
+ <div class="field">
+ <label class="label is-small" for="vendor">Vendor</label>
+ <div class="control">
+ <div class="select is-small">
+ <select name="vendor">
+ <option value="postgres">Postgres</option>
+ <option value="mysql">MySQL/MariaDB</option>
+ <option value="oracle">Oracle</option>
+ </select>
+ </div>
+ </div>
+ </div>
+ <div class="field">
+ <label class="label is-small" for="hostname">Hostname</label>
+ <div class="control">
+ <input name="hostname" class="input is-small" type="text"/>
+ </div>
+ </div>
+ <div class="field">
+ <label class="label is-small" for="port">Port</label>
+ <div class="control">
+ <input name="port" class="input is-small" type="text" placeholder="5432"/>
+ </div>
+ </div>
+ <div class="field">
+ <label class="label is-small" for="username">Username</label>
+ <div class="control">
+ <input name="username" class="input is-small" type="text"/>
+ </div>
+ </div>
+ <div class="field">
+ <label class="label is-small" for="password">Password</label>
+ <div class="control">
+ <input name="password" class="input is-small" type="text"/>
+ </div>
+ </div>
+ <div class="field is-grouped">
+ <div class="control">
+ <input id="create-connection" type="button" class="button is-link is-small" value="Add"></input>
+ </div>
+ <div class="control">
+ <input id="test-connection" type="button" class="button is-small" value="Test connection"></input>
+ </div>
+ <div class="control">
+ <input id="cancel" type="button" class="button is-text is-small" value="Cancel"></input>
+ </div>
+ </div>
+ </form>
+ </body>
+ <script>
+ require("./new-connection.js");
+ </script>
+</html> \ No newline at end of file
diff --git a/new-connection.js b/new-connection.js
new file mode 100644
index 0000000..2493c25
--- /dev/null
+++ b/new-connection.js
@@ -0,0 +1,46 @@
+"use strict";
+
+const { remote, ipcRenderer } = require("electron");
+const $ = window.jQuery = require("jquery");
+
+function cancel() {
+ let confirm = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
+ type: "question",
+ buttons: ["Yes", "No"],
+ title: "Confirm",
+ message: "Remove this connection?"
+ });
+
+ if (confirm === 0) {
+ remote.getCurrentWindow().close();
+ }
+}
+
+function parseForm() {
+ let formData = {};
+
+ $("form").serializeArray().forEach((input) => {
+ formData[input.name] = input.value;
+ });
+
+ return formData;
+}
+
+function createConnection() {
+ let connectionProps = parseForm();
+ ipcRenderer.send("newConnection.createConnection", connectionProps);
+}
+
+$(document).ready(() => {
+ $("#create-connection").click(() => {
+ createConnection();
+ });
+
+ $("#test-connection").click(() => {
+ //TODO
+ });
+
+ $("#cancel").click(() => {
+ cancel();
+ });
+}); \ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 04cec7a..b05f744 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -111,6 +111,12 @@
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
"dev": true
},
+ "bulma": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/bulma/-/bulma-0.6.2.tgz",
+ "integrity": "sha1-9LHRHVrMUaeWROsKKwsQZJ09cfU=",
+ "dev": true
+ },
"camelcase": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
@@ -318,6 +324,12 @@
"sumchecker": "1.3.1"
}
},
+ "electron-tabs": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/electron-tabs/-/electron-tabs-0.9.0.tgz",
+ "integrity": "sha1-kb+L66dftJjp0vjf5WHhH6cLm34=",
+ "dev": true
+ },
"error-ex": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
diff --git a/package.json b/package.json
index ae1958c..5dd96cb 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,9 @@
"pg": "7.4.1",
"split.js": "1.3.5",
"jquery-ui": "1.12.1",
- "jquery.tabulator": "3.4.2"
+ "jquery.tabulator": "3.4.2",
+ "electron-tabs": "0.9.0",
+ "uuid": "3.2.1",
+ "bulma": "0.6.2"
}
}
diff --git a/query-executor.js b/query-executor.js
index 7bd836d..88df7e1 100644
--- a/query-executor.js
+++ b/query-executor.js
@@ -1,29 +1,45 @@
"use strict";
-const { ipcRenderer } = require("electron");
+const { remote, ipcRenderer } = require("electron");
const { Pool } = require("pg");
+const executorId = require("uuid/v1")();
+
+const connectionConfig = remote.getCurrentWindow().connectionConfig;
+
const connectionPool = new Pool({
- user: "postgres",
- host: "localhost",
+ user: connectionConfig.username,
+ host: connectionConfig.host,
database: "postgres",
- password: "",
- port: 5432
+ password: connectionConfig.password,
+ port: connectionConfig.port
});
+// Initialisation completed
+ipcRenderer.send("queryExecutor.initialiseConnectionCallback", executorId);
+
ipcRenderer.on("queryExecutor.runQuery", (event, payload) => {
- connectionPool.query(payload, (err, res) => {
+ if(payload.queryExecutorId === executorId) {
+ connectionPool.query(payload.query, (err, res) => {
- console.log(err, res)
+ console.log(err, res);
+
+ if(err !== undefined) {
+ // The IPC payload is serialised to JSON beofre transmisison, so the protoype chain is lost.
+ // So store the nicely formatted error message as a property.
+ err.cause = err.toString();
+ }
- ipcRenderer.send("queryExecutor.runQueryComplete", {
- "error": err,
- "result": res
+ ipcRenderer.send("queryExecutor.runQueryComplete", {
+ "error": err,
+ "result": res,
+ "editorInstanceId": payload.editorInstanceId
+ });
+
});
-
- });
-
+ }
+
});
ipcRenderer.on("queryExecutor.queryTableMetadata", (event, payload) => {
@@ -50,7 +66,8 @@ ipcRenderer.on("queryExecutor.queryTableMetadata", (event, payload) => {
ipcRenderer.send("queryExecutor.queryTableMetadataComplete", {
"error": err,
- "result": tableMetadata
+ "result": tableMetadata,
+ "editorInstanceId": payload.editorInstanceId
});
});
diff --git a/styles/style.css b/styles/editor-instance.css
index 7ae5727..7ae5727 100644
--- a/styles/style.css
+++ b/styles/editor-instance.css
diff --git a/styles/instance-manager.css b/styles/instance-manager.css
new file mode 100644
index 0000000..c49c8a3
--- /dev/null
+++ b/styles/instance-manager.css
@@ -0,0 +1,35 @@
+body {
+ margin: 0;
+}
+
+.etabs-tabgroup {
+ background-color: #282a36;
+}
+
+.etabs-tab {
+ color: white;
+ background: none;
+ background-color: #474A5E;
+ border: none;
+ font-weight: bold;
+}
+
+.etabs-tab.active {
+ background: #6D8A88;
+}
+
+.etabs-views {
+ border: none;
+ height: calc(100vh - 52px);
+}
+
+.etabs-buttons button {
+ width: inherit;
+ padding: 4px 6px;
+ background: #6D8A88;
+}
+
+.etabs-tabgroup-footer {
+ background: #6D8A88;
+ height: 20px;
+} \ No newline at end of file
diff --git a/styles/new-connection.css b/styles/new-connection.css
new file mode 100644
index 0000000..97aaae6
--- /dev/null
+++ b/styles/new-connection.css
@@ -0,0 +1,3 @@
+html, body {
+ padding: 8px;
+} \ No newline at end of file