From e75f950ac70587a93471ae1fb0402a7b7d54acca Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 17 Aug 2016 14:12:40 -0700 Subject: [PATCH 1/6] Signal receivedHifiSchemeURL --- interface/src/Application.cpp | 1 + interface/src/Application.h | 1 + interface/src/ui/AddressBarDialog.cpp | 1 + interface/src/ui/AddressBarDialog.h | 1 + 4 files changed, 4 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4077fc0d57..340597f18a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4853,6 +4853,7 @@ bool Application::canAcceptURL(const QString& urlString) const { bool Application::acceptURL(const QString& urlString, bool defaultUpload) { if (urlString.startsWith(HIFI_URL_SCHEME)) { // this is a hifi URL - have the AddressManager handle it + emit receivedHifiSchemeURL(urlString); QMetaObject::invokeMethod(DependencyManager::get().data(), "handleLookupString", Qt::AutoConnection, Q_ARG(const QString&, urlString)); return true; diff --git a/interface/src/Application.h b/interface/src/Application.h index 713febeb83..9fcce66f55 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -266,6 +266,7 @@ signals: void activeDisplayPluginChanged(); void uploadRequest(QString path); + void receivedHifiSchemeURL(QString path); public slots: QVector pasteEntities(float x, float y, float z); diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index e5b4262770..3e84c4c3c5 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -39,6 +39,7 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare _backEnabled = !(DependencyManager::get()->getBackStack().isEmpty()); _forwardEnabled = !(DependencyManager::get()->getForwardStack().isEmpty()); connect(DependencyManager::get().data(), &DialogsManager::setUseFeed, this, &AddressBarDialog::setUseFeed); + connect(qApp, &Application::receivedHifiSchemeURL, this, &AddressBarDialog::receivedHifiSchemeURL); } void AddressBarDialog::loadAddress(const QString& address, bool fromSuggestions) { diff --git a/interface/src/ui/AddressBarDialog.h b/interface/src/ui/AddressBarDialog.h index 10f0e0822a..68a12d4eb0 100644 --- a/interface/src/ui/AddressBarDialog.h +++ b/interface/src/ui/AddressBarDialog.h @@ -33,6 +33,7 @@ signals: void backEnabledChanged(); void forwardEnabledChanged(); void useFeedChanged(); + void receivedHifiSchemeURL(QString url); protected: void displayAddressOfflineMessage(); From d58cc8ddd99814557ed608c809c97c1c613c1335 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 17 Aug 2016 14:15:27 -0700 Subject: [PATCH 2/6] use html card from user_story id, and close things when receiveHifiSchemeUrl --- interface/resources/qml/AddressBarDialog.qml | 34 +- interface/resources/qml/AddressBarDialog.qml~ | 510 ++++++++++++++++++ interface/resources/qml/hifi/Card.qml | 1 + 3 files changed, 541 insertions(+), 4 deletions(-) create mode 100644 interface/resources/qml/AddressBarDialog.qml~ diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index 832ba14013..7c2ef17590 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -15,6 +15,7 @@ import "styles" import "windows" import "hifi" import "hifi/toolbars" +import "controls-uit" as HifiControls Window { id: root @@ -46,15 +47,21 @@ Window { anchors.centerIn = parent; } + function resetAfterTeleport() { + console.log('hrs fixme got reset') + storyCardFrame.shown = root.shown = false; + } function goCard(card) { if (addressBarDialog.useFeed) { - storyCard.imageUrl = card.imageUrl; + /*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.visible = true;*/ + storyCard.url = metaverseBase + "user_stories/" + card.storyId + ".html"; + storyCardFrame.shown = true; return; } addressLine.text = card.hifiUrl; @@ -74,6 +81,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(); } ListModel { id: suggestions } @@ -102,6 +110,7 @@ Window { action: model.action; timestamp: model.created_at; onlineUsers: model.online_users; + storyId: model.metaverseId; hoverThunk: function () { ListView.view.currentIndex = index; } unhoverThunk: function () { ListView.view.currentIndex = -1; } } @@ -114,6 +123,7 @@ Window { Image { // Just a visual indicator that the user can swipe the cards over to see more. source: "../images/Swipe-Icon-single.svg" width: 50; + visible: suggestions.count > 3; anchors { right: scroll.right; verticalCenter: scroll.verticalCenter; @@ -214,14 +224,27 @@ Window { } } - UserStoryCard { + /*UserStoryCard { // hrs fixme remove id: storyCard; visible: false; visitPlace: function (hifiUrl) { storyCard.visible = false; addressLine.text = hifiUrl; toggleOrGo(true); - }; + };*/ + Window { + width: 750; + height: 360; + HifiControls.WebView { + anchors.fill: parent; + id: storyCard; + } + id: storyCardFrame; + + shown: false; + destroyOnCloseButton: false; + pinnable: false; + anchors { verticalCenter: scroll.verticalCenter; horizontalCenter: scroll.horizontalCenter; @@ -348,6 +371,7 @@ 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 || "", @@ -357,6 +381,8 @@ Window { thumbnail_url: thumbnail_url, image_url: image_url, + metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. + tags: tags, description: description, online_users: data.online_users || 0, diff --git a/interface/resources/qml/AddressBarDialog.qml~ b/interface/resources/qml/AddressBarDialog.qml~ new file mode 100644 index 0000000000..5da49a1ee1 --- /dev/null +++ b/interface/resources/qml/AddressBarDialog.qml~ @@ -0,0 +1,510 @@ +// +// 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 + } + } +} diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 278f9330d3..fe162349c1 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -25,6 +25,7 @@ Rectangle { property string thumbnail: defaultThumbnail; property string imageUrl: ""; property var goFunction: null; + property string storyId: ""; property string timePhrase: pastTime(timestamp); property string actionPhrase: makeActionPhrase(action); From 0f415abd6b36fa279ce26137e3f901fec611787f Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 17 Aug 2016 16:56:52 -0700 Subject: [PATCH 3/6] 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 - } - } -} From 5e8722ccf549a1f2b48c93d3dfc8d2c06787e773 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 18 Aug 2016 12:58:00 -0700 Subject: [PATCH 4/6] persist "open feed after" checkbox setting --- scripts/system/html/ShareSnapshot.html | 7 +++++-- scripts/system/snapshot.js | 27 ++++++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/scripts/system/html/ShareSnapshot.html b/scripts/system/html/ShareSnapshot.html index 9098b30121..9b9403cf78 100644 --- a/scripts/system/html/ShareSnapshot.html +++ b/scripts/system/html/ShareSnapshot.html @@ -84,6 +84,9 @@ } function handleShareButtons(shareMsg) { + var openFeed = document.getElementById('openFeed'); + openFeed.checked = shareMsg.openFeedAfterShare; + openFeed.onchange = function () { EventBridge.emitWebEvent(openFeed.checked ? 'setOpenFeedTrue' : 'setOpenFeedFalse'); }; if (!shareMsg.canShare) { // this means you may or may not be logged in, but can't share // because you are not in a public place. @@ -116,10 +119,10 @@ }; // beware of bug: Cannot send objects at top level. (Nested in arrays is fine.) shareSelected = function () { - EventBridge.emitWebEvent(paths.concat({openFeed: document.getElementById("openFeed").checked})); + EventBridge.emitWebEvent(paths); }; doNotShare = function () { - EventBridge.emitWebEvent([{openFeed: document.getElementById("openFeed").checked}]); + EventBridge.emitWebEvent([]); }; snapshotSettings = function () { EventBridge.emitWebEvent("openSettings"); diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 6c9132194e..8e99783ff6 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -20,23 +20,32 @@ var button = toolBar.addButton({ alpha: 0.9, }); +function shouldOpenFeedAfterShare() { + var persisted = Settings.getValue('openFeedAfterShare', true); // might answer true, false, "true", or "false" + return persisted && (persisted !== 'false'); +} function showFeedWindow() { DialogsManager.showFeed(); } -var openFeed, outstanding; +var outstanding; function confirmShare(data) { var dialog = new OverlayWebWindow('Snapshot Review', Script.resolvePath("html/ShareSnapshot.html"), 800, 470); function onMessage(message) { switch (message) { case 'ready': dialog.emitScriptEvent(data); // Send it. - openFeed = false; outstanding = 0; break; case 'openSettings': Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog"); break; + case 'setOpenFeedFalse': + Settings.setValue('openFeedAfterShare', false) + break; + case 'setOpenFeedTrue': + Settings.setValue('openFeedAfterShare', true) + break; default: dialog.webEventReceived.disconnect(onMessage); // I'm not certain that this is necessary. If it is, what do we do on normal close? dialog.close(); @@ -46,11 +55,9 @@ function confirmShare(data) { print('sharing', submessage.localPath); outstanding++; Window.shareSnapshot(submessage.localPath); - } else if (submessage.openFeed) { - openFeed = true; } }); - if (openFeed && !outstanding) { + if (!outstanding && shouldOpenFeedAfterShare()) { showFeedWindow(); } } @@ -67,7 +74,7 @@ function snapshotShared(success) { // for now just print an error. print('snapshot upload/share failed'); } - if ((--outstanding <= 0) && openFeed) { + if ((--outstanding <= 0) && shouldOpenFeedAfterShare()) { showFeedWindow(); } } @@ -114,8 +121,12 @@ function resetButtons(path, notify) { // last element in data array tells dialog whether we can share or not confirmShare([ - { localPath: path }, - { canShare: Boolean(Window.location.placename), isLoggedIn: Account.isLoggedIn() } + { localPath: path }, + { + canShare: Boolean(Window.location.placename), + isLoggedIn: Account.isLoggedIn(), + openFeedAfterShare: shouldOpenFeedAfterShare() + } ]); } From 4492c4e64ecc83134c2cdbcac6a4e6bd8ed57eb3 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 18 Aug 2016 14:19:36 -0700 Subject: [PATCH 5/6] Allow javascript to safely open the correct login window (or answer that it is unneded). --- interface/src/scripting/AccountScriptingInterface.cpp | 5 +++++ interface/src/scripting/AccountScriptingInterface.h | 1 + 2 files changed, 6 insertions(+) diff --git a/interface/src/scripting/AccountScriptingInterface.cpp b/interface/src/scripting/AccountScriptingInterface.cpp index 4090c99ac8..d8533bb769 100644 --- a/interface/src/scripting/AccountScriptingInterface.cpp +++ b/interface/src/scripting/AccountScriptingInterface.cpp @@ -26,6 +26,11 @@ bool AccountScriptingInterface::isLoggedIn() { return accountManager->isLoggedIn(); } +bool AccountScriptingInterface::checkAndSignalForAccessToken() { + auto accountManager = DependencyManager::get(); + return accountManager->checkAndSignalForAccessToken(); +} + QString AccountScriptingInterface::getUsername() { auto accountManager = DependencyManager::get(); if (accountManager->isLoggedIn()) { diff --git a/interface/src/scripting/AccountScriptingInterface.h b/interface/src/scripting/AccountScriptingInterface.h index 49648781ce..0f958f2286 100644 --- a/interface/src/scripting/AccountScriptingInterface.h +++ b/interface/src/scripting/AccountScriptingInterface.h @@ -26,6 +26,7 @@ public slots: static AccountScriptingInterface* getInstance(); QString getUsername(); bool isLoggedIn(); + bool checkAndSignalForAccessToken(); }; #endif // hifi_AccountScriptingInterface_h From aab3b83ad9a138e3356a4851fa9758ec12d74562 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 18 Aug 2016 14:20:53 -0700 Subject: [PATCH 6/6] open login if user tries to share and is not logged in, rather than messaging user that they cannot share --- scripts/system/snapshot.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 8e99783ff6..92b16d31bc 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -32,6 +32,12 @@ var outstanding; function confirmShare(data) { var dialog = new OverlayWebWindow('Snapshot Review', Script.resolvePath("html/ShareSnapshot.html"), 800, 470); function onMessage(message) { + // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: + // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) + // 2. Although we currently use a single image, we would like to take snapshot, a selfie, a 360 etc. all at the + // same time, show the user all of them, and have the user deselect any that they do not want to share. + // So we'll ultimately be receiving a set of objects, perhaps with different post processing for each. + var isLoggedIn, needsLogin = false; switch (message) { case 'ready': dialog.emitScriptEvent(data); // Send it. @@ -50,16 +56,26 @@ function confirmShare(data) { dialog.webEventReceived.disconnect(onMessage); // I'm not certain that this is necessary. If it is, what do we do on normal close? dialog.close(); dialog.deleteLater(); + isLoggedIn = Account.isLoggedIn(); message.forEach(function (submessage) { + if (submessage.share && !isLoggedIn) { + needsLogin = true; + submessage.share = false; + } if (submessage.share) { print('sharing', submessage.localPath); outstanding++; Window.shareSnapshot(submessage.localPath); + } else { + print('not sharing', submessage.localPath); } }); if (!outstanding && shouldOpenFeedAfterShare()) { showFeedWindow(); } + if (needsLogin) { // after the possible feed, so that the login is on top + Account.checkAndSignalForAccessToken(); + } } } dialog.webEventReceived.connect(onMessage); @@ -124,7 +140,7 @@ function resetButtons(path, notify) { { localPath: path }, { canShare: Boolean(Window.location.placename), - isLoggedIn: Account.isLoggedIn(), + isLoggedIn: true, // Just have the dialog act as though we are. To be removed at both ends later. openFeedAfterShare: shouldOpenFeedAfterShare() } ]);