From 0f415abd6b36fa279ce26137e3f901fec611787f Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 17 Aug 2016 16:56:52 -0700 Subject: [PATCH] dual purpose address bar - switchable between html and qml --- interface/resources/qml/AddressBarDialog.qml | 43 +- interface/resources/qml/AddressBarDialog.qml~ | 510 ------------------ 2 files changed, 26 insertions(+), 527 deletions(-) delete mode 100644 interface/resources/qml/AddressBarDialog.qml~ diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index 7c2ef17590..089bec07f4 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -48,20 +48,22 @@ Window { } function resetAfterTeleport() { - console.log('hrs fixme got reset') storyCardFrame.shown = root.shown = false; } function goCard(card) { if (addressBarDialog.useFeed) { - /*storyCard.imageUrl = card.imageUrl; // hrs fixme - storyCard.userName = card.userName; - storyCard.placeName = card.placeName; - storyCard.actionPhrase = card.actionPhrase; - storyCard.timePhrase = card.timePhrase; - storyCard.hifiUrl = card.hifiUrl; - storyCard.visible = true;*/ - storyCard.url = metaverseBase + "user_stories/" + card.storyId + ".html"; - storyCardFrame.shown = true; + if (useHTML) { + storyCardHTML.url = metaverseBase + "user_stories/" + card.storyId + ".html"; + storyCardFrame.shown = true; + } else { + storyCardQML.imageUrl = card.imageUrl; + storyCardQML.userName = card.userName; + storyCardQML.placeName = card.placeName; + storyCardQML.actionPhrase = card.actionPhrase; + storyCardQML.timePhrase = card.timePhrase; + storyCardQML.hifiUrl = card.hifiUrl; + storyCardQML.visible = true; + } return; } addressLine.text = card.hifiUrl; @@ -72,6 +74,8 @@ Window { property int cardWidth: 200; property int cardHeight: 152; property string metaverseBase: "https://metaverse.highfidelity.com/api/v1/"; + //property string metaverseBase: "http://10.0.0.241:3000/api/v1/"; + property bool useHTML: false; // fixme: remove this and all false branches after the server is updated AddressBarDialog { id: addressBarDialog @@ -81,7 +85,7 @@ Window { onBackEnabledChanged: backArrow.buttonState = addressBarDialog.backEnabled ? 1 : 0; onForwardEnabledChanged: forwardArrow.buttonState = addressBarDialog.forwardEnabled ? 1 : 0; onUseFeedChanged: { updateFeedState(); } - onReceivedHifiSchemeURL: { console.log('hrs status change'); console.log('hrs status got', url); resetAfterTeleport(); } + onReceivedHifiSchemeURL: resetAfterTeleport(); ListModel { id: suggestions } @@ -224,20 +228,26 @@ Window { } } - /*UserStoryCard { // hrs fixme remove - id: storyCard; + UserStoryCard { + id: storyCardQML; visible: false; visitPlace: function (hifiUrl) { - storyCard.visible = false; + storyCardQML.visible = false; addressLine.text = hifiUrl; toggleOrGo(true); - };*/ + }; + anchors { + verticalCenter: scroll.verticalCenter; + horizontalCenter: scroll.horizontalCenter; + verticalCenterOffset: 50; + } + } Window { width: 750; height: 360; HifiControls.WebView { anchors.fill: parent; - id: storyCard; + id: storyCardHTML; } id: storyCardFrame; @@ -371,7 +381,6 @@ Window { console.log(name, "has bad details", data.details); } } - console.log('hrs fixme id', data.id, typeof data.id, JSON.stringify(data)); return { place_name: name, username: data.username || "", diff --git a/interface/resources/qml/AddressBarDialog.qml~ b/interface/resources/qml/AddressBarDialog.qml~ deleted file mode 100644 index 5da49a1ee1..0000000000 --- a/interface/resources/qml/AddressBarDialog.qml~ +++ /dev/null @@ -1,510 +0,0 @@ -// -// AddressBarDialog.qml -// -// Created by Austin Davis on 2015/04/14 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import Hifi 1.0 -import QtQuick 2.4 -import "controls" -import "styles" -import "windows" -import "hifi" -import "hifi/toolbars" - -Window { - id: root - HifiConstants { id: hifi } - - objectName: "AddressBarDialog" - frame: HiddenFrame {} - hideBackground: true - - shown: false - destroyOnHidden: false - resizable: false - scale: 1.25 // Make this dialog a little larger than normal - - width: addressBarDialog.implicitWidth - height: addressBarDialog.implicitHeight - - onShownChanged: addressBarDialog.observeShownChanged(shown); - Component.onCompleted: { - root.parentChanged.connect(center); - center(); - } - Component.onDestruction: { - root.parentChanged.disconnect(center); - } - - function center() { - // Explicitly center in order to avoid warnings at shutdown - anchors.centerIn = parent; - } - - function goCard(card) { - if (addressBarDialog.useFeed) { - storyCard.imageUrl = card.imageUrl; - storyCard.userName = card.userName; - storyCard.placeName = card.placeName; - storyCard.actionPhrase = card.actionPhrase; - storyCard.timePhrase = card.timePhrase; - storyCard.hifiUrl = card.hifiUrl; - storyCard.visible = true; - return; - } - addressLine.text = card.hifiUrl; - toggleOrGo(true); - } - property var allPlaces: []; - property var allStories: []; - property int cardWidth: 200; - property int cardHeight: 152; - //property string metaverseBase: "https://metaverse.highfidelity.com/api/v1/"; - property string metaverseBase: "http://10.0.0.242:3000/api/v1/"; - - AddressBarDialog { - id: addressBarDialog - implicitWidth: backgroundImage.width - implicitHeight: backgroundImage.height - // The buttons have their button state changed on hover, so we have to manually fix them up here - onBackEnabledChanged: backArrow.buttonState = addressBarDialog.backEnabled ? 1 : 0; - onForwardEnabledChanged: forwardArrow.buttonState = addressBarDialog.forwardEnabled ? 1 : 0; - onUseFeedChanged: { updateFeedState(); } - - ListModel { id: suggestions } - - ListView { - id: scroll - width: backgroundImage.width; - height: cardHeight; - spacing: hifi.layout.spacing; - clip: true; - anchors { - bottom: backgroundImage.top; - bottomMargin: 2 * hifi.layout.spacing; - horizontalCenter: backgroundImage.horizontalCenter - } - model: suggestions; - orientation: ListView.Horizontal; - delegate: Card { - width: cardWidth; - height: cardHeight; - goFunction: goCard; - userName: model.username; - placeName: model.place_name; - hifiUrl: model.place_name + model.path; - imageUrl: model.image_url; - thumbnail: model.thumbnail_url; - action: model.action; - timestamp: model.created_at; - onlineUsers: model.online_users; - hoverThunk: function () { ListView.view.currentIndex = index; } - unhoverThunk: function () { ListView.view.currentIndex = -1; } - } - highlightMoveDuration: -1; - highlightMoveVelocity: -1; - highlight: Rectangle { color: "transparent"; border.width: 4; border.color: "#1DB5ED"; z: 1; } - leftMargin: 50; // Start the first item over be about the same amount as the last item peeks through on the other side. - rightMargin: 50; - } - Image { // Just a visual indicator that the user can swipe the cards over to see more. - source: "../images/Swipe-Icon-single.svg" - width: 50; - anchors { - right: scroll.right; - verticalCenter: scroll.verticalCenter; - } - } - Image { - id: backgroundImage - source: "../images/address-bar.svg" - width: 576 * root.scale - height: 80 * root.scale - property int inputAreaHeight: 56.0 * root.scale // Height of the background's input area - property int inputAreaStep: (height - inputAreaHeight) / 2 - - ToolbarButton { - id: homeButton - imageURL: "../images/home.svg" - buttonState: 1 - defaultState: 1 - hoverState: 2 - onClicked: addressBarDialog.loadHome(); - anchors { - left: parent.left - leftMargin: homeButton.width / 2 - verticalCenter: parent.verticalCenter - } - } - - ToolbarButton { - id: backArrow; - imageURL: "../images/backward.svg"; - hoverState: addressBarDialog.backEnabled ? 2 : 0; - defaultState: addressBarDialog.backEnabled ? 1 : 0; - buttonState: addressBarDialog.backEnabled ? 1 : 0; - onClicked: addressBarDialog.loadBack(); - anchors { - left: homeButton.right - verticalCenter: parent.verticalCenter - } - } - ToolbarButton { - id: forwardArrow; - imageURL: "../images/forward.svg"; - hoverState: addressBarDialog.forwardEnabled ? 2 : 0; - defaultState: addressBarDialog.forwardEnabled ? 1 : 0; - buttonState: addressBarDialog.forwardEnabled ? 1 : 0; - onClicked: addressBarDialog.loadForward(); - anchors { - left: backArrow.right - verticalCenter: parent.verticalCenter - } - } - - // FIXME replace with TextField - TextInput { - id: addressLine - focus: true - anchors { - top: parent.top - bottom: parent.bottom - left: forwardArrow.right - right: placesButton.left - leftMargin: forwardArrow.width - rightMargin: placesButton.width - topMargin: parent.inputAreaStep + hifi.layout.spacing - bottomMargin: parent.inputAreaStep + hifi.layout.spacing - } - font.pixelSize: hifi.fonts.pixelSize * root.scale * 0.75 - helperText: "Go to: place, @user, /path, network address" - helperPixelSize: font.pixelSize * 0.75 - helperItalic: true - onTextChanged: filterChoicesByText() - } - // These two are radio buttons. - ToolbarButton { - id: placesButton - imageURL: "../images/places.svg" - buttonState: 1 - defaultState: addressBarDialog.useFeed ? 0 : 1; - hoverState: addressBarDialog.useFeed ? 2 : -1; - onClicked: addressBarDialog.useFeed ? toggleFeed() : identity() - anchors { - right: feedButton.left; - bottom: addressLine.bottom; - } - } - ToolbarButton { - id: feedButton; - imageURL: "../images/snap-feed.svg"; - buttonState: 0 - defaultState: addressBarDialog.useFeed ? 1 : 0; - hoverState: addressBarDialog.useFeed ? -1 : 2; - onClicked: addressBarDialog.useFeed ? identity() : toggleFeed(); - anchors { - right: parent.right; - bottom: addressLine.bottom; - rightMargin: feedButton.width / 2 - } - } - } - - UserStoryCard { - id: storyCard; - visible: false; - visitPlace: function (hifiUrl) { - storyCard.visible = false; - addressLine.text = hifiUrl; - toggleOrGo(true); - }; - anchors { - verticalCenter: scroll.verticalCenter; - horizontalCenter: scroll.horizontalCenter; - verticalCenterOffset: 50; - } - } - } - - - function toggleFeed() { - addressBarDialog.useFeed = !addressBarDialog.useFeed; - updateFeedState(); - } - function updateFeedState() { - placesButton.buttonState = addressBarDialog.useFeed ? 0 : 1; - feedButton.buttonState = addressBarDialog.useFeed ? 1 : 0; - filterChoicesByText(); - } - function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. - // TODO: make available to other .qml. - var request = new XMLHttpRequest(); - // QT bug: apparently doesn't handle onload. Workaround using readyState. - request.onreadystatechange = function () { - var READY_STATE_DONE = 4; - var HTTP_OK = 200; - if (request.readyState >= READY_STATE_DONE) { - var error = (request.status !== HTTP_OK) && request.status.toString() + ':' + request.statusText, - response = !error && request.responseText, - contentType = !error && request.getResponseHeader('content-type'); - if (!error && contentType.indexOf('application/json') === 0) { - try { - response = JSON.parse(response); - } catch (e) { - error = e; - } - } - cb(error, response); - } - }; - request.open("GET", url, true); - request.send(); - } - function asyncMap(array, iterator, cb) { - // call iterator(element, icb) once for each element of array, and then cb(error, mappedResult) - // when icb(error, mappedElement) has been called by each iterator. - // Calls to iterator are overlapped and map call icb in any order, but the mappedResults are collected in the same - // order as the elements of the array. - // short-circuits if error. Note that iterator MUST be an asynchronous function. (Use setTimeout if necessary.) - var count = array.length, results = []; - if (!count) { - return cb(null, results); - } - array.forEach(function (element, index) { - if (count < 0) { // don't keep iterating after we short-circuit - return; - } - iterator(element, function (error, mapped) { - results[index] = mapped; - if (error || !--count) { - count = 1; // don't cb multiple times if error - cb(error, results); - } - }); - }); - } - // Example: - /*asyncMap([0, 1, 2, 3, 4, 5, 6], function (elt, icb) { - console.log('called', elt); - setTimeout(function () { - console.log('answering', elt); - icb(null, elt); - }, Math.random() * 1000); - }, console.log); */ - - function identity(x) { - return x; - } - - function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey - if (!error && (data.status === 'success')) { - return; - } - if (!error) { // Create a message from the data - error = data.status + ': ' + data.error; - } - if (typeof(error) === 'string') { // Make a proper Error object - error = new Error(error); - } - error.message += ' in ' + url; // Include the url. - cb(error); - return true; - } - - function getPlace(placeData, cb) { // cb(error, side-effected-placeData), after adding path, thumbnails, and description - var url = metaverseBase + 'places/' + placeData.place_name; - getRequest(url, function (error, data) { - if (handleError(url, error, data, cb)) { - return; - } - var place = data.data.place, previews = place.previews; - placeData.path = place.path; - if (previews && previews.thumbnail) { - placeData.thumbnail_url = previews.thumbnail; - } - if (place.description) { - placeData.description = place.description; - placeData.searchText += ' ' + place.description.toUpperCase(); - } - cb(error, placeData); - }); - } - function makeModelData(data, optionalPlaceName) { // create a new obj from data - // ListModel elements will only ever have those properties that are defined by the first obj that is added. - // So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story. - var name = optionalPlaceName || data.place_name, - tags = data.tags || [data.action, data.username], - description = data.description || "", - thumbnail_url = data.thumbnail_url || "", - image_url = thumbnail_url; - if (data.details) { - try { - image_url = JSON.parse(data.details).image_url || thumbnail_url; - } catch (e) { - console.log(name, "has bad details", data.details); - } - } - return { - place_name: name, - username: data.username || "", - path: data.path || "", - created_at: data.created_at || "", - action: data.action || "", - thumbnail_url: thumbnail_url, - image_url: image_url, - - tags: tags, - description: description, - online_users: data.online_users || 0, - - searchText: [name].concat(tags, description || []).join(' ').toUpperCase() - } - } - function mapDomainPlaces(domain, cb) { // cb(error, arrayOfDomainPlaceData) - function addPlace(name, icb) { - getPlace(makeModelData(domain, name), icb); - } - // IWBNI we could get these results in order with most-recent-entered first. - // In any case, we don't really need to preserve the domain.names order in the results. - asyncMap(domain.names || [], addPlace, cb); - } - - function suggestable(place) { - if (addressBarDialog.useFeed) { - return true; - } - return (place.place_name !== AddressManager.hostname) // Not our entry, but do show other entry points to current domain. - && place.thumbnail_url - && place.online_users // at least one present means it's actually online - && place.online_users <= 20; - } - function getDomainPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model - // Each page of results is processed completely before we start on the next page. - // For each page of domains, we process each domain in parallel, and for each domain, process each place name in parallel. - // This gives us minimum latency within the page, but we do preserve the order within the page by using asyncMap and - // only appending the collected results. - var params = [ - 'open', // published hours handle now - // FIXME: should determine if place is actually running - 'restriction=open', // Not by whitelist, etc. FIXME: If logged in, add hifi to the restriction options, in order to include places that require login. - // FIXME add maturity - 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), - 'sort_by=users', - 'sort_order=desc', - 'page=' + pageNumber - ]; - var url = metaverseBase + 'domains/all?' + params.join('&'); - getRequest(url, function (error, data) { - if (handleError(url, error, data, cb)) { - return; - } - asyncMap(data.data.domains, mapDomainPlaces, function (error, pageResults) { - if (error) { - return cb(error); - } - // pageResults is now [ [ placeDataOneForDomainOne, placeDataTwoForDomainOne, ...], [ placeDataTwoForDomainTwo...] ] - pageResults.forEach(function (domainResults) { - allPlaces = allPlaces.concat(domainResults); - if (!addressLine.text && !addressBarDialog.useFeed) { // Don't add if the user is already filtering - domainResults.forEach(function (place) { - if (suggestable(place)) { - suggestions.append(place); - } - }); - } - }); - if (data.current_page < data.total_pages) { - return getDomainPage(pageNumber + 1, cb); - } - cb(); - }); - }); - } - function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model - var url = metaverseBase + 'user_stories?page=' + pageNumber; - getRequest(url, function (error, data) { - if (handleError(url, error, data, cb)) { - return; - } - var stories = data.user_stories.map(function (story) { // explicit single-argument function - return makeModelData(story); - }); - allStories = allStories.concat(stories); - if (!addressLine.text && addressBarDialog.useFeed) { // Don't add if the user is already filtering - stories.forEach(function (story) { - suggestions.append(story); - }); - } - if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now - return getUserStoryPage(pageNumber + 1, cb); - } - cb(); - }); - } - function filterChoicesByText() { - suggestions.clear(); - var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity), - data = addressBarDialog.useFeed ? allStories : allPlaces; - function matches(place) { - if (!words.length) { - return suggestable(place); - } - return words.every(function (word) { - return place.searchText.indexOf(word) >= 0; - }); - } - data.forEach(function (place) { - if (matches(place)) { - suggestions.append(place); - } - }); - } - - function fillDestinations() { - allPlaces = []; - allStories = []; - suggestions.clear(); - getDomainPage(1, function (error) { - console.log('domain query', error || 'ok', allPlaces.length); - }); - getUserStoryPage(1, function (error) { - console.log('user stories query', error || 'ok', allStories.length); - }); - } - - onVisibleChanged: { - if (visible) { - addressLine.forceActiveFocus() - fillDestinations(); - } else { - addressLine.text = "" - } - } - - function toggleOrGo(fromSuggestions) { - if (addressLine.text !== "") { - addressBarDialog.loadAddress(addressLine.text, fromSuggestions) - } - root.shown = false; - } - - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Escape: - case Qt.Key_Back: - root.shown = false - event.accepted = true - break - case Qt.Key_Enter: - case Qt.Key_Return: - toggleOrGo() - event.accepted = true - break - } - } -}