mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-19 08:18:05 +02:00
commit
0b5cdcef42
11 changed files with 332 additions and 14 deletions
|
@ -1083,9 +1083,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
|||
// Setup the domain object to send to the data server
|
||||
QJsonObject domainObject;
|
||||
|
||||
// add the version
|
||||
// add the versions
|
||||
static const QString VERSION_KEY = "version";
|
||||
domainObject[VERSION_KEY] = BuildInfo::VERSION;
|
||||
static const QString PROTOCOL_KEY = "protocol";
|
||||
domainObject[PROTOCOL_KEY] = protocolVersionsSignatureBase64();
|
||||
|
||||
// add networking
|
||||
if (!networkAddress.isEmpty()) {
|
||||
|
@ -1119,7 +1121,12 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
|||
QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact)));
|
||||
|
||||
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
||||
QString path = DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID()));
|
||||
#if DEV_BUILD || PR_BUILD
|
||||
qDebug() << "Domain metadata sent to" << path;
|
||||
qDebug() << "Domain metadata update:" << domainUpdateJSON;
|
||||
#endif
|
||||
DependencyManager::get<AccountManager>()->sendRequest(path,
|
||||
AccountManagerAuth::Optional,
|
||||
QNetworkAccessManager::PutOperation,
|
||||
JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"),
|
||||
|
|
BIN
interface/resources/images/default-domain.gif
Normal file
BIN
interface/resources/images/default-domain.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
|
@ -13,6 +13,7 @@ import QtQuick 2.4
|
|||
import "controls"
|
||||
import "styles"
|
||||
import "windows"
|
||||
import "hifi"
|
||||
|
||||
Window {
|
||||
id: root
|
||||
|
@ -44,11 +45,50 @@ Window {
|
|||
anchors.centerIn = parent;
|
||||
}
|
||||
|
||||
function goCard(card) {
|
||||
addressLine.text = card.userStory.name;
|
||||
toggleOrGo(true);
|
||||
}
|
||||
property var allDomains: [];
|
||||
property var suggestionChoices: [];
|
||||
property var domainsBaseUrl: null;
|
||||
property int cardWidth: 200;
|
||||
property int cardHeight: 152;
|
||||
|
||||
AddressBarDialog {
|
||||
id: addressBarDialog
|
||||
implicitWidth: backgroundImage.width
|
||||
implicitHeight: backgroundImage.height
|
||||
|
||||
Row {
|
||||
width: backgroundImage.width;
|
||||
anchors {
|
||||
bottom: backgroundImage.top;
|
||||
bottomMargin: 2 * hifi.layout.spacing;
|
||||
right: backgroundImage.right;
|
||||
rightMargin: -104; // FIXME
|
||||
}
|
||||
spacing: hifi.layout.spacing;
|
||||
Card {
|
||||
id: s0;
|
||||
width: cardWidth;
|
||||
height: cardHeight;
|
||||
goFunction: goCard
|
||||
}
|
||||
Card {
|
||||
id: s1;
|
||||
width: cardWidth;
|
||||
height: cardHeight;
|
||||
goFunction: goCard
|
||||
}
|
||||
Card {
|
||||
id: s2;
|
||||
width: cardWidth;
|
||||
height: cardHeight;
|
||||
goFunction: goCard
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: backgroundImage
|
||||
source: "../images/address-bar.svg"
|
||||
|
@ -130,22 +170,178 @@ Window {
|
|||
}
|
||||
font.pixelSize: hifi.fonts.pixelSize * root.scale * 0.75
|
||||
helperText: "Go to: place, @user, /path, network address"
|
||||
onTextChanged: 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();
|
||||
}
|
||||
// call iterator(element, icb) once for each element of array, and then cb(error) when icb(error) has been called by each iterator.
|
||||
// short-circuits if error. Note that iterator MUST be an asynchronous function. (Use setTimeout if necessary.)
|
||||
function asyncEach(array, iterator, cb) {
|
||||
var count = array.length;
|
||||
function icb(error) {
|
||||
if (!--count || error) {
|
||||
count = -1; // don't cb multiple times (e.g., if error)
|
||||
cb(error);
|
||||
}
|
||||
}
|
||||
if (!count) {
|
||||
return cb();
|
||||
}
|
||||
array.forEach(function (element) {
|
||||
iterator(element, icb);
|
||||
});
|
||||
}
|
||||
|
||||
function identity(x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
function addPictureToDomain(domainInfo, cb) { // asynchronously add thumbnail and lobby to domainInfo, if available, and cb(error)
|
||||
// This requests data for all the names at once, and just uses the first one to come back.
|
||||
// We might change this to check one at a time, which would be less requests and more latency.
|
||||
asyncEach([domainInfo.name].concat(domainInfo.names || null).filter(identity), function (name, icb) {
|
||||
var url = "https://metaverse.highfidelity.com/api/v1/places/" + name;
|
||||
getRequest(url, function (error, json) {
|
||||
var previews = !error && json.data.place.previews;
|
||||
if (previews) {
|
||||
if (!domainInfo.thumbnail) { // just grab the first one
|
||||
domainInfo.thumbnail = previews.thumbnail;
|
||||
}
|
||||
if (!domainInfo.lobby) {
|
||||
domainInfo.lobby = previews.lobby;
|
||||
}
|
||||
}
|
||||
icb(error);
|
||||
});
|
||||
}, cb);
|
||||
}
|
||||
|
||||
function getDomains(options, cb) { // cb(error, arrayOfData)
|
||||
if (!options.page) {
|
||||
options.page = 1;
|
||||
}
|
||||
if (!domainsBaseUrl) {
|
||||
var domainsOptions = [
|
||||
'open', // published hours handle now
|
||||
'active', // has at least one person connected. FIXME: really want any place that is verified accessible.
|
||||
// FIXME: really want places I'm allowed in, not just open ones.
|
||||
'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',
|
||||
];
|
||||
domainsBaseUrl = "https://metaverse.highfidelity.com/api/v1/domains/all?" + domainsOptions.join('&');
|
||||
}
|
||||
var url = domainsBaseUrl + "&page=" + options.page + "&users=" + options.minUsers + "-" + options.maxUsers;
|
||||
getRequest(url, function (error, json) {
|
||||
if (!error && (json.status !== 'success')) {
|
||||
error = new Error("Bad response: " + JSON.stringify(json));
|
||||
}
|
||||
if (error) {
|
||||
error.message += ' for ' + url;
|
||||
return cb(error);
|
||||
}
|
||||
var domains = json.data.domains;
|
||||
if (json.current_page < json.total_pages) {
|
||||
options.page++;
|
||||
return getDomains(options, function (error, others) {
|
||||
cb(error, domains.concat(others));
|
||||
});
|
||||
}
|
||||
cb(null, domains);
|
||||
});
|
||||
}
|
||||
|
||||
function filterChoicesByText() {
|
||||
function fill1(target, data) {
|
||||
if (!data) {
|
||||
target.visible = false;
|
||||
return;
|
||||
}
|
||||
console.log('suggestion:', JSON.stringify(data));
|
||||
target.userStory = data;
|
||||
target.image.source = data.lobby || target.defaultPicture;
|
||||
target.placeText = data.name;
|
||||
target.usersText = data.online_users + ((data.online_users === 1) ? ' user' : ' users');
|
||||
target.visible = true;
|
||||
}
|
||||
var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity);
|
||||
var filtered = !words.length ? suggestionChoices : allDomains.filter(function (domain) {
|
||||
var text = domain.names.concat(domain.tags).join(' ');
|
||||
if (domain.description) {
|
||||
text += domain.description;
|
||||
}
|
||||
text = text.toUpperCase();
|
||||
return words.every(function (word) {
|
||||
return text.indexOf(word) >= 0;
|
||||
});
|
||||
});
|
||||
fill1(s0, filtered[0]);
|
||||
fill1(s1, filtered[1]);
|
||||
fill1(s2, filtered[2]);
|
||||
}
|
||||
|
||||
function fillDestinations() {
|
||||
allDomains = suggestionChoices = [];
|
||||
getDomains({minUsers: 0, maxUsers: 20}, function (error, domains) {
|
||||
if (error) {
|
||||
console.log('domain query failed:', error);
|
||||
return filterChoicesByText();
|
||||
}
|
||||
var here = AddressManager.hostname; // don't show where we are now.
|
||||
allDomains = domains.filter(function (domain) { return domain.name !== here; });
|
||||
// Whittle down suggestions to those that have at least one user, and try to get pictures.
|
||||
suggestionChoices = allDomains.filter(function (domain) { return domain.online_users; });
|
||||
asyncEach(domains, addPictureToDomain, function (error) {
|
||||
if (error) {
|
||||
console.log('place picture query failed:', error);
|
||||
}
|
||||
// Whittle down more by requiring a picture.
|
||||
suggestionChoices = suggestionChoices.filter(function (domain) { return domain.lobby; });
|
||||
filterChoicesByText();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
addressLine.forceActiveFocus()
|
||||
fillDestinations();
|
||||
} else {
|
||||
addressLine.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
function toggleOrGo() {
|
||||
function toggleOrGo(fromSuggestions) {
|
||||
if (addressLine.text !== "") {
|
||||
addressBarDialog.loadAddress(addressLine.text)
|
||||
addressBarDialog.loadAddress(addressLine.text, fromSuggestions)
|
||||
}
|
||||
root.shown = false;
|
||||
}
|
||||
|
|
96
interface/resources/qml/hifi/Card.qml
Normal file
96
interface/resources/qml/hifi/Card.qml
Normal file
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// Card.qml
|
||||
// qml/hifi
|
||||
//
|
||||
// Displays a clickable card representing a user story or destination.
|
||||
//
|
||||
// Created by Howard Stearns on 7/13/2016
|
||||
// Copyright 2016 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.5
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../styles-uit"
|
||||
|
||||
Rectangle {
|
||||
property var goFunction: null;
|
||||
property var userStory: null;
|
||||
property alias image: lobby;
|
||||
property alias placeText: place.text;
|
||||
property alias usersText: users.text;
|
||||
property int textPadding: 20;
|
||||
property int textSize: 24;
|
||||
property string defaultPicture: "../../images/default-domain.gif";
|
||||
HifiConstants { id: hifi }
|
||||
Image {
|
||||
id: lobby;
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
source: defaultPicture;
|
||||
fillMode: Image.PreserveAspectCrop;
|
||||
// source gets filled in later
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.left: parent.left;
|
||||
onStatusChanged: {
|
||||
if (status == Image.Error) {
|
||||
console.log("source: " + source + ": failed to load " + JSON.stringify(userStory));
|
||||
source = defaultPicture;
|
||||
}
|
||||
}
|
||||
}
|
||||
property int dropHorizontalOffset: 0;
|
||||
property int dropVerticalOffset: 1;
|
||||
property int dropRadius: 2;
|
||||
property int dropSamples: 9;
|
||||
property int dropSpread: 0;
|
||||
DropShadow {
|
||||
source: place;
|
||||
anchors.fill: place;
|
||||
horizontalOffset: dropHorizontalOffset;
|
||||
verticalOffset: dropVerticalOffset;
|
||||
radius: dropRadius;
|
||||
samples: dropSamples;
|
||||
color: hifi.colors.black;
|
||||
spread: dropSpread;
|
||||
}
|
||||
DropShadow {
|
||||
source: users;
|
||||
anchors.fill: users;
|
||||
horizontalOffset: dropHorizontalOffset;
|
||||
verticalOffset: dropVerticalOffset;
|
||||
radius: dropRadius;
|
||||
samples: dropSamples;
|
||||
color: hifi.colors.black;
|
||||
spread: dropSpread;
|
||||
}
|
||||
RalewaySemiBold {
|
||||
id: place;
|
||||
color: hifi.colors.white;
|
||||
size: textSize;
|
||||
anchors {
|
||||
top: parent.top;
|
||||
left: parent.left;
|
||||
margins: textPadding;
|
||||
}
|
||||
}
|
||||
RalewayRegular {
|
||||
id: users;
|
||||
size: textSize;
|
||||
color: hifi.colors.white;
|
||||
anchors {
|
||||
bottom: parent.bottom;
|
||||
right: parent.right;
|
||||
margins: textPadding;
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
acceptedButtons: Qt.LeftButton;
|
||||
onClicked: goFunction(parent);
|
||||
hoverEnabled: true;
|
||||
}
|
||||
}
|
|
@ -40,10 +40,10 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare
|
|||
_forwardEnabled = !(DependencyManager::get<AddressManager>()->getForwardStack().isEmpty());
|
||||
}
|
||||
|
||||
void AddressBarDialog::loadAddress(const QString& address) {
|
||||
void AddressBarDialog::loadAddress(const QString& address, bool fromSuggestions) {
|
||||
qDebug() << "Called LoadAddress with address " << address;
|
||||
if (!address.isEmpty()) {
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(address);
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(address, fromSuggestions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ protected:
|
|||
void displayAddressOfflineMessage();
|
||||
void displayAddressNotFoundMessage();
|
||||
|
||||
Q_INVOKABLE void loadAddress(const QString& address);
|
||||
Q_INVOKABLE void loadAddress(const QString& address, bool fromSuggestions = false);
|
||||
Q_INVOKABLE void loadHome();
|
||||
Q_INVOKABLE void loadBack();
|
||||
Q_INVOKABLE void loadForward();
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "NodeList.h"
|
||||
#include "NetworkLogging.h"
|
||||
#include "UserActivityLogger.h"
|
||||
#include "udt/PacketHeaders.h"
|
||||
|
||||
|
||||
const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager";
|
||||
|
@ -37,6 +38,10 @@ AddressManager::AddressManager() :
|
|||
|
||||
}
|
||||
|
||||
QString AddressManager::protocolVersion() {
|
||||
return protocolVersionsSignatureBase64();
|
||||
}
|
||||
|
||||
bool AddressManager::isConnected() {
|
||||
return DependencyManager::get<NodeList>()->getDomainHandler().isConnected();
|
||||
}
|
||||
|
@ -221,7 +226,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void AddressManager::handleLookupString(const QString& lookupString) {
|
||||
void AddressManager::handleLookupString(const QString& lookupString, bool fromSuggestions) {
|
||||
if (!lookupString.isEmpty()) {
|
||||
// make this a valid hifi URL and handle it off to handleUrl
|
||||
QString sanitizedString = lookupString.trimmed();
|
||||
|
@ -236,7 +241,7 @@ void AddressManager::handleLookupString(const QString& lookupString) {
|
|||
lookupURL = QUrl(lookupString);
|
||||
}
|
||||
|
||||
handleUrl(lookupURL);
|
||||
handleUrl(lookupURL, fromSuggestions ? Suggestions : UserInput);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ class AddressManager : public QObject, public Dependency {
|
|||
Q_PROPERTY(QString hostname READ getHost)
|
||||
Q_PROPERTY(QString pathname READ currentPath)
|
||||
public:
|
||||
Q_INVOKABLE QString protocolVersion();
|
||||
using PositionGetter = std::function<glm::vec3()>;
|
||||
using OrientationGetter = std::function<glm::quat()>;
|
||||
|
||||
|
@ -49,7 +50,8 @@ public:
|
|||
StartupFromSettings,
|
||||
DomainPathResponse,
|
||||
Internal,
|
||||
AttemptedRefresh
|
||||
AttemptedRefresh,
|
||||
Suggestions
|
||||
};
|
||||
|
||||
bool isConnected();
|
||||
|
@ -77,7 +79,7 @@ public:
|
|||
std::function<void()> localSandboxNotRunningDoThat);
|
||||
|
||||
public slots:
|
||||
void handleLookupString(const QString& lookupString);
|
||||
void handleLookupString(const QString& lookupString, bool fromSuggestions = false);
|
||||
|
||||
// we currently expect this to be called from NodeList once handleLookupString has been called with a path
|
||||
bool goToViewpointForPath(const QString& viewpointString, const QString& pathString)
|
||||
|
|
|
@ -178,6 +178,9 @@ void UserActivityLogger::wentTo(AddressManager::LookupTrigger lookupTrigger, QSt
|
|||
case AddressManager::StartupFromSettings:
|
||||
trigger = "StartupFromSettings";
|
||||
break;
|
||||
case AddressManager::Suggestions:
|
||||
trigger = "Suggesions";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -106,8 +106,9 @@ void sendWrongProtocolVersionsSignature(bool sendWrongVersion) {
|
|||
}
|
||||
#endif
|
||||
|
||||
QByteArray protocolVersionsSignature() {
|
||||
static QByteArray protocolVersionSignature;
|
||||
static QByteArray protocolVersionSignature;
|
||||
static QString protocolVersionSignatureBase64;
|
||||
static void ensureProtocolVersionsSignature() {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
QByteArray buffer;
|
||||
|
@ -121,8 +122,11 @@ QByteArray protocolVersionsSignature() {
|
|||
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||
hash.addData(buffer);
|
||||
protocolVersionSignature = hash.result();
|
||||
protocolVersionSignatureBase64 = protocolVersionSignature.toBase64();
|
||||
});
|
||||
|
||||
}
|
||||
QByteArray protocolVersionsSignature() {
|
||||
ensureProtocolVersionsSignature();
|
||||
#if (PR_BUILD || DEV_BUILD)
|
||||
if (sendWrongProtocolVersion) {
|
||||
return QByteArray("INCORRECTVERSION"); // only for debugging version checking
|
||||
|
@ -131,3 +135,7 @@ QByteArray protocolVersionsSignature() {
|
|||
|
||||
return protocolVersionSignature;
|
||||
}
|
||||
QString protocolVersionsSignatureBase64() {
|
||||
ensureProtocolVersionsSignature();
|
||||
return protocolVersionSignatureBase64;
|
||||
}
|
||||
|
|
|
@ -113,6 +113,7 @@ extern const QSet<PacketType> NON_SOURCED_PACKETS;
|
|||
|
||||
PacketVersion versionForPacketType(PacketType packetType);
|
||||
QByteArray protocolVersionsSignature(); /// returns a unqiue signature for all the current protocols
|
||||
QString protocolVersionsSignatureBase64();
|
||||
|
||||
#if (PR_BUILD || DEV_BUILD)
|
||||
void sendWrongProtocolVersionsSignature(bool sendWrongVersion); /// for debugging version negotiation
|
||||
|
|
Loading…
Reference in a new issue