diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 0d42cb599e..cdb8368296 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -31,8 +31,9 @@ Rectangle { id: root property string activeView: "initialize" - property int currentSortIndex: 0 + property int currentSortIndex: 1 property string sortString: "recent" + property bool isAscending: false property string categoryString: "" property string searchString: "" property bool keyboardEnabled: HMD.active @@ -316,23 +317,28 @@ Rectangle { font.pixelSize: hifi.fontSizes.textFieldInput placeholderText: "Search Marketplace" + Timer { + id: keypressTimer + running: false + repeat: false + interval: 300 + onTriggered: searchField.accepted() + + } + // workaround for https://bugreports.qt.io/browse/QTBUG-49297 Keys.onPressed: { switch (event.key) { case Qt.Key_Return: case Qt.Key_Enter: event.accepted = true; + searchField.text = ""; - // emit accepted signal manually - if (acceptableInput) { - searchField.accepted(); - searchField.forceActiveFocus(); - } + getMarketplaceItems(); + searchField.forceActiveFocus(); break; - case Qt.Key_Backspace: - if (searchField.text === "") { - primaryFilter_index = -1; - } + default: + keypressTimer.restart(); break; } } @@ -498,6 +504,7 @@ Rectangle { "", "", root.sortString, + root.isAscending, WalletScriptingInterface.limitedCommerce, marketBrowseModel.currentPageToRetrieve, marketBrowseModel.itemsPerPage @@ -726,7 +733,7 @@ Rectangle { top: parent.top leftMargin: 20 } - width: root.isLoggedIn ? 322 : 242 + width: root.isLoggedIn ? 342 : 262 height: 36 radius: 4 @@ -737,27 +744,27 @@ Rectangle { id: sortModel ListElement { - name: "Name"; - glyph: ";" + name: "Name" sortString: "alpha" + ascending: true } ListElement { - name: "Date"; - glyph: ";"; - sortString: "recent"; + name: "Date" + sortString: "recent" + ascending: false } ListElement { - name: "Popular"; - glyph: ";"; - sortString: "likes"; + name: "Popular" + sortString: "likes" + ascending: false } ListElement { - name: "My Likes"; - glyph: ";"; - sortString: "my_likes"; + name: "My Likes" + sortString: "my_likes" + ascending: false } } @@ -783,10 +790,10 @@ Rectangle { currentIndex: 1; delegate: SortButton { - width: 80 + width: 85 height: parent.height - glyph: model.glyph + ascending: model.ascending text: model.name visible: root.isLoggedIn || model.sortString != "my_likes" @@ -794,6 +801,12 @@ Rectangle { checked: ListView.isCurrentItem onClicked: { + if(root.currentSortIndex == index) { + ascending = !ascending; + } else { + ascending = model.ascending; + } + root.isAscending = ascending; root.currentSortIndex = index; sortListView.positionViewAtIndex(index, ListView.Beginning); sortListView.currentIndex = index; @@ -802,7 +815,7 @@ Rectangle { } } highlight: Rectangle { - width: 80 + width: 85 height: parent.height color: hifi.colors.faintGray @@ -1125,6 +1138,8 @@ Rectangle { fill: parent } + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + RalewayRegular { id: licenseText diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 0a57e56099..2c7a50033c 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -520,7 +520,7 @@ Rectangle { } else if (root.license === "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)") { url = "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.txt" } else if (root.license === "Proof of Provenance License (PoP License)") { - url = "https://digitalassetregistry.com/PoP-License/v1/" + url = "licenses/Popv1.txt" } if(url) { showLicense(url) diff --git a/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml b/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml index 37ad2735ce..2673043b6b 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml @@ -28,58 +28,60 @@ Item { id: root; + property string ascGlyph: "\u2193" + property string descGlyph: "\u2191" + property string text: "" + property bool ascending: false + property bool checked: false + signal clicked() - property string glyph: ""; - property string text: ""; - property bool checked: false; - signal clicked(); - - width: childrenRect.width; - height: parent.height; + width: childrenRect.width + height: parent.height Rectangle { - anchors.top: parent.top; - anchors.left: parent.left; - height: parent.height; - width: 2; - color: hifi.colors.faintGray; - visible: index > 0; + anchors.top: parent.top + anchors.left: parent.left + height: parent.height + width: 2 + color: hifi.colors.faintGray + visible: index > 0 } - HiFiGlyphs { - id: buttonGlyph; - text: root.glyph; + RalewayRegular { + id: buttonGlyph + text: root.ascending ? root.ascGlyph : root.descGlyph // Size - size: 14; + size: 14 // Anchors - anchors.left: parent.left; - anchors.leftMargin: 0; - anchors.top: parent.top; - anchors.verticalCenter: parent.verticalCenter; - height: parent.height; - horizontalAlignment: Text.AlignHCenter; + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.top: parent.top + anchors.topMargin: 6 + anchors.bottom: parent.bottom + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop // Style - color: hifi.colors.lightGray; + color: hifi.colors.lightGray } RalewayRegular { - id: buttonText; - text: root.text; + id: buttonText + text: root.text // Text size - size: 14; + size: 14 // Style - color: hifi.colors.lightGray; - elide: Text.ElideRight; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; + color: hifi.colors.lightGray + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter // Anchors - anchors.left: parent.left; - anchors.leftMargin: 20; - anchors.top: parent.top; - height: parent.height; + anchors.left: buttonGlyph.right + anchors.leftMargin: 5 + anchors.top: parent.top + height: parent.height } MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; + anchors.fill: parent + hoverEnabled: enabled onClicked: { root.clicked(); } diff --git a/interface/resources/qml/hifi/commerce/marketplace/licenses/Popv1.txt b/interface/resources/qml/hifi/commerce/marketplace/licenses/Popv1.txt new file mode 100644 index 0000000000..1f44fc19e6 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplace/licenses/Popv1.txt @@ -0,0 +1,12 @@ + +

Proof of Provenance License (PoP License) v1.0

+
+

+ Subject to the terms and conditions of this license, the Copyright Holder grants a worldwide, non-exclusive, non-sublicensable, non-transferable (except by transfer of the Certificate or beneficial ownership thereof) license (i) to the Certificate Holder to display ONE COPY of the Item at a time across any and all virtual worlds WITHOUT MODIFICATION; (ii) to any party to view and interact with the Item as displayed by the Certificate Holder. Redistributions of source code must retain the all copyright notices. Notwithstanding the foregoing, modification of the Item may be permitted pursuant to terms provided in the Certificate. +

+

+ THE ITEM IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR A CONTRIBUTOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ITEM, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +

+

+ Reference to the “Certificate” means the Proof of Provenance Certificate containing a hash of the code used to generate the Item; ‘Item’ means the visual representation produced by the execution of the code hashed in the Certificate (which term includes the code itself); and “Certificate Holder” means a single holder of the private key for the Certificate. +

diff --git a/interface/src/commerce/QmlMarketplace.cpp b/interface/src/commerce/QmlMarketplace.cpp index 23ba418a2d..8197b20275 100644 --- a/interface/src/commerce/QmlMarketplace.cpp +++ b/interface/src/commerce/QmlMarketplace.cpp @@ -50,9 +50,10 @@ void QmlMarketplace::getMarketplaceItems( const QString& adminFilter, const QString& adminFilterCost, const QString& sort, - const bool isFree, - const int& page, - const int& perPage) { + bool isAscending, + bool isFree, + int page, + int perPage) { QString endpoint = "items"; QUrlQuery request; @@ -62,6 +63,7 @@ void QmlMarketplace::getMarketplaceItems( request.addQueryItem("adminFilter", adminFilter); request.addQueryItem("adminFilterCost", adminFilterCost); request.addQueryItem("sort", sort); + request.addQueryItem("sort_dir", isAscending ? "asc" : "desc"); if (isFree) { request.addQueryItem("isFree", "true"); } diff --git a/interface/src/commerce/QmlMarketplace.h b/interface/src/commerce/QmlMarketplace.h index 5794d4f53c..76b3d41449 100644 --- a/interface/src/commerce/QmlMarketplace.h +++ b/interface/src/commerce/QmlMarketplace.h @@ -46,9 +46,10 @@ protected: const QString& adminFilter = QString("published"), const QString& adminFilterCost = QString(), const QString& sort = QString(), - const bool isFree = false, - const int& page = 1, - const int& perPage = 20); + bool isAscending = false, + bool isFree = false, + int page = 1, + int perPage = 20); Q_INVOKABLE void getMarketplaceItem(const QString& marketplaceItemId); Q_INVOKABLE void marketplaceItemLike(const QString& marketplaceItemId, const bool like = true); Q_INVOKABLE void getMarketplaceCategories(); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js deleted file mode 100644 index 8d408169ba..0000000000 --- a/scripts/system/html/js/marketplacesInject.js +++ /dev/null @@ -1,744 +0,0 @@ -/* global $, window, MutationObserver */ - -// -// marketplacesInject.js -// -// Created by David Rowe on 12 Nov 2016. -// Copyright 2016 High Fidelity, Inc. -// -// Injected into marketplace Web pages. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -(function () { - // 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 GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; - 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; - var isPreparing = false; // Explicitly track download request status. - - var limitedCommerce = false; - var commerceMode = false; - var userIsLoggedIn = false; - var walletNeedsSetup = false; - var marketplaceBaseURL = "https://highfidelity.com"; - var messagesWaiting = false; - - function injectCommonCode(isDirectoryPage) { - // Supporting styles from marketplaces.css. - // Glyph font family, size, and spacing adjusted because HiFi-Glyphs cannot be used cross-domain. - $("head").append( - '' - ); - - // Supporting styles from edit-style.css. - // Font family, size, and position adjusted because Raleway-Bold cannot be used cross-domain. - $("head").append( - '' - ); - - // Footer. - var isInitialHiFiPage = location.href === (marketplaceBaseURL + "/marketplace?"); - $("body").append( - '
' + - (!isInitialHiFiPage ? '' : '') + - (isInitialHiFiPage ? '🛈 Get items from Clara.io!' : '') + - (!isDirectoryPage ? '' : '') + - (isDirectoryPage ? '🛈 Select a marketplace to explore.' : '') + - '
' - ); - - // Footer actions. - $("#back-button").on("click", function () { - if (document.referrer !== "") { - window.history.back(); - } else { - var params = { type: GOTO_MARKETPLACE }; - var itemIdMatch = location.search.match(/itemId=([^&]*)/); - if (itemIdMatch && itemIdMatch.length === 2) { - params.itemId = itemIdMatch[1]; - } - EventBridge.emitWebEvent(JSON.stringify(params)); - } - }); - $("#all-markets").on("click", function () { - EventBridge.emitWebEvent(JSON.stringify({ - type: GOTO_DIRECTORY - })); - }); - } - - function injectDirectoryCode() { - - // Remove e-mail hyperlink. - var letUsKnow = $("#letUsKnow"); - letUsKnow.replaceWith(letUsKnow.html()); - - // Add button links. - - $('#exploreClaraMarketplace').on('click', function () { - window.location = "https://clara.io/library?gameCheck=true&public=true"; - }); - $('#exploreHifiMarketplace').on('click', function () { - EventBridge.emitWebEvent(JSON.stringify({ - type: GOTO_MARKETPLACE - })); - }); - } - - emitWalletSetupEvent = function () { - EventBridge.emitWebEvent(JSON.stringify({ - type: "WALLET_SETUP" - })); - }; - - function maybeAddSetupWalletButton() { - if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) { - $('body').addClass("walletsetup-injected"); - - var resultsElement = document.getElementById('results'); - var setupWalletElement = document.createElement('div'); - setupWalletElement.classList.add("row"); - setupWalletElement.id = "setupWalletDiv"; - setupWalletElement.style = "height:60px;margin:20px 10px 10px 10px;padding:12px 5px;" + - "background-color:#D6F4D8;border-color:#aee9b2;border-width:2px;border-style:solid;border-radius:5px;"; - - var span = document.createElement('span'); - span.style = "margin:10px 5px;color:#1b6420;font-size:15px;"; - span.innerHTML = "Activate your Wallet to get money and shop in Marketplace."; - - var xButton = document.createElement('a'); - xButton.id = "xButton"; - xButton.setAttribute('href', "#"); - xButton.style = "width:50px;height:100%;margin:0;color:#ccc;font-size:20px;"; - xButton.innerHTML = "X"; - xButton.onclick = function () { - setupWalletElement.remove(); - dummyRow.remove(); - }; - - setupWalletElement.appendChild(span); - setupWalletElement.appendChild(xButton); - - resultsElement.insertBefore(setupWalletElement, resultsElement.firstChild); - - // Dummy row for padding - var dummyRow = document.createElement('div'); - dummyRow.classList.add("row"); - dummyRow.style = "height:15px;"; - resultsElement.insertBefore(dummyRow, resultsElement.firstChild); - } - } - - function maybeAddLogInButton() { - if (!$('body').hasClass("login-injected") && !userIsLoggedIn) { - $('body').addClass("login-injected"); - var resultsElement = document.getElementById('results'); - if (!resultsElement) { // If we're on the main page, this will evaluate to `true` - resultsElement = document.getElementById('item-show'); - resultsElement.style = 'margin-top:0;'; - } - var logInElement = document.createElement('div'); - logInElement.classList.add("row"); - logInElement.id = "logInDiv"; - logInElement.style = "height:60px;margin:20px 10px 10px 10px;padding:5px;" + - "background-color:#D6F4D8;border-color:#aee9b2;border-width:2px;border-style:solid;border-radius:5px;"; - - var button = document.createElement('a'); - button.classList.add("btn"); - button.classList.add("btn-default"); - button.id = "logInButton"; - button.setAttribute('href', "#"); - button.innerHTML = "LOG IN"; - button.style = "width:80px;height:100%;margin-top:0;margin-left:10px;padding:13px;font-weight:bold;background:linear-gradient(white, #ccc);"; - button.onclick = function () { - EventBridge.emitWebEvent(JSON.stringify({ - type: "LOGIN" - })); - }; - - var span = document.createElement('span'); - span.style = "margin:10px;color:#1b6420;font-size:15px;"; - span.innerHTML = "to get items from the Marketplace."; - - var xButton = document.createElement('a'); - xButton.id = "xButton"; - xButton.setAttribute('href', "#"); - xButton.style = "width:50px;height:100%;margin:0;color:#ccc;font-size:20px;"; - xButton.innerHTML = "X"; - xButton.onclick = function () { - logInElement.remove(); - dummyRow.remove(); - }; - - logInElement.appendChild(button); - logInElement.appendChild(span); - logInElement.appendChild(xButton); - - resultsElement.insertBefore(logInElement, resultsElement.firstChild); - - // Dummy row for padding - var dummyRow = document.createElement('div'); - dummyRow.classList.add("row"); - dummyRow.style = "height:15px;"; - resultsElement.insertBefore(dummyRow, resultsElement.firstChild); - } - } - - function changeDropdownMenu() { - var logInOrOutButton = document.createElement('a'); - logInOrOutButton.id = "logInOrOutButton"; - logInOrOutButton.setAttribute('href', "#"); - logInOrOutButton.innerHTML = userIsLoggedIn ? "Log Out" : "Log In"; - logInOrOutButton.onclick = function () { - EventBridge.emitWebEvent(JSON.stringify({ - type: "LOGIN" - })); - }; - - $($('.dropdown-menu').find('li')[0]).append(logInOrOutButton); - - $('a[href="/marketplace?view=mine"]').each(function () { - $(this).attr('href', '#'); - $(this).on('click', function () { - EventBridge.emitWebEvent(JSON.stringify({ - type: "MY_ITEMS" - })); - }); - }); - } - - function buyButtonClicked(id, referrer, edition) { - EventBridge.emitWebEvent(JSON.stringify({ - type: "CHECKOUT", - itemId: id, - referrer: referrer, - itemEdition: edition - })); - } - - function injectBuyButtonOnMainPage() { - var cost; - - // Unbind original mouseenter and mouseleave behavior - $('body').off('mouseenter', '#price-or-edit .price'); - $('body').off('mouseleave', '#price-or-edit .price'); - - $('.grid-item').find('#price-or-edit').each(function () { - $(this).css({ "margin-top": "0" }); - }); - - $('.grid-item').find('#price-or-edit').find('a').each(function() { - if ($(this).attr('href') !== '#') { // Guard necessary because of the AJAX nature of Marketplace site - $(this).attr('data-href', $(this).attr('href')); - $(this).attr('href', '#'); - } - cost = $(this).closest('.col-xs-3').find('.item-cost').text(); - var costInt = parseInt(cost, 10); - - $(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6'); - $(this).closest('.col-xs-3').attr("class", 'col-xs-6'); - - var priceElement = $(this).find('.price'); - var available = true; - - if (priceElement.text() === 'invalidated' || - priceElement.text() === 'sold out' || - priceElement.text() === 'not for sale') { - available = false; - priceElement.css({ - "padding": "3px 5px 10px 5px", - "height": "40px", - "background": "linear-gradient(#a2a2a2, #fefefe)", - "color": "#000", - "font-weight": "600", - "line-height": "34px" - }); - } else { - priceElement.css({ - "padding": "3px 5px", - "height": "40px", - "background": "linear-gradient(#00b4ef, #0093C5)", - "color": "#FFF", - "font-weight": "600", - "line-height": "34px" - }); - } - - if (parseInt(cost) > 0) { - priceElement.css({ "width": "auto" }); - - if (available) { - priceElement.html(' ' + cost); - } - - priceElement.css({ "min-width": priceElement.width() + 30 }); - } - }); - - // change pricing to GET/BUY on button hover - $('body').on('mouseenter', '#price-or-edit .price', function () { - var $this = $(this); - var buyString = "BUY"; - var getString = "GET"; - // Protection against the button getting stuck in the "BUY"/"GET" state. - // That happens when the browser gets two MOUSEENTER events before getting a - // MOUSELEAVE event. Also, if not available for sale, just return. - if ($this.text() === buyString || - $this.text() === getString || - $this.text() === 'invalidated' || - $this.text() === 'sold out' || - $this.text() === 'not for sale' ) { - return; - } - $this.data('initialHtml', $this.html()); - - var cost = $(this).parent().siblings().text(); - if (parseInt(cost) > 0) { - $this.text(buyString); - } - if (parseInt(cost) == 0) { - $this.text(getString); - } - }); - - $('body').on('mouseleave', '#price-or-edit .price', function () { - var $this = $(this); - $this.html($this.data('initialHtml')); - }); - - - $('.grid-item').find('#price-or-edit').find('a').on('click', function () { - var price = $(this).closest('.grid-item').find('.price').text(); - if (price === 'invalidated' || - price === 'sold out' || - price === 'not for sale') { - return false; - } - buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'), - "mainPage", - -1); - }); - } - - function injectUnfocusOnSearch() { - // unfocus input field on search, thus hiding virtual keyboard - $('#search-box').on('submit', function () { - if (document.activeElement) { - document.activeElement.blur(); - } - }); - } - - // fix for 10108 - marketplace category cannot scroll - function injectAddScrollbarToCategories() { - $('#categories-dropdown').on('show.bs.dropdown', function () { - $('body > div.container').css('display', 'none') - $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' }); - }); - - $('#categories-dropdown').on('hide.bs.dropdown', function () { - $('body > div.container').css('display', ''); - $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' }); - }); - } - - function injectHiFiCode() { - if (commerceMode) { - maybeAddLogInButton(); - maybeAddSetupWalletButton(); - - if (!$('body').hasClass("code-injected")) { - - $('body').addClass("code-injected"); - changeDropdownMenu(); - - var target = document.getElementById('templated-items'); - // MutationObserver is necessary because the DOM is populated after the page is loaded. - // We're searching for changes to the element whose ID is '#templated-items' - this is - // the element that gets filled in by the AJAX. - var observer = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - injectBuyButtonOnMainPage(); - }); - }); - var config = { attributes: true, childList: true, characterData: true }; - observer.observe(target, config); - - // Try this here in case it works (it will if the user just pressed the "back" button, - // since that doesn't trigger another AJAX request. - injectBuyButtonOnMainPage(); - } - } - - injectUnfocusOnSearch(); - injectAddScrollbarToCategories(); - } - - function injectHiFiItemPageCode() { - if (commerceMode) { - maybeAddLogInButton(); - - if (!$('body').hasClass("code-injected")) { - - $('body').addClass("code-injected"); - changeDropdownMenu(); - - var purchaseButton = $('#side-info').find('.btn').first(); - - var href = purchaseButton.attr('href'); - purchaseButton.attr('href', '#'); - var cost = $('.item-cost').text(); - var costInt = parseInt(cost, 10); - var availability = $.trim($('.item-availability').text()); - if (limitedCommerce && (costInt > 0)) { - availability = ''; - } - if (availability === 'available') { - purchaseButton.css({ - "background": "linear-gradient(#00b4ef, #0093C5)", - "color": "#FFF", - "font-weight": "600", - "padding-bottom": "10px" - }); - } else { - purchaseButton.css({ - "background": "linear-gradient(#a2a2a2, #fefefe)", - "color": "#000", - "font-weight": "600", - "padding-bottom": "10px" - }); - } - - var type = $('.item-type').text(); - var isUpdating = window.location.href.indexOf('edition=') > -1; - var urlParams = new URLSearchParams(window.location.search); - if (isUpdating) { - purchaseButton.html('UPDATE FOR FREE'); - } else if (availability !== 'available') { - purchaseButton.html('UNAVAILABLE ' + (availability ? ('(' + availability + ')') : '')); - } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { - purchaseButton.html('PURCHASE ' + cost); - } - - purchaseButton.on('click', function () { - if ('available' === availability || isUpdating) { - buyButtonClicked(window.location.pathname.split("/")[3], - "itemPage", - urlParams.get('edition')); - } - }); - } - } - - injectUnfocusOnSearch(); - } - - function updateClaraCode() { - // Have to repeatedly update Clara page because its content can change dynamically without location.href changing. - - // Clara library page. - if (location.href.indexOf("clara.io/library") !== -1) { - // Make entries navigate to "Image" view instead of default "Real Time" view. - var elements = $("a.thumbnail"); - for (var i = 0, length = elements.length; i < length; i++) { - var value = elements[i].getAttribute("href"); - if (value.slice(-6) !== "/image") { - elements[i].setAttribute("href", value + "/image"); - } - } - } - - // Clara item page. - if (location.href.indexOf("clara.io/view/") !== -1) { - // Make site navigation links retain gameCheck etc. parameters. - var element = $("a[href^=\'/library\']")[0]; - var parameters = "?gameCheck=true&public=true"; - var href = element.getAttribute("href"); - if (href.slice(-parameters.length) !== parameters) { - element.setAttribute("href", href + parameters); - } - - // Remove unwanted buttons and replace download options with a single "Download to High Fidelity" button. - var buttons = $("a.embed-button").parent("div"); - 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) { - var downloadContainer = $('
'); - $(".top-title .col-sm-4").append(downloadContainer); - downloadContainer.append(downloadFBX); - } - } - } - - // Automatic download to High Fidelity. - function startAutoDownload() { - // One file request at a time. - if (isPreparing) { - console.log("WARNING: 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"); - EventBridge.emitWebEvent(JSON.stringify({ - type: WARN_USER_NO_PERMISSIONS - })); - 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&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); - - xmlHttpRequest = new XMLHttpRequest(); - var responseTextIndex = 0; - var zipFileURL = ""; - - xmlHttpRequest.onreadystatechange = function () { - // Messages are appended to responseText; process the new ones. - var message = this.responseText.slice(responseTextIndex); - var statusMessage = ""; - - if (isPreparing) { // Ignore messages in flight after finished/cancelled. - var lines = message.split(/[\n\r]+/); - - 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 zip file URL. - if (data.hasOwnProperty("files") && data.files.length > 0) { - zipFileURL = data.files[0].url; - } - } - } - - if (statusMessage !== "") { - // Update the UI with the most recent status message. - EventBridge.emitWebEvent(JSON.stringify({ - type: CLARA_IO_STATUS, - status: statusMessage - })); - } - } - - responseTextIndex = this.responseText.length; - }; - - // Note: onprogress doesn't have computable total length so can't use it to determine % complete. - - xmlHttpRequest.onload = function () { - var statusMessage = ""; - - if (!isPreparing) { - return; - } - - isPreparing = false; - - var HTTP_OK = 200; - if (this.status !== HTTP_OK) { - EventBridge.emitWebEvent(JSON.stringify({ - type: CLARA_IO_STATUS, - status: statusMessage - })); - } else if (zipFileURL.slice(-4) !== ".zip") { - EventBridge.emitWebEvent(JSON.stringify({ - type: CLARA_IO_STATUS, - status: (statusMessage + ": " + zipFileURL) - })); - } else { - EventBridge.emitWebEvent(JSON.stringify({ - type: CLARA_IO_DOWNLOAD - })); - } - - xmlHttpRequest = null; - } - - isPreparing = true; - EventBridge.emitWebEvent(JSON.stringify({ - type: CLARA_IO_STATUS, - status: "Initiating download" - })); - - xmlHttpRequest.open("POST", url, true); - xmlHttpRequest.setRequestHeader("Accept", "text/event-stream"); - xmlHttpRequest.send(); - } - - function injectClaraCode() { - - // Make space for marketplaces footer in Clara pages. - $("head").append( - '' - ); - - // Condense space. - $("head").append( - '' - ); - - // Move "Download to High Fidelity" button. - $("head").append( - '' - ); - - // Update code injected per page displayed. - var updateClaraCodeInterval = undefined; - updateClaraCode(); - updateClaraCodeInterval = setInterval(function () { - updateClaraCode(); - }, 1000); - - window.addEventListener("unload", function () { - clearInterval(updateClaraCodeInterval); - updateClaraCodeInterval = undefined; - }); - - EventBridge.emitWebEvent(JSON.stringify({ - type: QUERY_CAN_WRITE_ASSETS - })); - } - - function cancelClaraDownload() { - isPreparing = false; - - if (xmlHttpRequest) { - xmlHttpRequest.abort(); - xmlHttpRequest = null; - console.log("Clara.io FBX: File download cancelled"); - EventBridge.emitWebEvent(JSON.stringify({ - type: CLARA_IO_CANCELLED_DOWNLOAD - })); - } - } - - function injectCode() { - var DIRECTORY = 0; - var HIFI = 1; - var CLARA = 2; - var HIFI_ITEM_PAGE = 3; - var pageType = DIRECTORY; - - if (location.href.indexOf(marketplaceBaseURL + "/") !== -1) { pageType = HIFI; } - if (location.href.indexOf("clara.io/") !== -1) { pageType = CLARA; } - if (location.href.indexOf(marketplaceBaseURL + "/marketplace/items/") !== -1) { pageType = HIFI_ITEM_PAGE; } - - injectCommonCode(pageType === DIRECTORY); - switch (pageType) { - case DIRECTORY: - injectDirectoryCode(); - break; - case HIFI: - injectHiFiCode(); - break; - case CLARA: - injectClaraCode(); - break; - case HIFI_ITEM_PAGE: - injectHiFiItemPageCode(); - break; - - } - } - - function onLoad() { - EventBridge.scriptEventReceived.connect(function (message) { - message = JSON.parse(message); - if (message.type === CAN_WRITE_ASSETS) { - canWriteAssets = message.canWriteAssets; - } else if (message.type === CLARA_IO_CANCEL_DOWNLOAD) { - cancelClaraDownload(); - } else if (message.type === "marketplaces") { - if (message.action === "commerceSetting") { - limitedCommerce = !!message.data.limitedCommerce; - commerceMode = !!message.data.commerceMode; - userIsLoggedIn = !!message.data.userIsLoggedIn; - walletNeedsSetup = !!message.data.walletNeedsSetup; - marketplaceBaseURL = message.data.metaverseServerURL; - if (marketplaceBaseURL.indexOf('metaverse.') !== -1) { - marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', ''); - } - messagesWaiting = message.data.messagesWaiting; - injectCode(); - } - } - }); - - // Request commerce setting - // Code is injected into the webpage after the setting comes back. - EventBridge.emitWebEvent(JSON.stringify({ - type: "REQUEST_SETTING" - })); - } - - // Load / unload. - window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). - window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed -}());