Browse Source

Merge pull request #834 from arkpar/mix_web

mix: Web preview and ethereum.js integration
cl-refactor
Gav Wood 10 years ago
parent
commit
c89d828308
  1. 15
      mix/CMakeLists.txt
  2. 33
      mix/ClientModel.cpp
  3. 21
      mix/ClientModel.h
  4. 12
      mix/CodeModel.cpp
  5. 6
      mix/CodeModel.h
  6. 5
      mix/FileIo.cpp
  7. 8
      mix/MixApplication.cpp
  8. 6
      mix/MixClient.cpp
  9. 1
      mix/MixClient.h
  10. 4
      mix/main.cpp
  11. 5
      mix/noweb.qrc
  12. 1
      mix/qml.qrc
  13. 33
      mix/qml/MainContent.qml
  14. 10
      mix/qml/ProjectModel.qml
  15. 6
      mix/qml/StateList.qml
  16. 207
      mix/qml/WebPreview.qml
  17. 15
      mix/qml/WebPreviewStub.qml
  18. 43
      mix/qml/html/WebContainer.html
  19. 43
      mix/qml/js/ProjectModel.js
  20. 14
      mix/qml/main.qml
  21. 6
      mix/web.qrc

15
mix/CMakeLists.txt

@ -12,12 +12,20 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
aux_source_directory(. SRC_LIST)
include_directories(..)
find_package (Qt5WebEngine)
qt5_add_resources(UI_RESOURCES qml.qrc)
file(GLOB HEADERS "*.h")
set(EXECUTABLE mix)
if ("${Qt5WebEngine_VERSION_STRING}" VERSION_GREATER "5.3.0")
set (ETH_HAVE_WEBENGINE TRUE)
qt5_add_resources(UI_RESOURCES web.qrc)
else()
qt5_add_resources(UI_RESOURCES noweb.qrc)
endif()
# eth_add_executable is defined in cmake/EthExecutableHelper.cmake
eth_add_executable(${EXECUTABLE}
ICON mix
@ -38,8 +46,13 @@ target_link_libraries(${EXECUTABLE} lll)
target_link_libraries(${EXECUTABLE} solidity)
target_link_libraries(${EXECUTABLE} evmcore)
target_link_libraries(${EXECUTABLE} devcore)
target_link_libraries(${EXECUTABLE} web3jsonrpc)
target_link_libraries(${EXECUTABLE} jsqrc)
target_link_libraries(${EXECUTABLE} web3jsonrpc)
if (${ETH_HAVE_WEBENGINE})
add_definitions(-DETH_HAVE_WEBENGINE)
target_link_libraries(${EXECUTABLE} Qt5::WebEngine)
endif()
# eth_install_executable is defined in cmake/EthExecutableHelper.cmake
eth_install_executable(${EXECUTABLE}

33
mix/ClientModel.cpp

@ -24,7 +24,7 @@
#include <QQmlApplicationEngine>
#include <libdevcore/CommonJS.h>
#include <libethereum/Transaction.h>
#include "ClientModel.h"
#include <libqwebthree/QWebThree.h>
#include "AppContext.h"
#include "DebuggingStateWrapper.h"
#include "QContractDefinition.h"
@ -33,13 +33,15 @@
#include "CodeModel.h"
#include "ClientModel.h"
#include "QEther.h"
#include "Web3Server.h"
#include "ClientModel.h"
using namespace dev;
using namespace dev::eth;
using namespace dev::mix;
ClientModel::ClientModel(AppContext* _context):
m_context(_context), m_running(false)
m_context(_context), m_running(false), m_qWebThree(nullptr)
{
qRegisterMetaType<QBigInt*>("QBigInt*");
qRegisterMetaType<QEther*>("QEther*");
@ -53,9 +55,30 @@ ClientModel::ClientModel(AppContext* _context):
connect(this, &ClientModel::dataAvailable, this, &ClientModel::showDebugger, Qt::QueuedConnection);
m_client.reset(new MixClient());
m_qWebThree = new QWebThree(this);
m_qWebThreeConnector.reset(new QWebThreeConnector());
m_qWebThreeConnector->setQWeb(m_qWebThree);
m_web3Server.reset(new Web3Server(*m_qWebThreeConnector.get(), std::vector<dev::KeyPair> { m_client->userAccount() }, m_client.get()));
connect(m_qWebThree, &QWebThree::response, this, &ClientModel::apiResponse);
_context->appEngine()->rootContext()->setContextProperty("clientModel", this);
}
ClientModel::~ClientModel()
{
}
void ClientModel::apiRequest(const QString& _message)
{
m_qWebThree->postMessage(_message);
}
QString ClientModel::contractAddress() const
{
QString address = QString::fromStdString(dev::toJS(m_client->lastContractAddress()));
return address;
}
void ClientModel::debugDeployment()
{
executeSequence(std::vector<TransactionSettings>(), 10000000 * ether);
@ -198,9 +221,11 @@ ExecutionResult ClientModel::deployContract(bytes const& _code)
u256 gas = 125000;
u256 amount = 100;
Address contractAddress = m_client->transact(m_client->userAccount().secret(), amount, _code, gas, gasPrice);
Address lastAddress = m_client->lastContractAddress();
Address newAddress = m_client->transact(m_client->userAccount().secret(), amount, _code, gas, gasPrice);
ExecutionResult r = m_client->lastExecutionResult();
r.contractAddress = contractAddress;
if (newAddress != lastAddress)
contractAddressChanged();
return r;
}

21
mix/ClientModel.h

@ -32,12 +32,16 @@ using AssemblyDebuggerData = std::tuple<QList<QObject*>, dev::mix::QQMLMap*>;
Q_DECLARE_METATYPE(AssemblyDebuggerData)
Q_DECLARE_METATYPE(dev::mix::ExecutionResult)
class QWebThree;
class QWebThreeConnector;
namespace dev
{
namespace mix
{
class AppContext;
class Web3Server;
/// Backend transaction config class
struct TransactionSettings
@ -67,10 +71,16 @@ class ClientModel: public QObject
public:
ClientModel(AppContext* _context);
~ClientModel();
/// @returns true if currently executing contract code
Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged)
/// @returns address of the last executed contract
Q_PROPERTY(QString contractAddress READ contractAddress NOTIFY contractAddressChanged)
public slots:
/// ethereum.js RPC request entry point
/// @param _message RPC request in Json format
void apiRequest(QString const& _message);
/// Run the contract constructor and show debugger window.
void debugDeployment();
/// Setup state, run transaction sequence, show debugger for the last transaction
@ -91,15 +101,21 @@ signals:
/// Transaction execution completed with error
/// @param _message Error message
void runFailed(QString const& _message);
/// Contract address changed
void contractAddressChanged();
/// Execution state changed
void stateChanged();
/// Show debugger window request
void showDebuggerWindow();
/// ethereum.js RPC response ready
/// @param _message RPC response in Json format
void apiResponse(QString const& _message);
/// Emited when machine states are available.
void dataAvailable(QList<QVariableDefinition*> const& _returnParams = QList<QVariableDefinition*>(), QList<QObject*> const& _wStates = QList<QObject*>(), AssemblyDebuggerData const& _code = AssemblyDebuggerData());
private:
QString contractAddress() const;
void executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance);
ExecutionResult deployContract(bytes const& _code);
ExecutionResult callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
@ -107,6 +123,9 @@ private:
AppContext* m_context;
std::atomic<bool> m_running;
std::unique_ptr<MixClient> m_client;
QWebThree* m_qWebThree;
std::unique_ptr<QWebThreeConnector> m_qWebThreeConnector;
std::unique_ptr<Web3Server> m_web3Server;
};
}

12
mix/CodeModel.cpp

@ -26,6 +26,7 @@
#include <QtQml>
#include <libsolidity/CompilerStack.h>
#include <libsolidity/SourceReferenceFormatter.h>
#include <libsolidity/InterfaceHandler.h>
#include <libevmcore/Instruction.h>
#include "QContractDefinition.h"
#include "QFunctionDefinition.h"
@ -55,9 +56,12 @@ CompilationResult::CompilationResult(const dev::solidity::CompilerStack& _compil
{
if (!_compiler.getContractNames().empty())
{
m_contract.reset(new QContractDefinition(&_compiler.getContractDefinition(std::string())));
auto const& contractDefinition = _compiler.getContractDefinition(std::string());
m_contract.reset(new QContractDefinition(&contractDefinition));
m_bytes = _compiler.getBytecode();
m_assemblyCode = QString::fromStdString(dev::eth::disassemble(m_bytes));
dev::solidity::InterfaceHandler interfaceHandler;
m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition));
}
else
m_contract.reset(new QContractDefinition());
@ -71,6 +75,7 @@ CompilationResult::CompilationResult(CompilationResult const& _prev, QString con
m_compilerMessage(_compilerMessage),
m_bytes(_prev.m_bytes),
m_assemblyCode(_prev.m_assemblyCode),
m_contractInterface(_prev.m_contractInterface),
m_codeHighlighter(_prev.m_codeHighlighter)
{}
@ -159,11 +164,16 @@ void CodeModel::runCompilationJob(int _jobId, QString const& _code)
void CodeModel::onCompilationComplete(CompilationResult* _newResult)
{
m_compiling = false;
bool contractChanged = m_result->contractInterface() != _newResult->contractInterface();
m_result.reset(_newResult);
emit compilationComplete();
emit stateChanged();
if (m_result->successful())
{
emit codeChanged();
if (contractChanged)
emit contractInterfaceChanged();
}
}
bool CodeModel::hasContract() const

6
mix/CodeModel.h

@ -67,6 +67,7 @@ class CompilationResult: public QObject
Q_PROPERTY(QContractDefinition* contract READ contract)
Q_PROPERTY(QString compilerMessage READ compilerMessage CONSTANT)
Q_PROPERTY(bool successful READ successful CONSTANT)
Q_PROPERTY(QString contractInterface READ contractInterface CONSTANT)
public:
/// Empty compilation result constructor
@ -88,6 +89,8 @@ public:
dev::bytes const& bytes() const { return m_bytes; }
/// @returns contract bytecode in human-readable form
QString assemblyCode() const { return m_assemblyCode; }
/// @returns contract definition in JSON format
QString contractInterface() const { return m_contractInterface; }
/// Get code highlighter
std::shared_ptr<CodeHighlighter> codeHighlighter() { return m_codeHighlighter; }
@ -98,6 +101,7 @@ private:
QString m_compilerMessage; ///< @todo: use some structure here
dev::bytes m_bytes;
QString m_assemblyCode;
QString m_contractInterface;
std::shared_ptr<CodeHighlighter> m_codeHighlighter;
friend class CodeModel;
@ -137,6 +141,8 @@ signals:
void scheduleCompilationJob(int _jobId, QString const& _content);
/// Emitted if there are any changes in the code model
void codeChanged();
/// Emitted if there are any changes in the contract interface
void contractInterfaceChanged();
/// Emitted on compilation complete. Internal
void compilationCompleteInternal(CompilationResult* _newResult);

5
mix/FileIo.cpp

@ -40,7 +40,10 @@ void FileIo::makeDir(QString const& _url)
QString FileIo::readFile(QString const& _url)
{
QUrl url(_url);
QFile file(url.path());
QString path(url.path());
if (url.scheme() == "qrc")
path = ":" + path;
QFile file(path);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream stream(&file);

8
mix/MixApplication.cpp

@ -21,6 +21,11 @@
#include <QDebug>
#include <QQmlApplicationEngine>
#ifdef ETH_HAVE_WEBENGINE
#include <QtWebEngine/QtWebEngine>
#endif
#include "MixApplication.h"
#include "AppContext.h"
@ -31,6 +36,9 @@ using namespace dev::mix;
MixApplication::MixApplication(int _argc, char* _argv[]):
QApplication(_argc, _argv), m_engine(new QQmlApplicationEngine()), m_appContext(new AppContext(m_engine.get()))
{
#ifdef ETH_HAVE_WEBENGINE
QtWebEngine::initialize();
#endif
QObject::connect(this, SIGNAL(lastWindowClosed()), context(), SLOT(quitApplication())); //use to kill ApplicationContext and other stuff
m_appContext->load();
}

6
mix/MixClient.cpp

@ -88,6 +88,7 @@ void MixClient::executeTransaction(bytesConstRef _rlp, State& _state)
d.executionData = data;
d.contentAvailable = true;
d.message = "ok";
d.contractAddress = m_lastExecutionResult.contractAddress;
m_lastExecutionResult = d;
}
@ -113,7 +114,9 @@ Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init,
eth::Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret);
bytes rlp = t.rlp();
executeTransaction(&rlp, m_state);
return right160(sha3(rlpList(t.sender(), t.nonce())));
Address address = right160(sha3(rlpList(t.sender(), t.nonce())));
m_lastExecutionResult.contractAddress = address;
return address;
}
void MixClient::inject(bytesConstRef _rlp)
@ -128,7 +131,6 @@ void MixClient::flushTransactions()
bytes MixClient::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice)
{
bytes out;
u256 n;
State temp;
{

1
mix/MixClient.h

@ -70,6 +70,7 @@ public:
void resetState(u256 _balance);
const KeyPair& userAccount() const { return m_userAccount; }
const ExecutionResult lastExecutionResult() const { ReadGuard l(x_state); return m_lastExecutionResult; }
const Address lastContractAddress() const { ReadGuard l(x_state); return m_lastExecutionResult.contractAddress; }
//dev::eth::Interface
void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override;

4
mix/main.cpp

@ -28,12 +28,14 @@ using namespace dev::mix;
int main(int _argc, char* _argv[])
{
#ifdef ETH_HAVE_WEBENGINE
Q_INIT_RESOURCE(js);
#endif
#if __linux
//work around ubuntu appmenu-qt5 bug
//https://bugs.launchpad.net/ubuntu/+source/appmenu-qt5/+bug/1323853
putenv((char*)"QT_QPA_PLATFORMTHEME=");
#endif
try
{
MixApplication app(_argc, _argv);

5
mix/noweb.qrc

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file alias="qml/WebPreview.qml">qml/WebPreviewStub.qml</file>
</qresource>
</RCC>

1
mix/qml.qrc

@ -39,7 +39,6 @@
<file>qml/CodeEditor.qml</file>
<file>qml/CodeEditorView.qml</file>
<file>qml/js/ProjectModel.js</file>
<file>qml/WebPreview.qml</file>
<file>qml/Ether.qml</file>
<file>qml/EtherValue.qml</file>
<file>qml/BigIntValue.qml</file>

33
mix/qml/MainContent.qml

@ -18,6 +18,9 @@ Rectangle {
anchors.fill: parent
id: root
property alias rightViewVisible : rightView.visible
property alias webViewVisible : webPreview.visible
onWidthChanged:
{
if (rightView.visible)
@ -26,28 +29,28 @@ Rectangle {
contentView.width = parent.width - projectList.width;
}
function toggleRightView()
{
function toggleRightView() {
if (!rightView.visible)
rightView.show();
else
rightView.hide();
}
function ensureRightView()
{
function ensureRightView() {
if (!rightView.visible)
rightView.show();
}
function hideRightView()
{
function hideRightView() {
if (rightView.visible)
rightView.hide();
}
function rightViewVisible()
{
function toggleWebPreview() {
webPreview.visible = !webPreview.visible;
}
function rightViewVisible() {
return rightView.visible;
}
@ -126,10 +129,20 @@ Rectangle {
id: contentView
width: parent.width - projectList.width
height: parent.height
SplitView {
anchors.fill: parent
orientation: Qt.Vertical
CodeEditorView {
height: parent.height
height: parent.height * 0.6
anchors.top: parent.top
width: parent.width
Layout.fillWidth: true
Layout.fillHeight: true
}
WebPreview {
id: webPreview
height: parent.height * 0.4
Layout.fillWidth: true
}
}
}

10
mix/qml/ProjectModel.qml

@ -11,18 +11,21 @@ Item {
id: projectModel
signal projectClosed
signal projectLoaded
signal projectLoaded(var projectData)
signal documentOpened(var document)
signal documentRemoved(var documentId)
signal documentUpdated(var documentId) //renamed
signal documentAdded(var documentId)
signal projectSaving(var projectData)
signal projectSaved()
signal documentSaved(var documentId)
property bool isEmpty: (projectData === null)
property bool isEmpty: (projectPath === "")
readonly property string projectFileName: ".mix"
property bool haveUnsavedChanges: false
property string projectPath: ""
property var projectData: null
property string projectTitle: ""
property var listModel: projectListModel
//interface
@ -39,6 +42,7 @@ Item {
function openDocument(documentId) { ProjectModelCode.openDocument(documentId); }
function renameDocument(documentId, newName) { ProjectModelCode.renameDocument(documentId, newName); }
function removeDocument(documentId) { ProjectModelCode.removeDocument(documentId); }
function getDocument(documentId) { return ProjectModelCode.getDocument(documentId); }
Connections {
target: appContext

6
mix/qml/StateList.qml

@ -21,9 +21,9 @@ Rectangle {
stateListModel.clear();
}
onProjectLoaded: {
if (!target.projectData.states)
target.projectData.states = [];
var items = target.projectData.states;
if (!projectData.states)
projectData.states = [];
var items = projectData.states;
for(var i = 0; i < items.length; i++) {
stateListModel.append(items[i]);
stateList.push(items[i])

207
mix/qml/WebPreview.qml

@ -3,79 +3,166 @@ import QtQuick.Window 2.0
import QtQuick.Layouts 1.0
import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.1
import QtWebEngine 1.0
import Qt.WebSockets 1.0
Component {
Item {
signal editorTextChanged
id: webPreview
property string pendingPageUrl: ""
property bool initialized: false
function setText(text) {
codeEditor.text = text;
function setPreviewUrl(url) {
if (!initialized)
pendingPageUrl = url;
else {
pendingPageUrl = "";
updateContract();
webView.runJavaScript("loadPage(\"" + url + "\")");
}
}
function getText() {
return codeEditor.text;
function reload() {
updateContract();
webView.runJavaScript("reloadPage()");
}
anchors.fill: parent
id: contentView
width: parent.width
height: parent.height * 0.7
Rectangle {
id: lineColumn
property int rowHeight: codeEditor.font.pixelSize + 3
color: "#202020"
width: 50
height: parent.height
Column {
y: -codeEditor.flickableItem.contentY + 4
width: parent.width
Repeater {
model: Math.max(codeEditor.lineCount + 2, (lineColumn.height/lineColumn.rowHeight))
delegate: Text {
id: text
color: codeEditor.textColor
font: codeEditor.font
width: lineColumn.width - 4
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
height: lineColumn.rowHeight
renderType: Text.NativeRendering
text: index + 1
}
}
}
}
TextArea {
id: codeEditor
textColor: "#EEE8D5"
style: TextAreaStyle {
backgroundColor: "#002B36"
}
anchors.left: lineColumn.right
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
wrapMode: TextEdit.NoWrap
frameVisible: false
function updateContract() {
webView.runJavaScript("updateContract(\"" + clientModel.contractAddress + "\", " + codeModel.code.contractInterface + ")");
}
function reloadOnSave() {
if (autoReloadOnSave.checked)
reload();
}
function updateDocument(documentId, action) {
for (var i = 0; i < pageListModel.count; i++)
if (pageListModel.get(i).documentId === i)
action(i);
}
function changePage() {
if (pageCombo.currentIndex >=0 && pageCombo.currentIndex < pageListModel.count) {
setPreviewUrl(pageListModel.get(pageCombo.currentIndex).path);
} else {
setPreviewUrl("");
}
}
Connections {
target: appContext
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:///")
height: parent.height
font.family: "Monospace"
font.pointSize: 12
width: parent.width
}
}
Connections {
target: clientModel
onContractAddressChanged: reload();
}
Connections {
target: codeModel
onContractInterfaceChanged: reload();
}
Connections {
target: projectModel
onProjectSaved : reloadOnSave();
onDocumentSaved: reloadOnSave();
onDocumentAdded: {
var document = projectModel.getDocument(documentId)
if (document.isHtml)
pageListModel.append(document);
}
onDocumentRemoved: {
updateDocument(documentId, function(i) { pageListModel.remove(i) } )
}
onDocumentUpdated: {
updateDocument(documentId, function(i) { pageListModel.set(i, projectModel.getDocument(documentId)) } )
}
tabChangesFocus: false
Keys.onPressed: {
if (event.key === Qt.Key_Tab) {
codeEditor.insert(codeEditor.cursorPosition, "\t");
event.accepted = true;
onProjectLoaded: {
for (var i = 0; i < target.listModel.count; i++) {
var document = target.listModel.get(i);
if (document.isHtml) {
pageListModel.append(document);
if (pageListModel.count === 1) //first page added
changePage();
}
}
onTextChanged: {
editorTextChanged();
}
onProjectClosed: {
pageListModel.clear();
}
}
ListModel {
id: pageListModel
}
WebSocketServer {
id: socketServer
listen: true
name: "mix"
onClientConnected:
{
webSocket.onTextMessageReceived.connect(function(message) {
console.log("rpc_request: " + message);
clientModel.apiRequest(message);
});
clientModel.onApiResponse.connect(function(message) {
console.log("rpc_response: " + message);
webSocket.sendTextMessage(message);
});
}
}
ColumnLayout {
anchors.fill: parent
RowLayout {
anchors.top: parent.top
Layout.fillWidth: true;
Text {
text: qsTr("Page")
}
ComboBox {
id: pageCombo
model: pageListModel
textRole: "name"
currentIndex: -1
onCurrentIndexChanged: changePage()
}
Button {
text: qsTr("Reload")
onClicked: reload()
}
CheckBox {
id: autoReloadOnSave
checked: true
text: qsTr("Auto reload on save")
}
}
WebEngineView {
Layout.fillWidth: true
Layout.fillHeight: true
id: webView
onJavaScriptConsoleMessage: {
console.log(sourceID + ":" + lineNumber + ":" + message);
}
onLoadingChanged: {
if (!loading) {
initialized = true;
webView.runJavaScript("init(\"" + socketServer.url + "\")");
if (pendingPageUrl)
setPreviewUrl(pendingPageUrl);
}
}
}
}
}

15
mix/qml/WebPreviewStub.qml

@ -0,0 +1,15 @@
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Layouts 1.0
import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.1
Item {
id: webPreview
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: qsTr("Web preview is not supported in this build");
}
}

43
mix/qml/html/WebContainer.html

@ -0,0 +1,43 @@
<!doctype html>
<html>
<head>
<script src="qrc:///js/es6-promise-2.0.0.js"></script>
<script src="qrc:///js/bignumber.min.js"></script>
<script src="qrc:///js/webthree.js"></script>
<script>
loadPage = function(url) {
var preview = document.getElementById('preview');
preview.src = url;
};
reloadPage = function() {
var preview = document.getElementById('preview');
preview.contentWindow.location.reload();
};
updateContract = function(address, definition) {
if (window.web3) {
window.contractAddress = address;
window.contractDefinition = definition;
window.contract = window.web3.eth.contract(address, definition);
}
};
init = function(url) {
web3 = require('web3');
web3.setProvider(new web3.providers.WebSocketProvider(url));
window.web3 = web3;
};
</script>
<style>
body {
margin: 0;
}
</style>
</head>
<body><iframe src="" name="preview" id="preview" style="border: 0; width: 100%; height: 100%"></iframe></body>
</html>

43
mix/qml/js/ProjectModel.js

@ -43,10 +43,14 @@ function closeProject() {
function saveProject() {
if (!isEmpty) {
var projectData = { files: [] };
for (var i = 0; i < projectListModel.count; i++)
projectData.files.push(projectListModel.get(i).fileName)
projectSaving(projectData);
var json = JSON.stringify(projectData);
var projectFile = projectPath + projectFileName;
fileIo.writeFile(projectFile, json);
projectSaved();
}
}
@ -55,11 +59,12 @@ function loadProject(path) {
console.log("loading project at " + path);
var projectFile = path + projectFileName;
var json = fileIo.readFile(projectFile);
projectData = JSON.parse(json);
var projectData = JSON.parse(json);
if (!projectData.title) {
var parts = path.split("/");
projectData.title = parts[parts.length - 2];
}
projectTitle = projectData.title;
projectPath = path;
if (!projectData.files)
projectData.files = [];
@ -68,32 +73,31 @@ function loadProject(path) {
addFile(projectData.files[i]);
}
projectSettings.lastProjectPath = path;
projectLoaded();
projectLoaded(projectData);
}
function addExistingFile() {
addExistingFileDialog.open();
}
function addProjectFiles(files) {
for(var i = 0; i < files.length; i++)
addFile(files[i]);
}
function addFile(fileName) {
var p = projectPath + fileName;
var extension = fileName.substring(fileName.length - 4, fileName.length);
var extension = fileName.substring(fileName.lastIndexOf("."), fileName.length);
var isContract = extension === ".sol";
var fileData = {
var isHtml = extension === ".html";
var docData = {
contract: false,
path: p,
fileName: fileName,
name: isContract ? "Contract" : fileName,
documentId: fileName,
isText: isContract || extension === ".html" || extension === ".js",
isText: isContract || isHtml || extension === ".js",
isContract: isContract,
isHtml: isHtml,
};
projectListModel.append(fileData);
projectListModel.append(docData);
return docData.documentId;
}
function findDocument(documentId)
@ -113,7 +117,6 @@ function doCloseProject() {
console.log("closing project");
projectListModel.clear();
projectPath = "";
projectData = null;
projectClosed();
}
@ -143,14 +146,12 @@ function doCreateProject(title, path) {
function doAddExistingFiles(files) {
for(var i = 0; i < files.length; i++) {
var sourcePath = files[i];
console.log(sourcePath);
var sourceFileName = sourcePath.substring(sourcePath.lastIndexOf("/") + 1, sourcePath.length);
console.log(sourceFileName);
var destPath = projectPath + sourceFileName;
console.log(destPath);
if (sourcePath !== destPath)
fileIo.copyFile(sourcePath, destPath);
addFile(sourceFileName);
var id = addFile(sourceFileName);
documentAdded(id)
}
}
@ -168,12 +169,17 @@ function renameDocument(documentId, newName) {
}
}
function getDocument(documentId) {
var i = findDocument(documentId);
return projectListModel.get(i);
}
function removeDocument(documentId) {
var i = findDocument(documentId);
var document = projectListModel.get(i);
if (!document.isContract) {
projectListModel.remove(i);
documentUpdated(documentId);
documentRemoved(documentId);
}
}
@ -189,7 +195,8 @@ function createAndAddFile(name, extension, content) {
var fileName = generateFileName(name, extension);
var filePath = projectPath + fileName;
fileIo.writeFile(filePath, content);
addFile(fileName);
var id = addFile(fileName);
documentAdded(id);
}
function generateFileName(name, extension) {

14
mix/qml/main.qml

@ -40,6 +40,7 @@ ApplicationWindow {
Menu {
title: qsTr("Windows")
MenuItem { action: showHideRightPanel }
MenuItem { action: toggleWebPreview }
}
}
@ -88,10 +89,21 @@ ApplicationWindow {
onTriggered: clientModel.resetState();
}
Action {
id: toggleWebPreview
text: "Show Web View"
shortcut: "F2"
checkable: true
checked: mainContent.webViewVisible
onTriggered: mainContent.toggleWebPreview();
}
Action {
id: showHideRightPanel
text: "Show/Hide right view"
text: "Show Right View"
shortcut: "F7"
checkable: true
checked: mainContent.rightViewVisible
onTriggered: mainContent.toggleRightView();
}

6
mix/web.qrc

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file>qml/WebPreview.qml</file>
<file>qml/html/WebContainer.html</file>
</qresource>
</RCC>
Loading…
Cancel
Save