diff --git a/mix/FileIo.cpp b/mix/FileIo.cpp index 52fd57902..818d8c887 100644 --- a/mix/FileIo.cpp +++ b/mix/FileIo.cpp @@ -70,6 +70,12 @@ void FileIo::writeFile(QString const& _url, QString const& _data) void FileIo::copyFile(QString const& _sourceUrl, QString const& _destUrl) { + if (QUrl(_sourceUrl).scheme() == "qrc") + { + writeFile(_destUrl, readFile(_sourceUrl)); + return; + } + QUrl sourceUrl(_sourceUrl); QUrl destUrl(_destUrl); if (!QFile::copy(sourceUrl.path(), destUrl.path())) diff --git a/mix/HttpServer.cpp b/mix/HttpServer.cpp index cfe5c37f4..bf210444b 100644 --- a/mix/HttpServer.cpp +++ b/mix/HttpServer.cpp @@ -132,23 +132,25 @@ void HttpServer::readClient() if (socket->canReadLine()) { QString hdr = QString(socket->readLine()); - if (hdr.startsWith("POST")) + if (hdr.startsWith("POST") || hdr.startsWith("GET")) { + QUrl url(hdr.split(' ')[1]); QString l; do l = socket->readLine(); while (!(l.isEmpty() || l == "\r" || l == "\r\n")); QString content = socket->readAll(); - QUrl url; std::unique_ptr request(new HttpRequest(this, url, content)); clientConnected(request.get()); QTextStream os(socket); os.setAutoDetectUnicode(true); + QString q; ///@todo: allow setting response content-type, charset, etc - os << "HTTP/1.0 200 Ok\r\n" - "Content-Type: text/plain; charset=\"utf-8\"\r\n" - "\r\n"; + os << "HTTP/1.0 200 Ok\r\n"; + if (!request->m_responseContentType.isEmpty()) + os << "Content-Type: " << request->m_responseContentType << "; "; + os << "charset=\"utf-8\"\r\n\r\n"; os << request->m_response; } } diff --git a/mix/HttpServer.h b/mix/HttpServer.h index 00d63a073..add83238b 100644 --- a/mix/HttpServer.h +++ b/mix/HttpServer.h @@ -51,11 +51,15 @@ public: /// Set response for a request /// @param _response Response body. If no response is set, server returns status 200 with empty body Q_INVOKABLE void setResponse(QString const& _response) { m_response = _response; } + /// Set response content type + /// @param _contentType Response content type string. text/plain by default + Q_INVOKABLE void setResponseContentType(QString const& _contentType) { m_responseContentType = _contentType ; } private: QUrl m_url; QString m_content; QString m_response; + QString m_responseContentType; friend class HttpServer; }; diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml index 36fc586b3..4d54994fe 100644 --- a/mix/qml/CodeEditorView.qml +++ b/mix/qml/CodeEditorView.qml @@ -4,18 +4,26 @@ import QtQuick.Layouts 1.0 import QtQuick.Controls 1.0 Item { - + id: codeEditorView property string currentDocumentId: "" + signal documentEdit(string documentId) function getDocumentText(documentId) { - for (i = 0; i < editorListModel.count; i++) { + for (var i = 0; i < editorListModel.count; i++) { if (editorListModel.get(i).documentId === documentId) { - return editors.itemAt(i).getText(); + return editors.itemAt(i).item.getText(); } } return ""; } + function isDocumentOpen(documentId) { + for (var i = 0; i < editorListModel.count; i++) + if (editorListModel.get(i).documentId === documentId) + return true; + return false; + } + function openDocument(document) { loadDocument(document); currentDocumentId = document.documentId; @@ -31,13 +39,16 @@ Item { function doLoadDocument(editor, document) { var data = fileIo.readFile(document.path); - if (document.isContract) - editor.onEditorTextChanged.connect(function() { + editor.onEditorTextChanged.connect(function() { + documentEdit(document.documentId); + if (document.isContract) codeModel.registerCodeChange(editor.getText()); - }); + }); editor.setText(data, document.syntaxMode); } + Component.onCompleted: projectModel.codeEditor = codeEditorView; + Connections { target: projectModel onDocumentOpened: { diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index 98ed0b02e..7e65f3174 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -34,6 +34,7 @@ Item { property string deploymentAddress: "" property var listModel: projectListModel property var stateListModel: projectStateListModel.model + property CodeEditorView codeEditor: null //interface function saveAll() { ProjectModelCode.saveAll(); } @@ -53,7 +54,7 @@ Item { function getDocument(documentId) { return ProjectModelCode.getDocument(documentId); } function getDocumentIndex(documentId) { return ProjectModelCode.getDocumentIndex(documentId); } function addExistingFiles(paths) { ProjectModelCode.doAddExistingFiles(paths); } - function deployProject() { ProjectModelCode.deployProject(); } + function deployProject() { ProjectModelCode.deployProject(false); } Connections { target: appContext @@ -88,6 +89,18 @@ Item { } } + MessageDialog { + id: deployWarningDialog + title: qsTr("Project") + text: qsTr("This project has been already deployed to the network. Do you want to re-deploy it?") + standardButtons: StandardButton.Ok | StandardButton.Cancel + icon: StandardIcon.Question + onAccepted: { + ProjectModelCode.deployProject(true); + } + } + + ListModel { id: projectListModel } diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index f4ddca84e..a952d6345 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -43,8 +43,8 @@ Item { } function changePage() { - if (pageCombo.currentIndex >=0 && pageCombo.currentIndex < pageListModel.count) { - setPreviewUrl(pageListModel.get(pageCombo.currentIndex).path); + if (pageCombo.currentIndex >= 0 && pageCombo.currentIndex < pageListModel.count) { + setPreviewUrl(httpServer.url + "/" + pageListModel.get(pageCombo.currentIndex).documentId); } else { setPreviewUrl(""); } @@ -54,7 +54,7 @@ Item { onAppLoaded: { //We need to load the container using file scheme so that web security would allow loading local files in iframe var containerPage = fileIo.readFile("qrc:///qml/html/WebContainer.html"); - webView.loadHtml(containerPage, "file:///WebContainer.html") + webView.loadHtml(containerPage, httpServer.url + "/WebContainer.html") } } @@ -112,16 +112,35 @@ Item { accept: true port: 8893 onClientConnected: { - //filter polling spam - //TODO: do it properly - //var log = _request.content.indexOf("eth_changed") < 0; - var log = true; - if (log) - console.log(_request.content); - var response = clientModel.apiCall(_request.content); - if (log) - console.log(response); - _request.setResponse(response); + var urlPath = _request.url.toString(); + if (urlPath.indexOf("/rpc/") === 0) + { + //jsonrpc request + //filter polling requests //TODO: do it properly + var log = _request.content.indexOf("eth_changed") < 0; + if (log) + console.log(_request.content); + var response = clientModel.apiCall(_request.content); + if (log) + console.log(response); + _request.setResponse(response); + } + else + { + //document request + var documentId = urlPath.substr(urlPath.lastIndexOf("/") + 1); + var content = ""; + if (projectModel.codeEditor.isDocumentOpen(documentId)) + content = projectModel.codeEditor.getDocumentText(documentId); + else + content = fileIo.readFile(projectModel.getDocument(documentId).path); + if (documentId === pageListModel.get(pageCombo.currentIndex).documentId) { + //root page, inject deployment script + content = "\n" + content; + _request.setResponseContentType("text/html"); + } + _request.setResponse(content); + } } } @@ -163,7 +182,7 @@ Item { onLoadingChanged: { if (!loading) { initialized = true; - webView.runJavaScript("init(\"" + httpServer.url + "\")"); + webView.runJavaScript("init(\"" + httpServer.url + "/rpc/\")"); if (pendingPageUrl) setPreviewUrl(pendingPageUrl); } diff --git a/mix/qml/html/WebContainer.html b/mix/qml/html/WebContainer.html index 04ba8ab73..e48668041 100644 --- a/mix/qml/html/WebContainer.html +++ b/mix/qml/html/WebContainer.html @@ -21,13 +21,18 @@ updateContract = function(address, contractFace) { window.contractAddress = address; window.contractInterface = contractFace; window.contract = window.web3.eth.contract(address, contractFace); + window.deploy = { + contractAddress: address, + contractInterface: contractFace, + contract: window.contract + }; } }; init = function(url) { web3 = require('web3'); - web3.setProvider(new web3.providers.HttpSyncProvider(url)); window.web3 = web3; + web3.setProvider(new web3.providers.HttpSyncProvider(url)); }; diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index dcea66b13..fb6872bd4 100644 --- a/mix/qml/js/ProjectModel.js +++ b/mix/qml/js/ProjectModel.js @@ -253,11 +253,17 @@ function generateFileName(name, extension) { var jsonRpcRequestId = 1; -function deployProject() { +function deployProject(force) { saveAll(); //TODO: ask user - var deploymentId = Date.now().toLocaleString("ddMMyyyHHmmsszzz"); + if (!force && deploymentAddress !== "") { + deployWarningDialog.visible = true; + return; + } + + var date = new Date(); + var deploymentId = date.toLocaleString(Qt.locale(), "ddMMyyHHmmsszzz"); var jsonRpcUrl = "http://localhost:8080"; console.log("Deploying " + deploymentId + " to " + jsonRpcUrl); deploymentStarted(); @@ -283,7 +289,7 @@ function deployProject() { console.log("Created contract, address: " + address); finalizeDeployment(deploymentId, address); } else { - var errorText = qsTr("Deployment error: RPC server HTTP status ") + http.status; + var errorText = qsTr("Deployment error: RPC server HTTP status ") + httpRequest.status; console.log(errorText); deploymentError(errorText); } @@ -324,16 +330,17 @@ function finalizeDeployment(deploymentId, address) { "// Autogenerated by Mix\n" + "var web3 = require(\"web3\");\n" + "var contractInterface = " + codeModel.code.contractInterface + ";\n" + - "deployment = {\n" + + "deploy = {\n" + "\tweb3: web3,\n" + "\tcontractAddress: \"" + address + "\",\n" + "\tcontractInterface: contractInterface,\n" + "};\n" + - "deplyment.contract = web3.eth.contract(deplyment.contractAddress, deployment.contractInterface);\n"; + "deploy.contract = web3.eth.contract(deploy.contractAddress, deploy.contractInterface);\n"; fileIo.writeFile(deploymentDir + "deployment.js", deploymentJs); //copy scripts fileIo.copyFile("qrc:///js/bignumber.min.js", deploymentDir + "bignumber.min.js"); fileIo.copyFile("qrc:///js/webthree.js", deploymentDir + "ethereum.js"); + deploymentAddress = address; saveProject(); deploymentComplete(); }