aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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