From 2327e4e536e3be29ffd7af54ebba6ae24265c692 Mon Sep 17 00:00:00 2001 From: James Barnett Date: Sat, 17 Feb 2018 18:03:34 +0000 Subject: WIP - Add basic unstyled static tabs. IPC needs work --- editor-instance.html | 45 ++++++++++ editor-instance.js | 245 +++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 38 +++----- instance-manager.js | 29 ++++++ main.js | 16 +++- package-lock.json | 6 ++ package.json | 3 +- renderer.js | 245 --------------------------------------------------- 8 files changed, 352 insertions(+), 275 deletions(-) create mode 100644 editor-instance.html create mode 100644 editor-instance.js create mode 100644 instance-manager.js delete mode 100644 renderer.js diff --git a/editor-instance.html b/editor-instance.html new file mode 100644 index 0000000..1983bfe --- /dev/null +++ b/editor-instance.html @@ -0,0 +1,45 @@ + + + + + + SQL++ + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+
+
+
+
+ + +
+ + + + \ No newline at end of file diff --git a/editor-instance.js b/editor-instance.js new file mode 100644 index 0000000..3ec123a --- /dev/null +++ b/editor-instance.js @@ -0,0 +1,245 @@ +"use strict"; + +const { ipcRenderer } = require('electron'); +const $ = window.jQuery = require("jquery"); +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") +const Split = require("split.js"); + +const editorContext = cm(document.getElementById("editor"), { + value: "select *\nfrom information_schema.tables\n/\nselect now()\n/\nselect *\nfrom foo", + mode: "text/x-sql", + theme: "dracula", + lineNumbers: true, + gutters: ["CodeMirror-linenumbers", "statement-pointer"], + extraKeys: { "Ctrl-Space": "autocomplete" } +}); + +editorContext.on("cursorActivity", (instance) => { + let coords = instance.getCursor(); + $("#cursor-coords").text("Ln " + (parseInt(coords.line) + 1) + ", Col " + (parseInt(coords.ch) + 1)); +}); + +ipcRenderer.send("queryExecutor.queryTableMetadata"); +ipcRenderer.on("queryExecutor.queryTableMetadataComplete", (event, response) => { + console.log(response); + cm.commands.autocomplete = function (cmInstance) { + cm.showHint(cmInstance, cm.hint.sql, { + tables: response.result + }); + } +}); + +const statementDelimiter = "/"; + +let dataTable; +let execStartTime; +let execTimerInterval; +let execElapsedTime; +let queryMark; + +function runQuery() { + + _setExecutionStatusIndicator("RUNNING"); + _startExecTimer(); + + let query = findQuery(); + + ipcRenderer.send("queryExecutor.runQuery", query); +} + +/** + * If there's selected text, return it. Otherwise find the statement nearest the cursor. + * Statements are delimited by lines containing only a "/" character. + */ +function findQuery() { + let selectedText = editorContext.getSelection(); + + if (selectedText !== "") { + _clearQueryMarks(); + return selectedText; + } + else { + let cursorLine = editorContext.getCursor().line; + + let statementStartLine = editorContext.firstLine(); + + // lineCount rather than lastLine here, since lineCount is index 1 based. + // getRange(from, to) below is 0 based, but the range is exclusive, so if we need to include the last line we need the +1 + let statementEndLine = editorContext.lineCount(); + + // if the current line is a delimiter, thats the end of the statement + if (editorContext.getLine(cursorLine) === statementDelimiter) { + statementEndLine = cursorLine; + } + else { + // move down the document until a delimiter or the end of the document is reached + for (let i = cursorLine + 1; i <= editorContext.lastLine(); i++) { + if (editorContext.getLine(i) === statementDelimiter) { + statementEndLine = i; + break; + } + } + } + + // mode up the document until a previous statements delimiter is found or the start of the document is reached + for (let i = cursorLine - 1; i >= editorContext.firstLine(); i--) { + if (editorContext.getLine(i) === statementDelimiter) { + statementStartLine = i + 1; + break; + } + } + + let query = editorContext.getRange( + { line: statementStartLine, ch: 0 }, + { line: statementEndLine, ch: 0 } + ); + + console.log(query); + + _clearQueryMarks(); + + queryMark = editorContext.markText( + { line: statementStartLine, ch: 0 }, + { line: statementEndLine, ch: 0 }, + { className: "selected-statement" } + ); + + editorContext.setGutterMarker(statementStartLine, "statement-pointer", _createGutterMarkerDom()); + + return query; + } +} + +function _createGutterMarkerDom() { + return $("
>
").get(0); +} + +function _clearQueryMarks() { + if (queryMark) { + queryMark.clear(); + } + editorContext.clearGutter("statement-pointer"); +} + +ipcRenderer.on("queryExecutor.runQueryComplete", (event, response) => { + _stopExecTimer(); + if (response.error === undefined) { + handleResult(response.result); + } + else { + handleError(response.error); + } + +}); + +function _startExecTimer() { + execStartTime = new Date; + execElapsedTime = 0; + execTimerInterval = setInterval(() => { + execElapsedTime = Date.now() - execStartTime; + $("#execution-time").text("exec time: " + execElapsedTime + "ms"); + }, 10); +} + +function _stopExecTimer() { + clearInterval(execTimerInterval); + execStartTime = null; +} + +function handleError(err) { + _stopExecTimer(); + _destroyDataTable(); + $("#result-error").removeAttr("style").text("Error (" + err.code + ") - " + err.message); + _setExecutionStatusIndicator("ERROR"); + $("#execution-time").text("failed after " + execElapsedTime + " ms"); +} + +function handleResult(results) { + _stopExecTimer(); + _clearErrors(); + _destroyDataTable(); + + dataTable = $("#result-table").tabulator({ + height: "100%", + columns: _mapColumnProperties(results), + data: results.rows + }); + + _setExecutionStatusIndicator("OK"); + $("#execution-time").text("returned " + results.rowCount + " rows in " + execElapsedTime + " ms"); +} + +function _mapColumnProperties(results) { + return results.fields.map((column) => { + return { + field: column.name, + title: column.name + }; + }); +} + +function _resultTable() { + return $("#result-table"); +} + +function _setExecutionStatusIndicator(status) { + switch (status) { + case "RUNNING": + $("#execution-status").removeClass().addClass("exec-running").text("Running"); + break; + case "OK": + $("#execution-status").removeClass().addClass("exec-ok").text("Ok"); + break; + case "ERROR": + $("#execution-status").removeClass().addClass("exec-error").text("Error"); + break; + } +} + +function _destroyDataTable() { + if (dataTable) { + _resultTable().tabulator("destroy"); + _resultTable().removeAttr("style").empty(); + dataTable = undefined; + } +} + +function _clearErrors() { + $("#result-error").attr("style", "display:none;").empty(); +} + +function _onKeyUp(event) { + if (event.ctrlKey && event.keyCode == 13) { + runQuery(); + } +} + +$(document).ready(() => { + + // Event handlers + $("#run-query").click(runQuery); + $(document).keyup(_onKeyUp); + + Split([".editor-row", ".results-row"], { + sizes: [50, 50], + direction: "vertical", + gutterSize: 10, + elementStyle: (dimension, size, gutterSize) => { + return { + "flex-basis": "calc(" + size + "% - " + gutterSize + "px" + } + }, + gutterStyle: (dimension, gutterSize) => { + return { + "flex-basis": gutterSize + "px" + } + } + }); + +}) + diff --git a/index.html b/index.html index 0de4296..217c363 100644 --- a/index.html +++ b/index.html @@ -4,37 +4,23 @@ SQL++ - - - - - - + + -
-
- -
- -
-
- -
-
-
-
-
- - +
+
+
+
+ \ No newline at end of file diff --git a/instance-manager.js b/instance-manager.js new file mode 100644 index 0000000..e8b803e --- /dev/null +++ b/instance-manager.js @@ -0,0 +1,29 @@ +"use strict"; + +const { ipcRenderer } = require('electron'); +const TabGroup = require("electron-tabs"); + +let tabGroup = new TabGroup(); +let tab = tabGroup.addTab({ + title: "Electron", + 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.openDevTools(); + // }) + // } + // } +}); + +let tab2 = tabGroup.addTab({ + title: "Electron", + src: 'file://' + __dirname + '/editor-instance.html', + visible: true, + active: true, + webviewAttributes: { "nodeintegration": true }, +}); \ No newline at end of file diff --git a/main.js b/main.js index cc89b11..6c19d99 100644 --- a/main.js +++ b/main.js @@ -25,7 +25,7 @@ function createMainWindow() { function createQueryExecutorProcess() { queryExecutorProcess = new BrowserWindow({ - show: false + show: true }); queryExecutorProcess.loadURL(url.format({ @@ -57,8 +57,18 @@ app.on("activate", () => { } }); -ipcMain.on("queryExecutor.runQueryComplete", (event, payload) => uiWindow.webContents.send("queryExecutor.runQueryComplete", payload)); +const { webContents } = require('electron'); + +ipcMain.on("queryExecutor.runQueryComplete", (event, payload) => { + webContents.getAllWebContents().forEach((w) => { + w.send("queryExecutor.runQueryComplete", payload); + }) +}); ipcMain.on("queryExecutor.runQuery", (event, payload) => queryExecutorProcess.webContents.send("queryExecutor.runQuery", payload)); -ipcMain.on("queryExecutor.queryTableMetadataComplete", (event, payload) => uiWindow.webContents.send("queryExecutor.queryTableMetadataComplete", payload)); +ipcMain.on("queryExecutor.queryTableMetadataComplete", (event, payload) => { + webContents.getAllWebContents().forEach((w) => { + w.send("queryExecutor.queryTableMetadataComplete", payload); + }) +}); ipcMain.on("queryExecutor.queryTableMetadata", (event, payload) => queryExecutorProcess.webContents.send("queryExecutor.queryTableMetadata", payload)); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 04cec7a..7d59e86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -318,6 +318,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..f0ab1d3 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "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" } } diff --git a/renderer.js b/renderer.js deleted file mode 100644 index 9fe1be7..0000000 --- a/renderer.js +++ /dev/null @@ -1,245 +0,0 @@ -"use strict"; - -const { ipcRenderer } = require('electron'); -const $ = window.jQuery = require("jquery"); -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") -const Split = require("split.js"); - -const editorContext = cm(document.getElementById("editor"), { - value: "select *\nfrom information_schema.tables\n/\nselect now()\n/\nselect *\nfrom foo", - mode: "text/x-sql", - theme: "dracula", - lineNumbers: true, - gutters: ["CodeMirror-linenumbers", "statement-pointer"], - extraKeys: { "Ctrl-Space": "autocomplete" } -}); - -editorContext.on("cursorActivity", (instance) => { - let coords = instance.getCursor(); - $("#cursor-coords").text("Ln " + (parseInt(coords.line) + 1) + ", Col " + (parseInt(coords.ch) + 1)); -}); - -ipcRenderer.send("queryExecutor.queryTableMetadata"); -ipcRenderer.on("queryExecutor.queryTableMetadataComplete", (event, response) => { - console.log(response); - cm.commands.autocomplete = function (cmInstance) { - cm.showHint(cmInstance, cm.hint.sql, { - tables: response.result - }); - } -}); - -const statementDelimiter = "/"; - -let dataTable; -let execStartTime; -let execTimerInterval; -let execElapsedTime; -let queryMark; - -function runQuery() { - - _setExecutionStatusIndicator("RUNNING"); - _startExecTimer(); - - let query = findQuery(); - - ipcRenderer.send("queryExecutor.runQuery", query); -} - -/** - * If there's selected text, return it. Otherwise find the statement nearest the cursor. - * Statements are delimited by lines containing only a "/" character. - */ -function findQuery() { - let selectedText = editorContext.getSelection(); - - if (selectedText !== "") { - _clearQueryMarks(); - return selectedText; - } - else { - let cursorLine = editorContext.getCursor().line; - - let statementStartLine = editorContext.firstLine(); - - // lineCount rather than lastLine here, since lineCount is index 1 based. - // getRange(from, to) below is 0 based, but the range is exclusive, so if we need to include the last line we need the +1 - let statementEndLine = editorContext.lineCount(); - - // if the current line is a delimiter, thats the end of the statement - if (editorContext.getLine(cursorLine) === statementDelimiter) { - statementEndLine = cursorLine; - } - else { - // move down the document until a delimiter or the end of the document is reached - for (let i = cursorLine + 1; i <= editorContext.lastLine(); i++) { - if (editorContext.getLine(i) === statementDelimiter) { - statementEndLine = i; - break; - } - } - } - - // mode up the document until a previous statements delimiter is found or the start of the document is reached - for (let i = cursorLine - 1; i >= editorContext.firstLine(); i--) { - if (editorContext.getLine(i) === statementDelimiter) { - statementStartLine = i + 1; - break; - } - } - - let query = editorContext.getRange( - { line: statementStartLine, ch: 0 }, - { line: statementEndLine, ch: 0 } - ); - - console.log(query); - - _clearQueryMarks(); - - queryMark = editorContext.markText( - { line: statementStartLine, ch: 0 }, - { line: statementEndLine, ch: 0 }, - { className: "selected-statement" } - ); - - editorContext.setGutterMarker(statementStartLine, "statement-pointer", _createGutterMarkerDom()); - - return query; - } -} - -function _createGutterMarkerDom() { - return $("
>
").get(0); -} - -function _clearQueryMarks() { - if (queryMark) { - queryMark.clear(); - } - editorContext.clearGutter("statement-pointer"); -} - -ipcRenderer.on("queryExecutor.runQueryComplete", (event, response) => { - _stopExecTimer(); - if (response.error === undefined) { - handleResult(response.result); - } - else { - handleError(response.error); - } - -}); - -function _startExecTimer() { - execStartTime = new Date; - execElapsedTime = 0; - execTimerInterval = setInterval(() => { - execElapsedTime = Date.now() - execStartTime; - $("#execution-time").text("exec time: " + execElapsedTime + "ms"); - }, 10); -} - -function _stopExecTimer() { - clearInterval(execTimerInterval); - execStartTime = null; -} - -function handleError(err) { - _stopExecTimer(); - _destroyDataTable(); - $("#result-error").removeAttr("style").text("Error (" + err.code + ") - " + err.message); - _setExecutionStatusIndicator("ERROR"); - $("#execution-time").text("failed after " + execElapsedTime + " ms"); -} - -function handleResult(results) { - _stopExecTimer(); - _clearErrors(); - _destroyDataTable(); - - dataTable = $("#result-table").tabulator({ - height: "100%", - columns: _mapColumnProperties(results), - data: results.rows - }); - - _setExecutionStatusIndicator("OK"); - $("#execution-time").text("returned " + results.rowCount + " rows in " + execElapsedTime + " ms"); -} - -function _mapColumnProperties(results) { - return results.fields.map((column) => { - return { - field: column.name, - title: column.name - }; - }); -} - -function _resultTable() { - return $("#result-table"); -} - -function _setExecutionStatusIndicator(status) { - switch (status) { - case "RUNNING": - $("#execution-status").removeClass().addClass("exec-running").text("Running"); - break; - case "OK": - $("#execution-status").removeClass().addClass("exec-ok").text("Ok"); - break; - case "ERROR": - $("#execution-status").removeClass().addClass("exec-error").text("Error"); - break; - } -} - -function _destroyDataTable() { - if(dataTable) { - _resultTable().tabulator("destroy"); - _resultTable().removeAttr("style").empty(); - dataTable = undefined; - } -} - -function _clearErrors() { - $("#result-error").attr("style", "display:none;").empty(); -} - -function _onKeyUp(event) { - if (event.ctrlKey && event.keyCode == 13) { - runQuery(); - } -} - -$(document).ready(() => { - - // Event handlers - $("#run-query").click(runQuery); - $(document).keyup(_onKeyUp); - - Split([".editor-row", ".results-row"], { - sizes: [50, 50], - direction: "vertical", - gutterSize: 10, - elementStyle: (dimension, size, gutterSize) => { - return { - "flex-basis": "calc(" + size + "% - " + gutterSize + "px" - } - }, - gutterStyle: (dimension, gutterSize) => { - return { - "flex-basis": gutterSize + "px" - } - } - }); - -}) - -- cgit v1.2.3 From 0c0fdc4e61d07d8883c382a4dd9d1afaa1874c33 Mon Sep 17 00:00:00 2001 From: James Barnett Date: Sat, 17 Feb 2018 18:29:47 +0000 Subject: Add instance IDs to allow IPC message routing --- editor-instance.js | 38 ++++++++++++++++++++++++++------------ main.js | 2 ++ package.json | 3 ++- query-executor.js | 8 +++++--- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/editor-instance.js b/editor-instance.js index 3ec123a..cb26db7 100644 --- a/editor-instance.js +++ b/editor-instance.js @@ -6,10 +6,13 @@ 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')(); +console.log("instanceId=" + editorInstanceId); + 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 +27,7 @@ editorContext.on("cursorActivity", (instance) => { $("#cursor-coords").text("Ln " + (parseInt(coords.line) + 1) + ", Col " + (parseInt(coords.ch) + 1)); }); -ipcRenderer.send("queryExecutor.queryTableMetadata"); +ipcRenderer.send("queryExecutor.queryTableMetadata", _generateIpcPayload()); ipcRenderer.on("queryExecutor.queryTableMetadataComplete", (event, response) => { console.log(response); cm.commands.autocomplete = function (cmInstance) { @@ -47,9 +50,10 @@ 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 +130,25 @@ 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 } +} +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() { diff --git a/main.js b/main.js index 6c19d99..6cb76c8 100644 --- a/main.js +++ b/main.js @@ -59,6 +59,8 @@ app.on("activate", () => { 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); diff --git a/package.json b/package.json index f0ab1d3..5f05487 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "split.js": "1.3.5", "jquery-ui": "1.12.1", "jquery.tabulator": "3.4.2", - "electron-tabs": "0.9.0" + "electron-tabs": "0.9.0", + "uuid": "3.2.1" } } diff --git a/query-executor.js b/query-executor.js index 7bd836d..871da9e 100644 --- a/query-executor.js +++ b/query-executor.js @@ -13,13 +13,14 @@ const connectionPool = new Pool({ ipcRenderer.on("queryExecutor.runQuery", (event, payload) => { - connectionPool.query(payload, (err, res) => { + connectionPool.query(payload.query, (err, res) => { console.log(err, res) ipcRenderer.send("queryExecutor.runQueryComplete", { "error": err, - "result": res + "result": res, + "editorInstanceId": payload.editorInstanceId }); }); @@ -50,7 +51,8 @@ ipcRenderer.on("queryExecutor.queryTableMetadata", (event, payload) => { ipcRenderer.send("queryExecutor.queryTableMetadataComplete", { "error": err, - "result": tableMetadata + "result": tableMetadata, + "editorInstanceId": payload.editorInstanceId }); }); -- cgit v1.2.3 From 52f6783f63d2e2f52e06d14a97b5e00eab8ac1c0 Mon Sep 17 00:00:00 2001 From: James Barnett Date: Sun, 18 Feb 2018 21:20:37 +0000 Subject: Add new connection dialog box - WIP --- editor-instance.html | 2 +- index.html | 11 ++-- instance-manager.js | 18 +++++- main.js | 24 +++++++- new-connection.html | 62 ++++++++++++++++++++ package-lock.json | 6 ++ package.json | 3 +- styles/editor-instance.css | 134 ++++++++++++++++++++++++++++++++++++++++++++ styles/instance-manager.css | 28 +++++++++ styles/new-connection.css | 3 + styles/style.css | 133 ------------------------------------------- 11 files changed, 281 insertions(+), 143 deletions(-) create mode 100644 new-connection.html create mode 100644 styles/editor-instance.css create mode 100644 styles/instance-manager.css create mode 100644 styles/new-connection.css delete mode 100644 styles/style.css diff --git a/editor-instance.html b/editor-instance.html index 1983bfe..454c6ce 100644 --- a/editor-instance.html +++ b/editor-instance.html @@ -9,7 +9,7 @@ - + diff --git a/index.html b/index.html index 217c363..4f0d500 100644 --- a/index.html +++ b/index.html @@ -5,17 +5,16 @@ SQL++ - + +
-
+
+ +
diff --git a/instance-manager.js b/instance-manager.js index e8b803e..115f359 100644 --- a/instance-manager.js +++ b/instance-manager.js @@ -2,6 +2,7 @@ const { ipcRenderer } = require('electron'); const TabGroup = require("electron-tabs"); +const $ = window.jQuery = require("jquery"); let tabGroup = new TabGroup(); let tab = tabGroup.addTab({ @@ -26,4 +27,19 @@ let tab2 = tabGroup.addTab({ visible: true, active: true, webviewAttributes: { "nodeintegration": true }, -}); \ No newline at end of file +}); + +function createNewConnection() { + ipcRenderer.send("instanceManager.openNewConnectionDialog"); +} + +ipcRenderer.on("instanceManager.newConnectionCallback", (event, response) => { + console.log(response); +}); + + +$(document).ready(() => { + $("#new-connection").click(() => { + createNewConnection(); + }) +}) \ No newline at end of file diff --git a/main.js b/main.js index 6cb76c8..88c25b9 100644 --- a/main.js +++ b/main.js @@ -5,6 +5,7 @@ const url = require("url"); let uiWindow; let queryExecutorProcess; +let newConnectionDialog; function createMainWindow() { uiWindow = new BrowserWindow({ @@ -25,7 +26,7 @@ function createMainWindow() { function createQueryExecutorProcess() { queryExecutorProcess = new BrowserWindow({ - show: true + show: false }); queryExecutorProcess.loadURL(url.format({ @@ -57,6 +58,27 @@ app.on("activate", () => { } }); +function createNewConnectionDialog() { + newConnectionDialog = new BrowserWindow({ + width: 400, + height: 470 + }); + 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(); +}); + + const { webContents } = require('electron'); // TODO - only send messages to instance manager which will route request to correct webView, rather than diff --git a/new-connection.html b/new-connection.html new file mode 100644 index 0000000..dace021 --- /dev/null +++ b/new-connection.html @@ -0,0 +1,62 @@ + + + + + SQL++ - New connection + + + + + +

Add new connection

+ +
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7d59e86..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", diff --git a/package.json b/package.json index 5f05487..5dd96cb 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "jquery-ui": "1.12.1", "jquery.tabulator": "3.4.2", "electron-tabs": "0.9.0", - "uuid": "3.2.1" + "uuid": "3.2.1", + "bulma": "0.6.2" } } diff --git a/styles/editor-instance.css b/styles/editor-instance.css new file mode 100644 index 0000000..99ac763 --- /dev/null +++ b/styles/editor-instance.css @@ -0,0 +1,134 @@ +html, +body { + height: 100%; + margin: 0 +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: #474A5E; +} + +::-webkit-scrollbar-thumb { + background: #63677C; +} + +::-webkit-scrollbar-thumb:hover { + background: #555; +} +::-webkit-scrollbar-corner { + background: #63677C +} + +.flex-wrapper { + display: flex; + flex-flow: column; + height: 100%; +} + +.flex-wrapper .row.header { + flex: 0 1 auto; + /* background-color: #474A5E; */ + background: #6D8A88; + color: white; +} + +.flex-wrapper .row.content { + flex: 1 1 auto; + display: flex; + flex-direction: column; + height: 100%; +} + +.flex-wrapper .row.footer { + flex: 0 1 auto; + background-color: #474A5E; + color: white; + display: flex; + flex-direction: row; + font-family: monospace; + align-items: center; + height: 20px; +} + +.flex-wrapper .row .split-row { + overflow: auto; +} + +.gutter { + background-color: #474A5E; + background-repeat: no-repeat; + background-position: 50%; +} + +.gutter.gutter-vertical { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII='); + cursor: ns-resize; +} + +.gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); + cursor: ew-resize; +} + +.CodeMirror { + height: 100%; +} + +.results-row { + color: #ffffff; + background-color: #282a36; + font-family: monospace; +} + +#result-error { + padding: 5px; +} + +#execution-status { + padding: 2px; + flex-basis: 60px; + text-align: center; + font-weight: bold; +} + +#execution-time { + /* background-color: green; */ + flex-basis: auto; + padding: 2px 10px; +} + +#cursor-coords { + margin-left: auto; + padding-right: 10px; +} + +.exec-idle { + background-color: gray; +} + +.exec-running { + background-color: yellow; + color: black; +} + +.exec-ok { + background-color: green; +} + +.exec-error { + background-color: red; + color: black; +} + +.selected-statement { + background-color: #4F4837; +} + +.statement-pointer { + width: 10px; +} \ No newline at end of file diff --git a/styles/instance-manager.css b/styles/instance-manager.css new file mode 100644 index 0000000..8449b9c --- /dev/null +++ b/styles/instance-manager.css @@ -0,0 +1,28 @@ +body { + margin: 0; +} + +.etabs-tabgroup { + background-color: #282a36; +} + +.etabs-tab { + color: white; + background: none; + background-color: #474A5E; + border: none; +} + +.etabs-tab.active { + background: #6D8A88; +} + +.etabs-views { + border: none; +} + +.etabs-buttons button { + width: inherit; + padding: 4px 6px; + background: #6D8A88; +} \ 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 diff --git a/styles/style.css b/styles/style.css deleted file mode 100644 index 7ae5727..0000000 --- a/styles/style.css +++ /dev/null @@ -1,133 +0,0 @@ -html, -body { - height: 100%; - margin: 0 -} - -::-webkit-scrollbar { - width: 10px; - height: 10px; -} - -::-webkit-scrollbar-track { - background: #474A5E; -} - -::-webkit-scrollbar-thumb { - background: #63677C; -} - -::-webkit-scrollbar-thumb:hover { - background: #555; -} -::-webkit-scrollbar-corner { - background: #63677C -} - -.flex-wrapper { - display: flex; - flex-flow: column; - height: 100%; -} - -.flex-wrapper .row.header { - flex: 0 1 auto; - background-color: #474A5E; - color: white; -} - -.flex-wrapper .row.content { - flex: 1 1 auto; - display: flex; - flex-direction: column; - height: 100%; -} - -.flex-wrapper .row.footer { - flex: 0 1 auto; - background-color: #474A5E; - color: white; - display: flex; - flex-direction: row; - font-family: monospace; - align-items: center; - height: 20px; -} - -.flex-wrapper .row .split-row { - overflow: auto; -} - -.gutter { - background-color: #474A5E; - background-repeat: no-repeat; - background-position: 50%; -} - -.gutter.gutter-vertical { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII='); - cursor: ns-resize; -} - -.gutter.gutter-horizontal { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); - cursor: ew-resize; -} - -.CodeMirror { - height: 100%; -} - -.results-row { - color: #ffffff; - background-color: #282a36; - font-family: monospace; -} - -#result-error { - padding: 5px; -} - -#execution-status { - padding: 2px; - flex-basis: 60px; - text-align: center; - font-weight: bold; -} - -#execution-time { - /* background-color: green; */ - flex-basis: auto; - padding: 2px 10px; -} - -#cursor-coords { - margin-left: auto; - padding-right: 10px; -} - -.exec-idle { - background-color: gray; -} - -.exec-running { - background-color: yellow; - color: black; -} - -.exec-ok { - background-color: green; -} - -.exec-error { - background-color: red; - color: black; -} - -.selected-statement { - background-color: #4F4837; -} - -.statement-pointer { - width: 10px; -} \ No newline at end of file -- cgit v1.2.3 From b81c0f834f7f2285c40cfd57eb2943140025edad Mon Sep 17 00:00:00 2001 From: James Barnett Date: Tue, 20 Feb 2018 21:44:28 +0000 Subject: Dynamically create query executors - WIP --- editor-instance.js | 29 ++++++++++------- instance-manager.js | 47 +++++++++++++--------------- main.js | 64 +++++++++++++++++++++++++------------- new-connection.html | 89 +++++++++++++++++++++++++++-------------------------- new-connection.js | 46 +++++++++++++++++++++++++++ query-executor.js | 41 ++++++++++++++---------- 6 files changed, 199 insertions(+), 117 deletions(-) create mode 100644 new-connection.js diff --git a/editor-instance.js b/editor-instance.js index cb26db7..008705f 100644 --- a/editor-instance.js +++ b/editor-instance.js @@ -11,7 +11,8 @@ require("codemirror/addon/hint/sql-hint.js"); const Split = require("split.js"); const editorInstanceId = require('uuid/v1')(); -console.log("instanceId=" + editorInstanceId); + +let queryExecutorId; const editorContext = cm(document.getElementById("editor"), { value: "select *\nfrom information_schema.tables\n/\nselect now()\n/\nselect *\nfrom foo", @@ -27,7 +28,20 @@ editorContext.on("cursorActivity", (instance) => { $("#cursor-coords").text("Ln " + (parseInt(coords.line) + 1) + ", Col " + (parseInt(coords.ch) + 1)); }); -ipcRenderer.send("queryExecutor.queryTableMetadata", _generateIpcPayload()); +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) { @@ -37,14 +51,6 @@ ipcRenderer.on("queryExecutor.queryTableMetadataComplete", (event, response) => } }); -const statementDelimiter = "/"; - -let dataTable; -let execStartTime; -let execTimerInterval; -let execElapsedTime; -let queryMark; - function runQuery() { _setExecutionStatusIndicator("RUNNING"); @@ -132,7 +138,8 @@ function _clearQueryMarks() { function _generateIpcPayload() { return { - editorInstanceId: editorInstanceId + editorInstanceId: editorInstanceId, + queryExecutorId: queryExecutorId } } diff --git a/instance-manager.js b/instance-manager.js index 115f359..6a4d019 100644 --- a/instance-manager.js +++ b/instance-manager.js @@ -4,37 +4,32 @@ const { ipcRenderer } = require('electron'); const TabGroup = require("electron-tabs"); const $ = window.jQuery = require("jquery"); -let tabGroup = new TabGroup(); -let tab = tabGroup.addTab({ - title: "Electron", - 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.openDevTools(); - // }) - // } - // } -}); - -let tab2 = tabGroup.addTab({ - title: "Electron", - src: 'file://' + __dirname + '/editor-instance.html', - visible: true, - active: true, - webviewAttributes: { "nodeintegration": true }, -}); +const tabGroup = new TabGroup(); function createNewConnection() { ipcRenderer.send("instanceManager.openNewConnectionDialog"); } -ipcRenderer.on("instanceManager.newConnectionCallback", (event, response) => { - console.log(response); +function registerNewInstance(assignedQueryExecutorId) { + tabGroup.addTab({ + title: "Electron", + 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", assignedQueryExecutorId); + }) + } + } + }); +} + +ipcRenderer.on("instanceManager.registerNewInstance", (event, assignedQueryExecutorId) => { + registerNewInstance(assignedQueryExecutorId); }); diff --git a/main.js b/main.js index 88c25b9..2639034 100644 --- a/main.js +++ b/main.js @@ -4,8 +4,8 @@ const path = require("path"); const url = require("url"); let uiWindow; -let queryExecutorProcess; let newConnectionDialog; +let queryExecutors = []; function createMainWindow() { uiWindow = new BrowserWindow({ @@ -24,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", () => { @@ -78,6 +60,36 @@ 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, payload) => { + // TODO - handle connection initialisation errors + + uiWindow.webContents.send("instanceManager.registerNewInstance", payload); + newConnectionDialog.close(); +}); + const { webContents } = require('electron'); @@ -88,11 +100,21 @@ ipcMain.on("queryExecutor.runQueryComplete", (event, payload) => { w.send("queryExecutor.runQueryComplete", payload); }) }); -ipcMain.on("queryExecutor.runQuery", (event, payload) => queryExecutorProcess.webContents.send("queryExecutor.runQuery", payload)); + +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) => queryExecutorProcess.webContents.send("queryExecutor.queryTableMetadata", payload)); \ No newline at end of file +; +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 index dace021..18d4ad9 100644 --- a/new-connection.html +++ b/new-connection.html @@ -9,54 +9,57 @@

Add new connection

- -
- -
-
- -
-
-
-
- -
- -
-
-
- -
- +
+
+ +
+
+ +
+
-
-
- -
- +
+ +
+ +
-
-
- -
- +
+ +
+ +
-
-
-
- +
+ +
+ +
-
- +
+ +
+ +
-
- +
+
+ +
+
+ +
+
+ +
-
+ - + \ 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/query-executor.js b/query-executor.js index 871da9e..0c5b440 100644 --- a/query-executor.js +++ b/query-executor.js @@ -1,30 +1,39 @@ "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 }); -ipcRenderer.on("queryExecutor.runQuery", (event, payload) => { - - connectionPool.query(payload.query, (err, res) => { +// Initialisation completed +ipcRenderer.send("queryExecutor.initialiseConnectionCallback", executorId); - console.log(err, res) +ipcRenderer.on("queryExecutor.runQuery", (event, payload) => { - ipcRenderer.send("queryExecutor.runQueryComplete", { - "error": err, - "result": res, - "editorInstanceId": payload.editorInstanceId + if(payload.queryExecutorId === executorId) { + connectionPool.query(payload.query, (err, res) => { + + console.log(err, res) + + ipcRenderer.send("queryExecutor.runQueryComplete", { + "error": err, + "result": res, + "editorInstanceId": payload.editorInstanceId + }); + }); - - }); - + } + }); ipcRenderer.on("queryExecutor.queryTableMetadata", (event, payload) => { -- cgit v1.2.3 From 4ef675b2b7803c72ed6e74ee13841e356a71f103 Mon Sep 17 00:00:00 2001 From: James Barnett Date: Wed, 21 Feb 2018 21:07:01 +0000 Subject: Make connection tab title configurable --- index.html | 1 + instance-manager.js | 10 +++++----- main.js | 10 +++++++--- new-connection.html | 7 +++++++ styles/editor-instance.css | 3 +-- styles/instance-manager.css | 7 +++++++ 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/index.html b/index.html index 4f0d500..2d8dbee 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,7 @@
+