From 530b176d09b955f46cb85eebf76dad7e8a6c0630 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 11 Jan 2017 16:36:18 +1300 Subject: [PATCH 01/16] Replace Clara download button with own --- scripts/system/html/js/marketplacesInject.js | 52 +++++--------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 8dad85a66d..0fbb7d8bbb 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -111,61 +111,35 @@ element.setAttribute("href", href + parameters); } - // Replace download options with a single, "Download to High Fidelity" option. + // Remove unwanted buttons and replace download options with a single "Download to High Fidelity" button. var buttons = $("a.embed-button").parent("div"); - if (buttons.length > 0) { - var downloadFBX = buttons.find("a[data-extension=\'fbx\']")[0]; - downloadFBX.addEventListener("click", startAutoDownload); - var firstButton = buttons.children(":first-child")[0]; - buttons[0].insertBefore(downloadFBX, firstButton); - downloadFBX.setAttribute("class", "btn btn-primary download"); - downloadFBX.innerHTML = " Download to High Fidelity"; - buttons.children(":nth-child(2), .btn-group , .embed-button").each(function () { this.remove(); }); + var downloadFBX; + if (buttons.find("div.btn-group").length > 0) { + buttons.children(".btn-primary, .btn-group , .embed-button").each(function () { this.remove(); }); + if ($("#hifi-download-container").length === 0) { // Button hasn't been moved already. + downloadFBX = $(' Download to High Fidelity'); + buttons.prepend(downloadFBX); + downloadFBX[0].addEventListener("click", startAutoDownload); + } } // Move the "Download to High Fidelity" button to be more visible on tablet. if ($("#hifi-download-container").length === 0 && window.innerWidth < 700) { - // Moving the button stops the Clara.io download from starting so instead, make a visual copy in the right place - // and wire its click event to click the original button. var downloadContainer = $('
'); $(".top-title .col-sm-4").append(downloadContainer); - var downloadButton = $("a[data-extension=\'fbx\']").clone(); - downloadButton[0].addEventListener("click", function () { downloadFBX.click(); }); - downloadContainer.append(downloadButton); - downloadFBX.style.visibility = "hidden"; + downloadContainer.append(downloadFBX); } // Automatic download to High Fidelity. - var downloadTimer; - function startAutoDownload(event) { + function startAutoDownload() { if (!canWriteAssets) { console.log("Clara.io FBX file download cancelled because no permissions to write to Asset Server"); EventBridge.emitWebEvent(WARN_USER_NO_PERMISSIONS); event.stopPropagation(); + return; } - window.scrollTo(0, 0); // Scroll to top ready for history.back(). - if (!downloadTimer) { - downloadTimer = setInterval(autoDownload, 1000); - } - } - function autoDownload() { - if ($("div.download-body").length !== 0) { - var downloadButton = $("div.download-body a.download-file"); - if (downloadButton.length > 0) { - clearInterval(downloadTimer); - downloadTimer = null; - var href = downloadButton[0].href; - EventBridge.emitWebEvent(CLARA_IO_DOWNLOAD + " " + href); - console.log("Clara.io FBX file download initiated for " + href); - $("a.btn.cancel").click(); - history.back(); // Remove history item created by clicking "download". - }; - } else if ($("div#view-signup_login_dialog").length === 0) { - // Don't stop checking for button if user is asked to log in. - clearInterval(downloadTimer); - downloadTimer = null; - } + // TODO: Initiate download using Clara.io API. } } } From 71d6a5cb1d77dddfd3aa60488e30d592aa687515 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 11 Jan 2017 21:33:03 +1300 Subject: [PATCH 02/16] Implement model request and download --- scripts/system/html/js/marketplacesInject.js | 59 +++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 0fbb7d8bbb..2db250c52a 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -133,13 +133,68 @@ // Automatic download to High Fidelity. function startAutoDownload() { if (!canWriteAssets) { - console.log("Clara.io FBX file download cancelled because no permissions to write to Asset Server"); + console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server"); EventBridge.emitWebEvent(WARN_USER_NO_PERMISSIONS); event.stopPropagation(); return; } - // TODO: Initiate download using Clara.io API. + // Obtain zip file to download for requested asset. + // Reference: https://clara.io/learn/sdk/api/export + var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGound=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; + var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1]; + var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid); + + var xmlHttpRequest = new XMLHttpRequest(); + var responseTextIndex = 0; + var statusMessage = ""; + var zipFileURL = ""; + + xmlHttpRequest.onreadystatechange = function () { + // Messages are appended to responseText; process the new one. + var message = this.responseText.slice(responseTextIndex); + + if (message.slice(0, 5) === "data:") { + var data = JSON.parse(message.slice(5)); + + // Extract status message. + if (data.hasOwnProperty("message") && data.message !== null) { + statusMessage = data.message; + console.log("Clara.io FBX: " + statusMessage); + } + + // Extract zip file URL. + if (data.hasOwnProperty("files") && data.files.length > 0) { + zipFileURL = data.files[0].url; + } + } + + responseTextIndex = this.responseText.length; + }; + + // Note: onprogress doesn't have computable total length. + + xmlHttpRequest.onload = function () { + var HTTP_OK = 200; + if (this.status !== HTTP_OK) { + console.log("ERROR: Clara.io FBX: Zip file request terminated with " + this.status + " " + + this.statusText); + return; + } + + if (zipFileURL == "") { + console.log("ERROR: Clara.io FBX: Zip file URL not found"); + return; + } + + EventBridge.emitWebEvent(CLARA_IO_DOWNLOAD + " " + zipFileURL); + console.log("Clara.io FBX: File download initiated for " + zipFileURL); + } + + console.log("Clara.io FBX: Request zip file for " + uuid); + xmlHttpRequest.open("POST", url, true); + xmlHttpRequest.setRequestHeader("Accept", "text/event-stream"); + xmlHttpRequest.send(); } } } From 9bdcdf73c16d0ea628395a811e9ffe402e0fdc8e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 Jan 2017 09:15:01 +1300 Subject: [PATCH 03/16] User must be logged into Clara.io site to download --- scripts/system/html/js/marketplacesInject.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 2db250c52a..c5d463875f 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -132,6 +132,8 @@ // Automatic download to High Fidelity. function startAutoDownload() { + + // User must be able to write to Asset Server. if (!canWriteAssets) { console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server"); EventBridge.emitWebEvent(WARN_USER_NO_PERMISSIONS); @@ -139,6 +141,13 @@ return; } + // User must be logged in. + var loginButton = $("#topnav a[href='/signup']"); + if (loginButton.length > 0) { + loginButton[0].click(); + return; + } + // Obtain zip file to download for requested asset. // Reference: https://clara.io/learn/sdk/api/export var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGound=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; From b41f8c754d2742fa3395a9d1c7f43d8d948280a7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 Jan 2017 15:46:53 +1300 Subject: [PATCH 04/16] Add updateable message box to JavaScript API --- .../scripting/WindowScriptingInterface.cpp | 72 +++++++++++++++++++ .../src/scripting/WindowScriptingInterface.h | 16 +++++ 2 files changed, 88 insertions(+) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 765c9a8499..7cfbfb174e 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -60,6 +60,19 @@ WindowScriptingInterface::WindowScriptingInterface() { }); } +WindowScriptingInterface::~WindowScriptingInterface() { + QHashIterator i(_messageBoxes); + while (i.hasNext()) { + i.next(); + auto messageBox = i.value(); + disconnect(messageBox); + messageBox->setVisible(false); + messageBox->deleteLater(); + } + + _messageBoxes.clear(); +} + QScriptValue WindowScriptingInterface::hasFocus() { return qApp->hasFocus(); } @@ -210,3 +223,62 @@ void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& hr bool WindowScriptingInterface::isPhysicsEnabled() { return qApp->isPhysicsEnabled(); } + +int WindowScriptingInterface::openMessageBox(QString title, QString text, int buttons, int defaultButton) { + if (QThread::currentThread() != thread()) { + int result; + QMetaObject::invokeMethod(this, "openMessageBox", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(int, result), + Q_ARG(QString, title), + Q_ARG(QString, text), + Q_ARG(int, buttons), + Q_ARG(int, defaultButton)); + return result; + } + + return createMessageBox(title, text, buttons, defaultButton); +} + +int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) { + auto messageBox = DependencyManager::get()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text, + static_cast>(buttons), static_cast(defaultButton)); + connect(messageBox, SIGNAL(selected(int)), this, SLOT(onMessageBoxSelected(int))); + + _lastMessageBoxID += 1; + _messageBoxes.insert(_lastMessageBoxID, messageBox); + + return _lastMessageBoxID; +} + +void WindowScriptingInterface::updateMessageBox(int id, QString title, QString text, int buttons, int defaultButton) { + auto messageBox = _messageBoxes.value(id); + if (messageBox) { + messageBox->setProperty("title", title); + messageBox->setProperty("text", text); + messageBox->setProperty("buttons", buttons); + messageBox->setProperty("defaultButton", defaultButton); + } +} + +void WindowScriptingInterface::closeMessageBox(int id) { + auto messageBox = _messageBoxes.value(id); + if (messageBox) { + disconnect(messageBox); + messageBox->setVisible(false); + messageBox->deleteLater(); + _messageBoxes.remove(id); + } +} + +void WindowScriptingInterface::onMessageBoxSelected(int button) { + auto messageBox = qobject_cast(sender()); + auto keys = _messageBoxes.keys(messageBox); + if (keys.length() > 0) { + auto id = keys[0]; // Should be just one message box. + emit messageBoxClosed(id, button); + disconnect(messageBox); + messageBox->setVisible(false); + messageBox->deleteLater(); + _messageBoxes.remove(id); + } +} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 6aa8707496..4652e00661 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -14,7 +14,9 @@ #include #include +#include #include +#include class CustomPromptResult { public: @@ -35,6 +37,7 @@ class WindowScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int y READ getY) public: WindowScriptingInterface(); + ~WindowScriptingInterface(); int getInnerWidth(); int getInnerHeight(); int getX(); @@ -56,6 +59,13 @@ public slots: void shareSnapshot(const QString& path, const QUrl& href = QUrl("")); bool isPhysicsEnabled(); + int openMessageBox(QString title, QString text, int buttons, int defaultButton); + void updateMessageBox(int id, QString title, QString text, int buttons, int defaultButton); + void closeMessageBox(int id); + +private slots: + void onMessageBoxSelected(int button); + signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); @@ -64,9 +74,15 @@ signals: void snapshotShared(const QString& error); void processingGif(); + void messageBoxClosed(int id, int button); + private: QString getPreviousBrowseLocation() const; void setPreviousBrowseLocation(const QString& location); + + int createMessageBox(QString title, QString text, int buttons, int defaultButton); + QHash _messageBoxes; + int _lastMessageBoxID{ -1 }; }; #endif // hifi_WindowScriptingInterface_h From 37da800a1440e294faa8612875e8cee78b73f59d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 Jan 2017 15:47:41 +1300 Subject: [PATCH 05/16] Display Clara model preparation progress --- scripts/system/html/js/marketplacesInject.js | 35 +++++++++++-- scripts/system/marketplaces/marketplaces.js | 54 +++++++++++++++++++- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index c5d463875f..d70575184c 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -14,12 +14,16 @@ // Event bridge messages. var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; + var CLARA_IO_STATUS = "CLARA.IO STATUS"; + var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; + var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; var GOTO_DIRECTORY = "GOTO_DIRECTORY"; var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; var canWriteAssets = false; + var xmlHttpRequest = null; function injectCommonCode(isDirectoryPage) { @@ -154,7 +158,7 @@ var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1]; var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid); - var xmlHttpRequest = new XMLHttpRequest(); + xmlHttpRequest = new XMLHttpRequest(); var responseTextIndex = 0; var statusMessage = ""; var zipFileURL = ""; @@ -170,6 +174,7 @@ if (data.hasOwnProperty("message") && data.message !== null) { statusMessage = data.message; console.log("Clara.io FBX: " + statusMessage); + EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); } // Extract zip file URL. @@ -186,21 +191,28 @@ xmlHttpRequest.onload = function () { var HTTP_OK = 200; if (this.status !== HTTP_OK) { - console.log("ERROR: Clara.io FBX: Zip file request terminated with " + this.status + " " - + this.statusText); + statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText; + console.log("ERROR: Clara.io FBX: " + statusMessage); + EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); return; } - if (zipFileURL == "") { - console.log("ERROR: Clara.io FBX: Zip file URL not found"); + if (zipFileURL === "") { + statusMessage = "Download file URL not provided"; + console.log("ERROR: Clara.io FBX: " + statusMessage); + EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); return; } EventBridge.emitWebEvent(CLARA_IO_DOWNLOAD + " " + zipFileURL); console.log("Clara.io FBX: File download initiated for " + zipFileURL); + + xmlHttpRequest = null; } console.log("Clara.io FBX: Request zip file for " + uuid); + EventBridge.emitWebEvent(CLARA_IO_STATUS + " Initiating download"); + xmlHttpRequest.open("POST", url, true); xmlHttpRequest.setRequestHeader("Accept", "text/event-stream"); xmlHttpRequest.send(); @@ -248,12 +260,25 @@ EventBridge.emitWebEvent(QUERY_CAN_WRITE_ASSETS); } + function cancelClaraDownload() { + if (xmlHttpRequest) { + xmlHttpRequest.abort(); + xmlHttpRequest = null; + console.log("Clara.io FBX: File download cancelled"); + EventBridge.emitWebEvent(CLARA_IO_CANCELLED_DOWNLOAD); + } + } + function onLoad() { EventBridge.scriptEventReceived.connect(function (message) { if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) { canWriteAssets = message.slice(-4) === "true"; } + + if (message.slice(0, CLARA_IO_CANCEL_DOWNLOAD.length) === CLARA_IO_CANCEL_DOWNLOAD) { + cancelClaraDownload(); + } }); var DIRECTORY = 0; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index c173683c74..53aef34600 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -15,17 +15,29 @@ Script.include("../libraries/WebTablet.js"); var toolIconUrl = Script.resolvePath("../assets/images/tools/"); -var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; +//var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; +var MARKETPLACE_URL = "https://clara.io/library?gameCheck=true&public=true"; var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); // Event bridge messages. var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; +var CLARA_IO_STATUS = "CLARA.IO STATUS"; +var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; +var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; var GOTO_DIRECTORY = "GOTO_DIRECTORY"; var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; + +var CLARA_DOWNLOAD_TITLE = "Preparing Download"; +var messageBox = null; +var isDownloadBeingCancelled = false; + +var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel +var NO_BUTTON = 0; // QMessageBox::NoButton + var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server."; var marketplaceWindow = new OverlayWebWindow({ @@ -36,18 +48,58 @@ var marketplaceWindow = new OverlayWebWindow({ visible: false }); marketplaceWindow.setScriptURL(MARKETPLACES_INJECT_SCRIPT_URL); + marketplaceWindow.webEventReceived.connect(function (message) { if (message === GOTO_DIRECTORY) { marketplaceWindow.setURL(MARKETPLACES_URL); + return; } if (message === QUERY_CAN_WRITE_ASSETS) { marketplaceWindow.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); + return; } if (message === WARN_USER_NO_PERMISSIONS) { Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); + return; + } + + if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) { + if (isDownloadBeingCancelled) { + return; + } + + var text = message.slice(CLARA_IO_STATUS.length); + if (messageBox === null) { + messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); + } else { + Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); + } + return; + } + + if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) { + if (messageBox) { + Window.closeMessageBox(messageBox); + messageBox = null; + } + return; + } + + if (message === CLARA_IO_CANCELLED_DOWNLOAD) { + isDownloadBeingCancelled = false; } }); +function onMessageBoxClosed(id, button) { + if (id === messageBox && button === CANCEL_BUTTON) { + isDownloadBeingCancelled = true; + messageBox = null; + marketplaceWindow.emitScriptEvent(CLARA_IO_CANCEL_DOWNLOAD); + } +} + +Window.messageBoxClosed.connect(onMessageBoxClosed); + var toolHeight = 50; var toolWidth = 50; var TOOLBAR_MARGIN_Y = 0; From 8cd7e1b3771dbab2f3da6ce49b2cfa7eab7bf48c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 Jan 2017 15:48:25 +1300 Subject: [PATCH 06/16] Improve user messages --- interface/src/Application.cpp | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 681d160821..6631111910 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5642,18 +5642,18 @@ void Application::showAssetServerWidget(QString filePath) { } void Application::addAssetToWorldFromURL(QString url) { - qInfo(interfaceapp) << "Download asset and add to world from" << url; + qInfo(interfaceapp) << "Download model and add to world from" << url; QString filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL. if (!DependencyManager::get()->getThisNodeCanWriteAssets()) { QString errorInfo = "You do not have permissions to write to the Asset Server."; - qWarning(interfaceapp) << "Error downloading asset: " + errorInfo; + qWarning(interfaceapp) << "Error downloading model: " + errorInfo; addAssetToWorldError(filename, errorInfo); return; } - addAssetToWorldInfo(filename, "Downloading asset file " + filename + "."); + addAssetToWorldInfo(filename, "Downloading model file " + filename + "."); auto request = ResourceManager::createResourceRequest(nullptr, QUrl(url)); connect(request, &ResourceRequest::finished, this, &Application::addAssetToWorldFromURLRequestFinished); @@ -5668,7 +5668,7 @@ void Application::addAssetToWorldFromURLRequestFinished() { QString filename = url.section("filename=", 1, 1); // Filename from trailing "?filename=" URL parameter. if (result == ResourceRequest::Success) { - qInfo(interfaceapp) << "Downloaded asset from" << url; + qInfo(interfaceapp) << "Downloaded model from" << url; QTemporaryDir temporaryDir; temporaryDir.setAutoRemove(false); if (temporaryDir.isValid()) { @@ -5720,7 +5720,7 @@ void Application::addAssetToWorld(QString filePath) { // Test repeated because possibly different code paths. if (!DependencyManager::get()->getThisNodeCanWriteAssets()) { QString errorInfo = "You do not have permissions to write to the Asset Server."; - qWarning(interfaceapp) << "Error downloading asset: " + errorInfo; + qWarning(interfaceapp) << "Error downloading model: " + errorInfo; addAssetToWorldError(filename, errorInfo); return; } @@ -5741,7 +5741,7 @@ void Application::addAssetToWorldWithNewMapping(QString filePath, QString mappin } else if (result != GetMappingRequest::NoError) { QString errorInfo = "Could not map asset name: " + mapping.left(mapping.length() - QString::number(copy).length() - 1); - qWarning(interfaceapp) << "Error downloading asset: " + errorInfo; + qWarning(interfaceapp) << "Error downloading model: " + errorInfo; addAssetToWorldError(filenameFromPath(filePath), errorInfo); } else if (copy < MAX_COPY_COUNT - 1) { if (copy > 0) { @@ -5753,7 +5753,7 @@ void Application::addAssetToWorldWithNewMapping(QString filePath, QString mappin } else { QString errorInfo = "Too many copies of asset name: " + mapping.left(mapping.length() - QString::number(copy).length() - 1); - qWarning(interfaceapp) << "Error downloading asset: " + errorInfo; + qWarning(interfaceapp) << "Error downloading model: " + errorInfo; addAssetToWorldError(filenameFromPath(filePath), errorInfo); } request->deleteLater(); @@ -5767,8 +5767,8 @@ void Application::addAssetToWorldUpload(QString filePath, QString mapping) { auto upload = DependencyManager::get()->createUpload(filePath); QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable { if (upload->getError() != AssetUpload::NoError) { - QString errorInfo = "Could not upload asset to the Asset Server."; - qWarning(interfaceapp) << "Error downloading asset: " + errorInfo; + QString errorInfo = "Could not upload model to the Asset Server."; + qWarning(interfaceapp) << "Error downloading model: " + errorInfo; addAssetToWorldError(filenameFromPath(filePath), errorInfo); } else { addAssetToWorldSetMapping(filePath, mapping, hash); @@ -5793,7 +5793,7 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q connect(request, &SetMappingRequest::finished, this, [=](SetMappingRequest* request) mutable { if (request->getError() != SetMappingRequest::NoError) { QString errorInfo = "Could not set asset mapping."; - qWarning(interfaceapp) << "Error downloading asset: " + errorInfo; + qWarning(interfaceapp) << "Error downloading model: " + errorInfo; addAssetToWorldError(filenameFromPath(filePath), errorInfo); } else { addAssetToWorldAddEntity(filePath, mapping); @@ -5820,8 +5820,8 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) { // on. But FBX dimensions may be in cm, so we monitor for the dimension change and rescale again if warranted. if (entityID == QUuid()) { - QString errorInfo = "Could not add asset " + mapping + " to world."; - qWarning(interfaceapp) << "Could not add asset to world: " + errorInfo; + QString errorInfo = "Could not add model " + mapping + " to world."; + qWarning(interfaceapp) << "Could not add model to world: " + errorInfo; addAssetToWorldError(filenameFromPath(filePath), errorInfo); } else { // Monitor when asset is rendered in world so that can resize if necessary. @@ -5864,7 +5864,7 @@ void Application::addAssetToWorldCheckModelSize() { auto scale = std::min(MAXIMUM_DIMENSION / dimensions.x, std::min(MAXIMUM_DIMENSION / dimensions.y, MAXIMUM_DIMENSION / dimensions.z)); dimensions *= scale; - qInfo(interfaceapp) << "Asset" << name << "auto-resized from" << previousDimensions << " to " << dimensions; + qInfo(interfaceapp) << "Model" << name << "auto-resized from" << previousDimensions << " to " << dimensions; doResize = true; item = _addAssetToWorldResizeList.erase(item); // Finished with this entity; advance to next. @@ -5879,7 +5879,7 @@ void Application::addAssetToWorldCheckModelSize() { // Rescale all dimensions. const glm::vec3 UNIT_DIMENSIONS = glm::vec3(1.0f, 1.0f, 1.0f); dimensions = UNIT_DIMENSIONS; - qInfo(interfaceapp) << "Asset" << name << "auto-resize timed out; resized to " << dimensions; + qInfo(interfaceapp) << "Model" << name << "auto-resize timed out; resized to " << dimensions; doResize = true; item = _addAssetToWorldResizeList.erase(item); // Finished with this entity; advance to next. @@ -5932,7 +5932,7 @@ void Application::addAssetToWorldInfo(QString modelName, QString infoText) { if (!_addAssetToWorldErrorTimer.isActive()) { if (!_addAssetToWorldMessageBox) { _addAssetToWorldMessageBox = DependencyManager::get()->createMessageBox(OffscreenUi::ICON_INFORMATION, - "Downloading Asset", "", QMessageBox::NoButton, QMessageBox::NoButton); + "Downloading Model", "", QMessageBox::NoButton, QMessageBox::NoButton); connect(_addAssetToWorldMessageBox, SIGNAL(destroyed()), this, SLOT(onAssetToWorldMessageBoxClosed())); } @@ -5997,7 +5997,6 @@ void Application::addAssetToWorldInfoTimeout() { } } - void Application::addAssetToWorldError(QString modelName, QString errorText) { // Displays the most recent error message for a few seconds. @@ -6016,7 +6015,7 @@ void Application::addAssetToWorldError(QString modelName, QString errorText) { if (!_addAssetToWorldMessageBox) { _addAssetToWorldMessageBox = DependencyManager::get()->createMessageBox(OffscreenUi::ICON_INFORMATION, - "Downloading Asset", "", QMessageBox::NoButton, QMessageBox::NoButton); + "Downloading Model", "", QMessageBox::NoButton, QMessageBox::NoButton); connect(_addAssetToWorldMessageBox, SIGNAL(destroyed()), this, SLOT(onAssetToWorldMessageBoxClosed())); } From 9587b6a80e0def7ad36d5401ba51907cddd31131 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 Jan 2017 20:53:57 +1300 Subject: [PATCH 07/16] Fix initial marketplace URL --- scripts/system/marketplaces/marketplaces.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 53aef34600..ad4edce927 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -15,8 +15,7 @@ Script.include("../libraries/WebTablet.js"); var toolIconUrl = Script.resolvePath("../assets/images/tools/"); -//var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; -var MARKETPLACE_URL = "https://clara.io/library?gameCheck=true&public=true"; +var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); From cbe8ce0c198f96118262f6504b9b88442b04532f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 Jan 2017 21:33:32 +1300 Subject: [PATCH 08/16] Improve handling of zip file generation failing --- scripts/system/html/js/marketplacesInject.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index d70575184c..df6cd137ea 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -180,6 +180,9 @@ // Extract zip file URL. if (data.hasOwnProperty("files") && data.files.length > 0) { zipFileURL = data.files[0].url; + if (zipFileURL.slice(-4) !== ".zip") { + console.log(JSON.stringify(data)); // Data for debugging. + } } } @@ -197,9 +200,9 @@ return; } - if (zipFileURL === "") { - statusMessage = "Download file URL not provided"; - console.log("ERROR: Clara.io FBX: " + statusMessage); + if (zipFileURL.slice(-4) !== ".zip") { + statusMessage = "Error creating zip file for download"; + console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL); EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); return; } From 6e622b37195750406e955900dd39087736ae7a6b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 Jan 2017 21:35:24 +1300 Subject: [PATCH 09/16] Don't request model be centered as work-around for downloads failing --- scripts/system/html/js/marketplacesInject.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index df6cd137ea..903b5a45b2 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -154,7 +154,12 @@ // Obtain zip file to download for requested asset. // Reference: https://clara.io/learn/sdk/api/export - var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGound=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; + + //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; + // 12 Jan 21017: Remove "¢erScene=true" option because it causes the Clara.io site to not generate zip files + // for some models (e.g., "Julia" and "Ur Draug"). + var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; + var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1]; var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid); From 288c069687b6d90e739ed5882c66127004a5745d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 Jan 2017 22:31:09 +1300 Subject: [PATCH 10/16] Make cancelling downloads more robust --- scripts/system/html/js/marketplacesInject.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 903b5a45b2..fbffed24d3 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -24,6 +24,7 @@ var canWriteAssets = false; var xmlHttpRequest = null; + var isDownloading = false; // Explicitly track download request status. function injectCommonCode(isDirectoryPage) { @@ -172,7 +173,7 @@ // Messages are appended to responseText; process the new one. var message = this.responseText.slice(responseTextIndex); - if (message.slice(0, 5) === "data:") { + if (isDownloading && message.slice(0, 5) === "data:") { // Ignore messages in flight after finished/cancelled. var data = JSON.parse(message.slice(5)); // Extract status message. @@ -197,6 +198,11 @@ // Note: onprogress doesn't have computable total length. xmlHttpRequest.onload = function () { + + if (!isDownloading) { + return; + } + var HTTP_OK = 200; if (this.status !== HTTP_OK) { statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText; @@ -216,8 +222,11 @@ console.log("Clara.io FBX: File download initiated for " + zipFileURL); xmlHttpRequest = null; + isDownloading = false; } + isDownloading = true; + console.log("Clara.io FBX: Request zip file for " + uuid); EventBridge.emitWebEvent(CLARA_IO_STATUS + " Initiating download"); @@ -269,6 +278,8 @@ } function cancelClaraDownload() { + isDownloading = false; + if (xmlHttpRequest) { xmlHttpRequest.abort(); xmlHttpRequest = null; From d45e6f00cbbe012e28baee53745c2e81f137e508 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 Jan 2017 22:35:42 +1300 Subject: [PATCH 11/16] Prepare only one download at a time --- scripts/system/html/js/marketplacesInject.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index fbffed24d3..bce6a0e5d9 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -138,6 +138,12 @@ // Automatic download to High Fidelity. function startAutoDownload() { + // One file request at a time. + if (isDownloading) { + console.log("WARNIKNG: Clara.io FBX: Prepare only one download at a time"); + return; + } + // User must be able to write to Asset Server. if (!canWriteAssets) { console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server"); From 4dee0efdd3b9619f029ef78dac71481d4b867bf0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 13 Jan 2017 09:12:49 +1300 Subject: [PATCH 12/16] Use more reliable Clara.io API parameters --- scripts/system/html/js/marketplacesInject.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index bce6a0e5d9..94e2a68460 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -163,9 +163,9 @@ // Reference: https://clara.io/learn/sdk/api/export //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; - // 12 Jan 21017: Remove "¢erScene=true" option because it causes the Clara.io site to not generate zip files - // for some models (e.g., "Julia" and "Ur Draug"). - var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; + // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to + // be successful in generating zip files. + var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL"; var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1]; var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid); From 6f2386eabd02dc0b0560d50bd4a0324934426bcb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 13 Jan 2017 11:20:00 +1300 Subject: [PATCH 13/16] Fix processing of Clara.io status messages --- scripts/system/html/js/marketplacesInject.js | 48 ++++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 94e2a68460..65f722826f 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -172,29 +172,46 @@ xmlHttpRequest = new XMLHttpRequest(); var responseTextIndex = 0; - var statusMessage = ""; var zipFileURL = ""; xmlHttpRequest.onreadystatechange = function () { - // Messages are appended to responseText; process the new one. + // Messages are appended to responseText; process the new ones. var message = this.responseText.slice(responseTextIndex); + var statusMessage = ""; - if (isDownloading && message.slice(0, 5) === "data:") { // Ignore messages in flight after finished/cancelled. - var data = JSON.parse(message.slice(5)); + if (isDownloading) { // Ignore messages in flight after finished/cancelled. + var lines = message.split(/[\n\r]+/); - // Extract status message. - if (data.hasOwnProperty("message") && data.message !== null) { - statusMessage = data.message; - console.log("Clara.io FBX: " + statusMessage); - EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); + for (var i = 0, length = lines.length; i < length; i++) { + if (lines[i].slice(0, 5) === "data:") { + // Parse line. + var data; + try { + data = JSON.parse(lines[i].slice(5)); + } + catch (e) { + data = {}; + } + + // Extract status message. + if (data.hasOwnProperty("message") && data.message !== null) { + statusMessage = data.message; + console.log("Clara.io FBX: " + statusMessage); + } + + // Extract zip file URL. + if (data.hasOwnProperty("files") && data.files.length > 0) { + zipFileURL = data.files[0].url; + if (zipFileURL.slice(-4) !== ".zip") { + console.log(JSON.stringify(data)); // Data for debugging. + } + } + } } - // Extract zip file URL. - if (data.hasOwnProperty("files") && data.files.length > 0) { - zipFileURL = data.files[0].url; - if (zipFileURL.slice(-4) !== ".zip") { - console.log(JSON.stringify(data)); // Data for debugging. - } + if (statusMessage !== "") { + // Update the UI with the most recent status message. + EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); } } @@ -204,6 +221,7 @@ // Note: onprogress doesn't have computable total length. xmlHttpRequest.onload = function () { + var statusMessage = ""; if (!isDownloading) { return; From 632989e031f557f4f4793662e6a9c4638c5f5b25 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 13 Jan 2017 12:09:22 +1300 Subject: [PATCH 14/16] Fix progress messages not displaying in HMD --- scripts/system/marketplaces/marketplaces.js | 34 ++++++++++++--------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index ad4edce927..cea22d50e0 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -48,13 +48,25 @@ var marketplaceWindow = new OverlayWebWindow({ }); marketplaceWindow.setScriptURL(MARKETPLACES_INJECT_SCRIPT_URL); -marketplaceWindow.webEventReceived.connect(function (message) { +function onWebEventReceived(message) { if (message === GOTO_DIRECTORY) { - marketplaceWindow.setURL(MARKETPLACES_URL); + var url = MARKETPLACES_URL; + if (marketplaceWindow.visible) { + marketplaceWindow.setURL(url); + } + if (marketplaceWebTablet) { + marketplaceWebTablet.setURL(url); + } return; } if (message === QUERY_CAN_WRITE_ASSETS) { - marketplaceWindow.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); + var canWriteAssets = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets(); + if (marketplaceWindow.visible) { + marketplaceWindow.emitScriptEvent(canWriteAssets); + } + if (marketplaceWebTablet) { + marketplaceWebTablet.getOverlayObject().emitScriptEvent(canWriteAssets); + } return; } if (message === WARN_USER_NO_PERMISSIONS) { @@ -87,7 +99,9 @@ marketplaceWindow.webEventReceived.connect(function (message) { if (message === CLARA_IO_CANCELLED_DOWNLOAD) { isDownloadBeingCancelled = false; } -}); +} + +marketplaceWindow.webEventReceived.connect(onWebEventReceived); function onMessageBoxClosed(id, button) { if (id === messageBox && button === CANCEL_BUTTON) { @@ -122,17 +136,7 @@ function showMarketplace() { marketplaceWebTablet = new WebTablet(MARKETPLACE_URL_INITIAL, null, null, true); Settings.setValue(persistenceKey, marketplaceWebTablet.pickle()); marketplaceWebTablet.setScriptURL(MARKETPLACES_INJECT_SCRIPT_URL); - marketplaceWebTablet.getOverlayObject().webEventReceived.connect(function (message) { - if (message === GOTO_DIRECTORY) { - marketplaceWebTablet.setURL(MARKETPLACES_URL); - } - if (message === QUERY_CAN_WRITE_ASSETS) { - marketplaceWebTablet.getOverlayObject().emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); - } - if (message === WARN_USER_NO_PERMISSIONS) { - Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); - } - }); + marketplaceWebTablet.getOverlayObject().webEventReceived.connect(onWebEventReceived); } else { marketplaceWindow.setURL(MARKETPLACE_URL_INITIAL); marketplaceWindow.setVisible(true); From ad5822d96d51f663b860bd88a62baad86ab78595 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 13 Jan 2017 12:26:42 +1300 Subject: [PATCH 15/16] Fix first progress message box not closing --- scripts/system/marketplaces/marketplaces.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index cea22d50e0..d5530e7db2 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -89,7 +89,7 @@ function onWebEventReceived(message) { } if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) { - if (messageBox) { + if (messageBox !== null) { Window.closeMessageBox(messageBox); messageBox = null; } From d7ab7316a89286c370e9adb0775e9b8dcc69a261 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 13 Jan 2017 12:28:53 +1300 Subject: [PATCH 16/16] Tidying --- scripts/system/html/js/marketplacesInject.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 65f722826f..0944c4113e 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -80,10 +80,10 @@ // Add button links. $('#exploreClaraMarketplace').on('click', function () { - window.location = "https://clara.io/library?gameCheck=true&public=true" + window.location = "https://clara.io/library?gameCheck=true&public=true"; }); $('#exploreHifiMarketplace').on('click', function () { - window.location = "http://www.highfidelity.com/marketplace" + window.location = "http://www.highfidelity.com/marketplace"; }); } @@ -218,7 +218,7 @@ responseTextIndex = this.responseText.length; }; - // Note: onprogress doesn't have computable total length. + // Note: onprogress doesn't have computable total length so can't use it to determine % complete. xmlHttpRequest.onload = function () { var statusMessage = ""; @@ -236,7 +236,7 @@ } if (zipFileURL.slice(-4) !== ".zip") { - statusMessage = "Error creating zip file for download"; + statusMessage = "Error creating zip file for download."; console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL); EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); return;