mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge branch 'master' of https://github.com/highfidelity/hifi into entityItemCleanup
This commit is contained in:
commit
04ee1adabd
312 changed files with 9632 additions and 3422 deletions
|
@ -68,7 +68,7 @@ module.exports = {
|
|||
"eqeqeq": ["error", "always"],
|
||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||
"keyword-spacing": ["error", { "before": true, "after": true }],
|
||||
"max-len": ["error", 192, 4],
|
||||
"max-len": ["error", 128, 4],
|
||||
"new-cap": ["error"],
|
||||
"no-floating-decimal": ["error"],
|
||||
//"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }],
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -64,6 +64,10 @@ gvr-interface/libs/*
|
|||
# ignore files for various dev environments
|
||||
TAGS
|
||||
*.sw[po]
|
||||
*.qmlc
|
||||
|
||||
# ignore QML compilation output
|
||||
*.qmlc
|
||||
|
||||
# ignore node files for the console
|
||||
node_modules
|
||||
|
|
|
@ -19,6 +19,10 @@ Documentation is available at [docs.highfidelity.com](https://docs.highfidelity.
|
|||
|
||||
There is also detailed [documentation on our coding standards](https://wiki.highfidelity.com/wiki/Coding_Standards).
|
||||
|
||||
Contributor License Agreement (CLA)
|
||||
=========
|
||||
Technology companies frequently receive and use code from contributors outside the company's development team. Outside code can be a tremendous resource, but it also carries responsibility. Best practice for accepting outside contributions consists of an Apache-type Contributor License Agreement (CLA). We have modeled the High Fidelity CLA after the CLA that Google presents to developers for contributions to their projects. This CLA does not transfer ownership of code, instead simply granting a non-exclusive right for High Fidelity to use the code you’ve contributed. In that regard, you should be sure you have permission if the work relates to or uses the resources of a company that you work for. You will be asked to sign our CLA when you create your first PR or when the CLA is updated. You can also [review it here](https://gist.githubusercontent.com/hifi-gustavo/fef8f06a8233d42a0040d45c3efb97a9/raw/9981827eb94f0b18666083670b6f6a02929fb402/High%2520Fidelity%2520CLA). We sincerely appreciate your contribution and efforts toward the success of the platform.
|
||||
|
||||
Build Instructions
|
||||
=========
|
||||
All information required to build is found in the [build guide](BUILD.md).
|
||||
|
|
|
@ -28,6 +28,10 @@
|
|||
const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor";
|
||||
const int WAIT_FOR_CHILD_MSECS = 1000;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
HANDLE PROCESS_GROUP = createProcessGroup();
|
||||
#endif
|
||||
|
||||
AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmentClientForks,
|
||||
const unsigned int minAssignmentClientForks,
|
||||
const unsigned int maxAssignmentClientForks,
|
||||
|
@ -202,6 +206,10 @@ void AssignmentClientMonitor::spawnChildClient() {
|
|||
assignmentClient->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
assignmentClient->start(QCoreApplication::applicationFilePath(), _childArguments);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
addProcessToGroup(PROCESS_GROUP, assignmentClient->processId());
|
||||
#endif
|
||||
|
||||
QString stdoutPath, stderrPath;
|
||||
|
||||
if (_wantsChildFileLogging) {
|
||||
|
|
|
@ -50,9 +50,9 @@ static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
|
|||
|
||||
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
||||
|
||||
static const QStringList BAKEABLE_MODEL_EXTENSIONS = {"fbx"};
|
||||
static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" };
|
||||
static QStringList BAKEABLE_TEXTURE_EXTENSIONS;
|
||||
static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {"js"};
|
||||
static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {};
|
||||
static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
|
||||
static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
|
||||
static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js";
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <UUID.h>
|
||||
#include <CPUDetect.h>
|
||||
|
||||
#include "AudioLogging.h"
|
||||
#include "AudioHelpers.h"
|
||||
#include "AudioRingBuffer.h"
|
||||
#include "AudioMixerClientData.h"
|
||||
|
@ -130,7 +131,7 @@ void AudioMixer::queueReplicatedAudioPacket(QSharedPointer<ReceivedMessage> mess
|
|||
PacketType rewrittenType = PacketTypeEnum::getReplicatedPacketMapping().key(message->getType());
|
||||
|
||||
if (rewrittenType == PacketType::Unknown) {
|
||||
qDebug() << "Cannot unwrap replicated packet type not present in REPLICATED_PACKET_WRAPPING";
|
||||
qCDebug(audio) << "Cannot unwrap replicated packet type not present in REPLICATED_PACKET_WRAPPING";
|
||||
}
|
||||
|
||||
auto replicatedMessage = QSharedPointer<ReceivedMessage>::create(audioData, rewrittenType,
|
||||
|
@ -345,7 +346,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
|
||||
void AudioMixer::run() {
|
||||
|
||||
qDebug() << "Waiting for connection to domain to request settings from domain-server.";
|
||||
qCDebug(audio) << "Waiting for connection to domain to request settings from domain-server.";
|
||||
|
||||
// wait until we have the domain-server settings, otherwise we bail
|
||||
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||
|
@ -502,14 +503,14 @@ void AudioMixer::throttle(std::chrono::microseconds duration, int frame) {
|
|||
int proportionalTerm = 1 + (_trailingMixRatio - TARGET) / 0.1f;
|
||||
_throttlingRatio += THROTTLE_RATE * proportionalTerm;
|
||||
_throttlingRatio = std::min(_throttlingRatio, 1.0f);
|
||||
qDebug("audio-mixer is struggling (%f mix/sleep) - throttling %f of streams",
|
||||
(double)_trailingMixRatio, (double)_throttlingRatio);
|
||||
qCDebug(audio) << "audio-mixer is struggling (" << _trailingMixRatio << "mix/sleep) - throttling"
|
||||
<< _throttlingRatio << "of streams";
|
||||
} else if (_throttlingRatio > 0.0f && _trailingMixRatio <= BACKOFF_TARGET) {
|
||||
int proportionalTerm = 1 + (TARGET - _trailingMixRatio) / 0.2f;
|
||||
_throttlingRatio -= BACKOFF_RATE * proportionalTerm;
|
||||
_throttlingRatio = std::max(_throttlingRatio, 0.0f);
|
||||
qDebug("audio-mixer is recovering (%f mix/sleep) - throttling %f of streams",
|
||||
(double)_trailingMixRatio, (double)_throttlingRatio);
|
||||
qCDebug(audio) << "audio-mixer is recovering (" << _trailingMixRatio << "mix/sleep) - throttling"
|
||||
<< _throttlingRatio << "of streams";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -534,7 +535,7 @@ void AudioMixer::clearDomainSettings() {
|
|||
}
|
||||
|
||||
void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
||||
qDebug() << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled");
|
||||
qCDebug(audio) << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled");
|
||||
|
||||
if (settingsObject.contains(AUDIO_THREADING_GROUP_KEY)) {
|
||||
QJsonObject audioThreadingGroupObject = settingsObject[AUDIO_THREADING_GROUP_KEY].toObject();
|
||||
|
@ -557,7 +558,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic_jitter_buffer";
|
||||
bool enableDynamicJitterBuffer = audioBufferGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
|
||||
if (enableDynamicJitterBuffer) {
|
||||
qDebug() << "Enabling dynamic jitter buffers.";
|
||||
qCDebug(audio) << "Enabling dynamic jitter buffers.";
|
||||
|
||||
bool ok;
|
||||
const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static_desired_jitter_buffer_frames";
|
||||
|
@ -565,9 +566,9 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
if (!ok) {
|
||||
_numStaticJitterFrames = InboundAudioStream::DEFAULT_STATIC_JITTER_FRAMES;
|
||||
}
|
||||
qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
|
||||
qCDebug(audio) << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
|
||||
} else {
|
||||
qDebug() << "Disabling dynamic jitter buffers.";
|
||||
qCDebug(audio) << "Disabling dynamic jitter buffers.";
|
||||
_numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES;
|
||||
}
|
||||
|
||||
|
@ -621,7 +622,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) {
|
||||
QString codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString();
|
||||
_codecPreferenceOrder = codecPreferenceOrder.split(",");
|
||||
qDebug() << "Codec preference order changed to" << _codecPreferenceOrder;
|
||||
qCDebug(audio) << "Codec preference order changed to" << _codecPreferenceOrder;
|
||||
}
|
||||
|
||||
const QString ATTENATION_PER_DOULING_IN_DISTANCE = "attenuation_per_doubling_in_distance";
|
||||
|
@ -630,7 +631,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
float attenuation = audioEnvGroupObject[ATTENATION_PER_DOULING_IN_DISTANCE].toString().toFloat(&ok);
|
||||
if (ok) {
|
||||
_attenuationPerDoublingInDistance = attenuation;
|
||||
qDebug() << "Attenuation per doubling in distance changed to" << _attenuationPerDoublingInDistance;
|
||||
qCDebug(audio) << "Attenuation per doubling in distance changed to" << _attenuationPerDoublingInDistance;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -640,7 +641,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
float noiseMutingThreshold = audioEnvGroupObject[NOISE_MUTING_THRESHOLD].toString().toFloat(&ok);
|
||||
if (ok) {
|
||||
_noiseMutingThreshold = noiseMutingThreshold;
|
||||
qDebug() << "Noise muting threshold changed to" << _noiseMutingThreshold;
|
||||
qCDebug(audio) << "Noise muting threshold changed to" << _noiseMutingThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -680,8 +681,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
glm::vec3 dimensions(xMax - xMin, yMax - yMin, zMax - zMin);
|
||||
AABox zoneAABox(corner, dimensions);
|
||||
_audioZones.insert(zone, zoneAABox);
|
||||
qDebug() << "Added zone:" << zone << "(corner:" << corner
|
||||
<< ", dimensions:" << dimensions << ")";
|
||||
qCDebug(audio) << "Added zone:" << zone << "(corner:" << corner << ", dimensions:" << dimensions << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -712,7 +712,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
_audioZones.contains(settings.source) && _audioZones.contains(settings.listener)) {
|
||||
|
||||
_zoneSettings.push_back(settings);
|
||||
qDebug() << "Added Coefficient:" << settings.source << settings.listener << settings.coefficient;
|
||||
qCDebug(audio) << "Added Coefficient:" << settings.source << settings.listener << settings.coefficient;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -745,7 +745,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
|
||||
_zoneReverbSettings.push_back(settings);
|
||||
|
||||
qDebug() << "Added Reverb:" << zone << reverbTime << wetLevel;
|
||||
qCDebug(audio) << "Added Reverb:" << zone << reverbTime << wetLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "InjectedAudioStream.h"
|
||||
|
||||
#include "AudioLogging.h"
|
||||
#include "AudioHelpers.h"
|
||||
#include "AudioMixer.h"
|
||||
#include "AudioMixerClientData.h"
|
||||
|
@ -132,7 +133,7 @@ void AudioMixerClientData::optionallyReplicatePacket(ReceivedMessage& message, c
|
|||
if (PacketTypeEnum::getReplicatedPacketMapping().key(message.getType()) != PacketType::Unknown) {
|
||||
mirroredType = message.getType();
|
||||
} else {
|
||||
qDebug() << "Packet passed to optionallyReplicatePacket was not a replicatable type - returning";
|
||||
qCDebug(audio) << "Packet passed to optionallyReplicatePacket was not a replicatable type - returning";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -189,8 +190,16 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const
|
|||
uint8_t packedGain;
|
||||
message.readPrimitive(&packedGain);
|
||||
float gain = unpackFloatGainFromByte(packedGain);
|
||||
hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain);
|
||||
qDebug() << "Setting gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain;
|
||||
|
||||
if (avatarUuid.isNull()) {
|
||||
// set the MASTER avatar gain
|
||||
setMasterAvatarGain(gain);
|
||||
qCDebug(audio) << "Setting MASTER avatar gain for " << uuid << " to " << gain;
|
||||
} else {
|
||||
// set the per-source avatar gain
|
||||
hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain);
|
||||
qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixerClientData::parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
|
||||
|
@ -276,7 +285,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
|
||||
auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStaticJitterFrames());
|
||||
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
|
||||
qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
||||
qCDebug(audio) << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
||||
|
||||
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec,
|
||||
this, &AudioMixerClientData::handleMismatchAudioFormat);
|
||||
|
@ -315,7 +324,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
|
||||
#if INJECTORS_SUPPORT_CODECS
|
||||
injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
qDebug() << "creating new injectorStream... codec:" << _selectedCodecName;
|
||||
qCDebug(audio) << "creating new injectorStream... codec:" << _selectedCodecName;
|
||||
#endif
|
||||
|
||||
auto emplaced = _audioStreams.emplace(
|
||||
|
@ -339,8 +348,8 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
auto parseResult = matchingStream->parseData(message);
|
||||
|
||||
if (matchingStream->getOverflowCount() > overflowBefore) {
|
||||
qDebug() << "Just overflowed on stream from" << message.getSourceID() << "at" << message.getSenderSockAddr();
|
||||
qDebug() << "This stream is for" << (isMicStream ? "microphone audio" : "injected audio");
|
||||
qCDebug(audio) << "Just overflowed on stream from" << message.getSourceID() << "at" << message.getSenderSockAddr();
|
||||
qCDebug(audio) << "This stream is for" << (isMicStream ? "microphone audio" : "injected audio");
|
||||
}
|
||||
|
||||
return parseResult;
|
||||
|
@ -689,7 +698,7 @@ void AudioMixerClientData::setupCodecForReplicatedAgent(QSharedPointer<ReceivedM
|
|||
auto codecString = message->readString();
|
||||
|
||||
if (codecString != _selectedCodecName) {
|
||||
qDebug() << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID())
|
||||
qCDebug(audio) << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID())
|
||||
<< "-" << codecString;
|
||||
|
||||
const std::pair<QString, CodecPluginPointer> codec = AudioMixer::negotiateCodec({ codecString });
|
||||
|
|
|
@ -83,6 +83,9 @@ public:
|
|||
// uses randomization to have the AudioMixer send a stats packet to this node around every second
|
||||
bool shouldSendStats(int frameNumber);
|
||||
|
||||
float getMasterAvatarGain() const { return _masterAvatarGain; }
|
||||
void setMasterAvatarGain(float gain) { _masterAvatarGain = gain; }
|
||||
|
||||
AudioLimiter audioLimiter;
|
||||
|
||||
void setupCodec(CodecPluginPointer codec, const QString& codecName);
|
||||
|
@ -175,6 +178,8 @@ private:
|
|||
|
||||
int _frameToSendStats { 0 };
|
||||
|
||||
float _masterAvatarGain { 1.0f }; // per-listener mixing gain, applied only to avatars
|
||||
|
||||
CodecPluginPointer _codec;
|
||||
QString _selectedCodecName;
|
||||
Encoder* _encoder{ nullptr }; // for outbound mixed stream
|
||||
|
|
|
@ -48,8 +48,8 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData&
|
|||
// mix helpers
|
||||
inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition);
|
||||
inline float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition, bool isEcho);
|
||||
inline float computeGain(const AudioMixerClientData& listenerNodeData, const AvatarAudioStream& listeningNodeStream,
|
||||
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, bool isEcho);
|
||||
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition);
|
||||
|
||||
|
@ -266,7 +266,7 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
|
|||
glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition();
|
||||
|
||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||
float gain = computeGain(listeningNodeStream, streamToAdd, relativePosition, isEcho);
|
||||
float gain = computeGain(listenerNodeData, listeningNodeStream, streamToAdd, relativePosition, isEcho);
|
||||
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
|
||||
const int HRTF_DATASET_INDEX = 1;
|
||||
|
||||
|
@ -484,10 +484,12 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi
|
|||
// when throttling, as close streams are expected to be heard by a user
|
||||
float distance = glm::length(relativePosition);
|
||||
return gain / distance;
|
||||
|
||||
// avatar: skip master gain - it is constant for all streams
|
||||
}
|
||||
|
||||
float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition, bool isEcho) {
|
||||
float computeGain(const AudioMixerClientData& listenerNodeData, const AvatarAudioStream& listeningNodeStream,
|
||||
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, bool isEcho) {
|
||||
float gain = 1.0f;
|
||||
|
||||
// injector: apply attenuation
|
||||
|
@ -507,6 +509,9 @@ float computeGain(const AvatarAudioStream& listeningNodeStream, const Positional
|
|||
float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION + (angleOfDelivery * (OFF_AXIS_ATTENUATION_STEP / PI_OVER_TWO));
|
||||
|
||||
gain *= offAxisCoefficient;
|
||||
|
||||
// apply master gain, only to avatars
|
||||
gain *= listenerNodeData.getMasterAvatarGain();
|
||||
}
|
||||
|
||||
auto& audioZones = AudioMixer::getAudioZones();
|
||||
|
|
|
@ -41,8 +41,15 @@ EntityServer::EntityServer(ReceivedMessage& message) :
|
|||
DependencyManager::set<ScriptCache>();
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics, PacketType::ChallengeOwnership },
|
||||
this, "handleEntityPacket");
|
||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
|
||||
PacketType::EntityEdit,
|
||||
PacketType::EntityErase,
|
||||
PacketType::EntityPhysics,
|
||||
PacketType::ChallengeOwnership,
|
||||
PacketType::ChallengeOwnershipRequest,
|
||||
PacketType::ChallengeOwnershipReply },
|
||||
this,
|
||||
"handleEntityPacket");
|
||||
|
||||
connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification);
|
||||
_dynamicDomainVerificationTimer.setSingleShot(true);
|
||||
|
@ -459,7 +466,7 @@ void EntityServer::startDynamicDomainVerification() {
|
|||
EntityItemPointer entity = tree->findEntityByEntityItemID(i.value());
|
||||
|
||||
if (entity) {
|
||||
if (!entity->verifyStaticCertificateProperties()) {
|
||||
if (!entity->getProperties().verifyStaticCertificateProperties()) {
|
||||
qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed"
|
||||
<< "static certificate verification.";
|
||||
// Delete the entity if it doesn't pass static certificate verification
|
||||
|
|
|
@ -23,6 +23,17 @@ EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedN
|
|||
{
|
||||
connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::editingEntityPointer, this, &EntityTreeSendThread::editingEntityPointer, Qt::QueuedConnection);
|
||||
connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::deletingEntityPointer, this, &EntityTreeSendThread::deletingEntityPointer, Qt::QueuedConnection);
|
||||
|
||||
// connect to connection ID change on EntityNodeData so we can clear state for this receiver
|
||||
auto nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
|
||||
connect(nodeData, &EntityNodeData::incomingConnectionIDChanged, this, &EntityTreeSendThread::resetState);
|
||||
}
|
||||
|
||||
void EntityTreeSendThread::resetState() {
|
||||
qCDebug(entities) << "Clearing known EntityTreeSendThread state for" << _nodeUuid;
|
||||
|
||||
_knownState.clear();
|
||||
_traversal.reset();
|
||||
}
|
||||
|
||||
void EntityTreeSendThread::preDistributionProcessing() {
|
||||
|
@ -175,7 +186,7 @@ bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filte
|
|||
return parentWasNew || ancestorsWereNew;
|
||||
}
|
||||
|
||||
// since we didn't have a parent niether of our parents or ancestors could be new additions
|
||||
// since we didn't have a parent, neither of our parents or ancestors could be new additions
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -204,7 +215,9 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
|
|||
return hasNewChild || hasNewDescendants;
|
||||
}
|
||||
|
||||
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
|
||||
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum) {
|
||||
|
||||
DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum);
|
||||
// there are three types of traversal:
|
||||
//
|
||||
|
@ -423,12 +436,19 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
|
|||
uint64_t sendTime = usecTimestampNow();
|
||||
auto nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
|
||||
nodeData->stats.encodeStarted();
|
||||
auto entityNode = _node.toStrongRef();
|
||||
auto entityNodeData = static_cast<EntityNodeData*>(entityNode->getLinkedData());
|
||||
while(!_sendQueue.empty()) {
|
||||
PrioritizedEntity queuedItem = _sendQueue.top();
|
||||
EntityItemPointer entity = queuedItem.getEntity();
|
||||
if (entity) {
|
||||
// Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again
|
||||
if (entity->matchesJSONFilters(jsonFilters)) {
|
||||
bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters);
|
||||
if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entity->getID())) {
|
||||
if (!jsonFilters.isEmpty() && entityMatchesFilters) {
|
||||
// Record explicitly filtered-in entity so that extra entities can be flagged.
|
||||
entityNodeData->insertSentFilteredEntity(entity->getID());
|
||||
}
|
||||
OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);
|
||||
|
||||
if (appendEntityState != OctreeElement::COMPLETED) {
|
||||
|
|
|
@ -33,12 +33,16 @@ protected:
|
|||
void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
|
||||
bool viewFrustumChanged, bool isFullScene) override;
|
||||
|
||||
private slots:
|
||||
void resetState(); // clears our known state forcing entities to appear unsent
|
||||
|
||||
private:
|
||||
// the following two methods return booleans to indicate if any extra flagged entities were new additions to set
|
||||
bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
|
||||
bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
|
||||
|
||||
void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum);
|
||||
void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum);
|
||||
bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
|
||||
|
||||
void preDistributionProcessing() override;
|
||||
|
|
|
@ -96,6 +96,14 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
|
|||
_myServer->getOctree()->withWriteLock([&] {
|
||||
_myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode);
|
||||
});
|
||||
} else if (packetType == PacketType::ChallengeOwnershipRequest) {
|
||||
_myServer->getOctree()->withWriteLock([&] {
|
||||
_myServer->getOctree()->processChallengeOwnershipRequestPacket(*message, sendingNode);
|
||||
});
|
||||
} else if (packetType == PacketType::ChallengeOwnershipReply) {
|
||||
_myServer->getOctree()->withWriteLock([&] {
|
||||
_myServer->getOctree()->processChallengeOwnershipReplyPacket(*message, sendingNode);
|
||||
});
|
||||
} else if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
|
||||
PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket);
|
||||
_receivedPacketCount++;
|
||||
|
|
|
@ -82,8 +82,12 @@ bool OctreeSendThread::process() {
|
|||
if (auto node = _node.lock()) {
|
||||
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(node->getLinkedData());
|
||||
|
||||
// Sometimes the node data has not yet been linked, in which case we can't really do anything
|
||||
if (nodeData && !nodeData->isShuttingDown()) {
|
||||
// If we don't have the OctreeQueryNode at all
|
||||
// or it's uninitialized because we haven't received a query yet from the client
|
||||
// or we don't know where we should send packets for this node
|
||||
// or we're shutting down
|
||||
// then we can't send an entity data packet
|
||||
if (nodeData && nodeData->hasReceivedFirstQuery() && node->getActiveSocket() && !nodeData->isShuttingDown()) {
|
||||
bool viewFrustumChanged = nodeData->updateCurrentViewFrustum();
|
||||
packetDistributor(node, nodeData, viewFrustumChanged);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,8 @@ protected:
|
|||
OctreePacketData _packetData;
|
||||
QWeakPointer<Node> _node;
|
||||
OctreeServer* _myServer { nullptr };
|
||||
|
||||
QUuid _nodeUuid;
|
||||
|
||||
private:
|
||||
/// Called before a packetDistributor pass to allow for pre-distribution processing
|
||||
virtual void preDistributionProcessing() {};
|
||||
|
@ -71,8 +72,6 @@ private:
|
|||
virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene);
|
||||
virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); }
|
||||
|
||||
QUuid _nodeUuid;
|
||||
|
||||
int _truePacketsSent { 0 }; // available for debug stats
|
||||
int _trueBytesSent { 0 }; // available for debug stats
|
||||
int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition
|
||||
|
|
76
cmake/externals/hifiAudioCodec/CMakeLists.txt
vendored
76
cmake/externals/hifiAudioCodec/CMakeLists.txt
vendored
|
@ -5,43 +5,41 @@ set(EXTERNAL_NAME hifiAudioCodec)
|
|||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (NOT ANDROID)
|
||||
|
||||
if (WIN32 OR APPLE)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-1.zip
|
||||
URL_MD5 23ec3fe51eaa155ea159a4971856fc13
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
else ()
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux.zip
|
||||
URL_MD5 7d37914a18aa4de971d2f45dd3043bde
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
endif()
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
|
||||
elseif(APPLE)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
|
||||
elseif(NOT ANDROID)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-win-2.0.zip)
|
||||
set(DOWNLOAD_MD5 9199d4dbd6b16bed736b235efe980e67)
|
||||
elseif (APPLE)
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-mac-2.0.zip)
|
||||
set(DOWNLOAD_MD5 21649881e7d5dc94f922179be96f76ba)
|
||||
elseif (ANDROID)
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-android-2.0.zip)
|
||||
set(DOWNLOAD_MD5 aef2a852600d498d58aa586668191683)
|
||||
elseif (UNIX)
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux-2.0.zip)
|
||||
set(DOWNLOAD_MD5 67fb7755f9bcafb98a9fceea53bc7481)
|
||||
else()
|
||||
return()
|
||||
endif()
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL ${DOWNLOAD_URL}
|
||||
URL_MD5 ${DOWNLOAD_MD5}
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
|
||||
else()
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
|
||||
endif()
|
||||
|
|
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip
|
||||
URL_MD5 94f4765bdbcd53cd099f349ae031e769
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip
|
||||
URL_MD5 4f40e49715a420fb67b45b9cee19052c
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -60,7 +60,7 @@ if (WIN32 AND NOT CYGWIN)
|
|||
select_library_configurations(LIB_EAY)
|
||||
select_library_configurations(SSL_EAY)
|
||||
set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY})
|
||||
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS})
|
||||
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} NO_DEFAULT_PATH)
|
||||
endif()
|
||||
else()
|
||||
|
||||
|
|
|
@ -19,12 +19,13 @@
|
|||
Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.<br>
|
||||
Note: <strong>Your domain's content will be replaced by the content you upload</strong>, but the backup files of your domain's content will not immediately be changed.
|
||||
</p>
|
||||
<p>
|
||||
If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:<br>
|
||||
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
</p>
|
||||
<p>If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:</p>
|
||||
<label class="control-label">Windows</label>
|
||||
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<label class="control-label">OSX</label>
|
||||
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<label class="control-label">Linux</label>
|
||||
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<br>
|
||||
<input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
|
||||
<br>
|
||||
|
|
|
@ -80,11 +80,23 @@ span.port {
|
|||
display: none;
|
||||
}
|
||||
|
||||
#setup-sidebar.affix {
|
||||
/* This overrides a case where going to the bottom of the page,
|
||||
* then scrolling up, causes `position: relative` to be added to the style
|
||||
*/
|
||||
position: fixed !important;
|
||||
@media (min-width: 768px) {
|
||||
#setup-sidebar.affix {
|
||||
/* This overrides a case where going to the bottom of the page,
|
||||
* then scrolling up, causes `position: relative` to be added to the style
|
||||
*/
|
||||
position: fixed !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
#setup-sidebar.affix {
|
||||
position: static !important;
|
||||
}
|
||||
|
||||
#setup-sidebar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
#setup-sidebar button {
|
||||
|
@ -302,12 +314,12 @@ table .headers + .headers td {
|
|||
}
|
||||
|
||||
.account-connected-header {
|
||||
vertical-align: middle;
|
||||
color: #6FCF97;
|
||||
font-size: 30px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#visit-domain-link,
|
||||
.blue-link {
|
||||
font-size: 14px;
|
||||
text-decoration-line: underline;
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<li><a href="/settings/">Settings</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-right navbar-nav">
|
||||
<li><a id="visit-domain-link" target="_blank" style="display: none;">Visit domain in VR</a></li>
|
||||
<li><a id="visit-domain-link" class="blue-link" target="_blank" style="display: none;">Visit domain in VR</a></li>
|
||||
<li><a href="#" id="restart-server"><span class="glyphicon glyphicon-refresh"></span> Restart</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -62,26 +62,25 @@ var Strings = {
|
|||
// dialog with new path still set, allowing them to retry immediately, and without
|
||||
// having to type the new path in again.
|
||||
EDIT_PLACE_TITLE: "Modify Viewpoint or Path",
|
||||
EDIT_PLACE_ERROR: "Failed to update place path. Please try again.",
|
||||
EDIT_PLACE_ERROR: "Failed to update Viewpoint or Path for this Place Name. Please try again.",
|
||||
EDIT_PLACE_CONFIRM_BUTTON: "Save",
|
||||
EDIT_PLACE_CONFIRM_BUTTON_PENDING: "Saving...",
|
||||
EDIT_PLACE_CANCEL_BUTTON: "Cancel",
|
||||
|
||||
REMOVE_PLACE_TITLE: "Are you sure you want to remove <strong>{{place}}</strong>?",
|
||||
REMOVE_PLACE_ERROR: "Failed to remove place. Please try again.",
|
||||
REMOVE_PLACE_DELETE_BUTTON: "Delete",
|
||||
REMOVE_PLACE_TITLE: "Are you sure you want to remove <strong>{{place}}</strong> and its path information?",
|
||||
REMOVE_PLACE_ERROR: "Failed to remove Place Name and its Path information.",
|
||||
REMOVE_PLACE_DELETE_BUTTON: "This action removes your Place Name",
|
||||
REMOVE_PLACE_DELETE_BUTTON_PENDING: "Deleting...",
|
||||
REMOVE_PLACE_CANCEL_BUTTON: "Cancel",
|
||||
|
||||
ADD_PLACE_TITLE: "Choose a place",
|
||||
ADD_PLACE_MESSAGE: "Choose the High Fidelity place to point at this domain server.",
|
||||
ADD_PLACE_CONFIRM_BUTTON: "Choose place",
|
||||
ADD_PLACE_MESSAGE: "Choose a Place Name that you own or register a new Place Name.",
|
||||
ADD_PLACE_CONFIRM_BUTTON: "Save",
|
||||
ADD_PLACE_CONFIRM_BUTTON_PENDING: "Saving...",
|
||||
ADD_PLACE_CANCEL_BUTTON: "Cancel",
|
||||
ADD_PLACE_UNKNOWN_ERROR: "There was an error adding this place name.",
|
||||
ADD_PLACE_UNKNOWN_ERROR: "There was an error adding this Place Name. Try saving again",
|
||||
|
||||
ADD_PLACE_NO_PLACES_MESSAGE: "<p>You do not have any places in your High Fidelity account."
|
||||
+ "<br/><br/>Go to your <a href='https://metaverse.highfidelity.com/user/places/new'>places page</a> to create a new one. Once your place is created re-open this dialog to select it.</p>",
|
||||
ADD_PLACE_NO_PLACES_MESSAGE: "You don't have any Place Names registered. Once you have a Place Name, reopen this window to select it.",
|
||||
ADD_PLACE_NO_PLACES_BUTTON: "Create new place",
|
||||
ADD_PLACE_UNABLE_TO_LOAD_ERROR: "We were unable to load your place names. Please try again later.",
|
||||
ADD_PLACE_LOADING_DIALOG: "Loading your places...",
|
||||
|
@ -146,187 +145,256 @@ function sendUpdatePlaceRequest(id, path, domainID, clearDomainID, onSuccess, on
|
|||
});
|
||||
}
|
||||
|
||||
var pendingDomainRequest = null;
|
||||
function getDomainFromAPI(callback) {
|
||||
if (pendingDomainRequest !== null) {
|
||||
pendingDomainRequest.success(callback);
|
||||
pendingDomainRequest.error(function() { callback({ status: 'fail' }) });
|
||||
return pendingDomainRequest;
|
||||
}
|
||||
|
||||
if (callback === undefined) {
|
||||
callback = function() {};
|
||||
}
|
||||
|
||||
var domainID = Settings.data.values.metaverse.id;
|
||||
if (domainID === null || domainID === undefined || domainID === '') {
|
||||
callback({ status: 'fail' });
|
||||
return null;
|
||||
}
|
||||
|
||||
pendingDomainRequest = $.ajax({
|
||||
url: "/api/domains/" + domainID,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
pendingDomainRequest = null;
|
||||
|
||||
if (data.status === 'success') {
|
||||
DomainInfo = data.domain;
|
||||
} else {
|
||||
DomainInfo = null;
|
||||
}
|
||||
callback(data);
|
||||
},
|
||||
error: function() {
|
||||
pendingDomainRequest = null;
|
||||
|
||||
DomainInfo = null;
|
||||
callback({ status: 'fail' });
|
||||
}
|
||||
});
|
||||
|
||||
return pendingDomainRequest;
|
||||
}
|
||||
|
||||
function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded) {
|
||||
if (accessToken) {
|
||||
|
||||
var loadingDialog = showLoadingDialog(Strings.ADD_PLACE_LOADING_DIALOG);
|
||||
|
||||
$.ajax("/api/places", {
|
||||
dataType: 'json',
|
||||
jsonp: false,
|
||||
success: function(data) {
|
||||
if (data.status == 'success') {
|
||||
var modal_buttons = {
|
||||
cancel: {
|
||||
label: Strings.ADD_PLACE_CANCEL_BUTTON,
|
||||
className: 'add-place-cancel-button btn-default'
|
||||
}
|
||||
};
|
||||
|
||||
var dialog;
|
||||
var modal_body;
|
||||
|
||||
if (data.data.places.length) {
|
||||
var places_by_id = {};
|
||||
|
||||
modal_body = $('<div>');
|
||||
|
||||
modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
|
||||
|
||||
var currentDomainIDType = getCurrentDomainIDType();
|
||||
if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
|
||||
var warning = "<div class='domain-loading-error alert alert-warning'>";
|
||||
warning += "If you choose a place name it will replace your current temporary place name.";
|
||||
warning += "</div>";
|
||||
modal_body.append(warning);
|
||||
}
|
||||
|
||||
// setup a select box for the returned places
|
||||
modal_body.append($("<label for='place-name-select'>Places</label>"));
|
||||
place_select = $("<select id='place-name-select' class='form-control'></select>");
|
||||
_.each(data.data.places, function(place) {
|
||||
places_by_id[place.id] = place;
|
||||
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
|
||||
})
|
||||
modal_body.append(place_select);
|
||||
modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
|
||||
|
||||
if (forcePathTo === undefined || forcePathTo === null) {
|
||||
var path = "<div class='form-group'>";
|
||||
path += "<label for='place-path-input' class='control-label'>Path</label>";
|
||||
path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
|
||||
path += "</div>";
|
||||
modal_body.append($(path));
|
||||
}
|
||||
|
||||
var place_select = modal_body.find("#place-name-select")
|
||||
place_select.change(function(ev) {
|
||||
var warning = modal_body.find("#place-name-warning");
|
||||
var place = places_by_id[$(this).val()];
|
||||
if (place === undefined || place.pointee === null) {
|
||||
warning.hide();
|
||||
} else {
|
||||
warning.show();
|
||||
function loadPlaces() {
|
||||
$.ajax("/api/places", {
|
||||
dataType: 'json',
|
||||
jsonp: false,
|
||||
success: function(data) {
|
||||
if (data.status == 'success') {
|
||||
var modal_buttons = {
|
||||
cancel: {
|
||||
label: Strings.ADD_PLACE_CANCEL_BUTTON,
|
||||
className: 'add-place-cancel-button btn-default'
|
||||
}
|
||||
});
|
||||
place_select.trigger('change');
|
||||
};
|
||||
|
||||
modal_buttons["success"] = {
|
||||
label: Strings.ADD_PLACE_CONFIRM_BUTTON,
|
||||
className: 'add-place-confirm-button btn btn-primary',
|
||||
callback: function() {
|
||||
var placeID = $('#place-name-select').val();
|
||||
// set the place ID on the form
|
||||
$(Settings.place_ID_SELECTOR).val(placeID).change();
|
||||
var dialog;
|
||||
var modal_body;
|
||||
|
||||
if (forcePathTo === undefined || forcePathTo === null) {
|
||||
var placePath = $('#place-path-input').val();
|
||||
if (data.data.places.length) {
|
||||
var places_by_id = {};
|
||||
|
||||
modal_body = $('<div>');
|
||||
|
||||
modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
|
||||
|
||||
var currentDomainIDType = getCurrentDomainIDType();
|
||||
if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
|
||||
var warning = "<div class='domain-loading-error alert alert-warning'>";
|
||||
warning += "If you choose a place name it will replace your current temporary place name.";
|
||||
warning += "</div>";
|
||||
modal_body.append(warning);
|
||||
}
|
||||
|
||||
// setup a select box for the returned places
|
||||
modal_body.append($("<label for='place-name-select'>Places</label>"));
|
||||
place_select = $("<select id='place-name-select' class='form-control'></select>");
|
||||
_.each(data.data.places, function(place) {
|
||||
places_by_id[place.id] = place;
|
||||
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
|
||||
})
|
||||
modal_body.append(place_select);
|
||||
modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
|
||||
|
||||
if (forcePathTo === undefined || forcePathTo === null) {
|
||||
var path = "<div class='form-group'>";
|
||||
path += "<label for='place-path-input' class='control-label'>Path or Viewpoint</label>";
|
||||
path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
|
||||
path += "</div>";
|
||||
modal_body.append($(path));
|
||||
}
|
||||
|
||||
var place_select = modal_body.find("#place-name-select")
|
||||
place_select.change(function(ev) {
|
||||
var warning = modal_body.find("#place-name-warning");
|
||||
var place = places_by_id[$(this).val()];
|
||||
if (place === undefined || place.pointee === null) {
|
||||
warning.hide();
|
||||
} else {
|
||||
var placePath = forcePathTo;
|
||||
warning.show();
|
||||
}
|
||||
});
|
||||
place_select.trigger('change');
|
||||
|
||||
$('.add-place-confirm-button').attr('disabled', 'disabled');
|
||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING);
|
||||
$('.add-place-cancel-button').attr('disabled', 'disabled');
|
||||
modal_buttons["success"] = {
|
||||
label: Strings.ADD_PLACE_CONFIRM_BUTTON,
|
||||
className: 'add-place-confirm-button btn btn-primary',
|
||||
callback: function() {
|
||||
var placeID = $('#place-name-select').val();
|
||||
// set the place ID on the form
|
||||
$(Settings.place_ID_SELECTOR).val(placeID).change();
|
||||
|
||||
function finalizeSaveDomainID(domainID) {
|
||||
var jsonSettings = {
|
||||
metaverse: {
|
||||
id: domainID
|
||||
if (forcePathTo === undefined || forcePathTo === null) {
|
||||
var placePath = $('#place-path-input').val();
|
||||
} else {
|
||||
var placePath = forcePathTo;
|
||||
}
|
||||
|
||||
$('.add-place-confirm-button').attr('disabled', 'disabled');
|
||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING);
|
||||
$('.add-place-cancel-button').attr('disabled', 'disabled');
|
||||
|
||||
function finalizeSaveDomainID(domainID) {
|
||||
var jsonSettings = {
|
||||
metaverse: {
|
||||
id: domainID
|
||||
}
|
||||
}
|
||||
var dialog = showLoadingDialog("Waiting for Domain Server to restart...");
|
||||
$.ajax('/settings.json', {
|
||||
data: JSON.stringify(jsonSettings),
|
||||
contentType: 'application/json',
|
||||
type: 'POST'
|
||||
}).done(function(data) {
|
||||
if (data.status == "success") {
|
||||
waitForDomainServerRestart(function() {
|
||||
dialog.modal('hide');
|
||||
if (onSuccessfullyAdded) {
|
||||
onSuccessfullyAdded(places_by_id[placeID].name, domainID);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
bootbox.alert("Failed to add place");
|
||||
}
|
||||
}).fail(function() {
|
||||
bootbox.alert("Failed to add place");
|
||||
});
|
||||
}
|
||||
|
||||
// If domainID is not specified, the current domain id will be used.
|
||||
function finishSettingUpPlace(domainID) {
|
||||
sendUpdatePlaceRequest(
|
||||
placeID,
|
||||
placePath,
|
||||
domainID,
|
||||
false,
|
||||
function(data) {
|
||||
dialog.modal('hide')
|
||||
if (domainID) {
|
||||
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
|
||||
finalizeSaveDomainID(domainID);
|
||||
} else {
|
||||
if (onSuccessfullyAdded) {
|
||||
onSuccessfullyAdded(places_by_id[placeID].name);
|
||||
}
|
||||
}
|
||||
},
|
||||
function(data) {
|
||||
$('.add-place-confirm-button').removeAttr('disabled');
|
||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
|
||||
$('.add-place-cancel-button').removeAttr('disabled');
|
||||
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function maybeCreateNewDomainID() {
|
||||
console.log("Maybe creating domain id", currentDomainIDType)
|
||||
if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) {
|
||||
finishSettingUpPlace();
|
||||
} else {
|
||||
sendCreateDomainRequest(function(domainID) {
|
||||
console.log("Created domain", domainID);
|
||||
finishSettingUpPlace(domainID);
|
||||
}, function() {
|
||||
$('.add-place-confirm-button').removeAttr('disabled');
|
||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
|
||||
$('.add-place-cancel-button').removeAttr('disabled');
|
||||
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
||||
});
|
||||
}
|
||||
}
|
||||
var dialog = showLoadingDialog("Waiting for Domain Server to restart...");
|
||||
$.ajax('/settings.json', {
|
||||
data: JSON.stringify(jsonSettings),
|
||||
contentType: 'application/json',
|
||||
type: 'POST'
|
||||
}).done(function(data) {
|
||||
if (data.status == "success") {
|
||||
waitForDomainServerRestart(function() {
|
||||
dialog.modal('hide');
|
||||
if (onSuccessfullyAdded) {
|
||||
onSuccessfullyAdded(places_by_id[placeID].name, domainID);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
bootbox.alert("Failed to add place");
|
||||
}
|
||||
}).fail(function() {
|
||||
bootbox.alert("Failed to add place");
|
||||
});
|
||||
}
|
||||
|
||||
function finishSettingUpPlace(domainID) {
|
||||
sendUpdatePlaceRequest(
|
||||
placeID,
|
||||
placePath,
|
||||
domainID,
|
||||
false,
|
||||
function(data) {
|
||||
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
|
||||
dialog.modal('hide')
|
||||
if (domainID) {
|
||||
finalizeSaveDomainID(domainID);
|
||||
} else {
|
||||
if (onSuccessfullyAdded) {
|
||||
onSuccessfullyAdded(places_by_id[placeID].name);
|
||||
}
|
||||
}
|
||||
},
|
||||
function(data) {
|
||||
$('.add-place-confirm-button').removeAttr('disabled');
|
||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
|
||||
$('.add-place-cancel-button').removeAttr('disabled');
|
||||
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
||||
}
|
||||
);
|
||||
}
|
||||
maybeCreateNewDomainID();
|
||||
|
||||
if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) {
|
||||
finishSettingUpPlace();
|
||||
} else {
|
||||
sendCreateDomainRequest(function(domainID) {
|
||||
console.log("Created domain", domainID);
|
||||
finishSettingUpPlace(domainID);
|
||||
}, function() {
|
||||
$('.add-place-confirm-button').removeAttr('disabled');
|
||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
|
||||
$('.add-place-cancel-button').removeAttr('disabled');
|
||||
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
||||
bootbox.alert("FAIL");
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
modal_buttons["success"] = {
|
||||
label: Strings.ADD_PLACE_NO_PLACES_BUTTON,
|
||||
callback: function() {
|
||||
window.open(URLs.METAVERSE_URL + "/user/places", '_blank');
|
||||
}
|
||||
}
|
||||
modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE;
|
||||
}
|
||||
|
||||
dialog = bootbox.dialog({
|
||||
title: Strings.ADD_PLACE_TITLE,
|
||||
message: modal_body,
|
||||
closeButton: false,
|
||||
buttons: modal_buttons,
|
||||
onEscape: true
|
||||
});
|
||||
} else {
|
||||
modal_buttons["success"] = {
|
||||
label: Strings.ADD_PLACE_NO_PLACES_BUTTON,
|
||||
callback: function() {
|
||||
window.open(URLs.METAVERSE_URL + "/user/places", '_blank');
|
||||
}
|
||||
}
|
||||
modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE;
|
||||
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
|
||||
}
|
||||
|
||||
dialog = bootbox.dialog({
|
||||
title: Strings.ADD_PLACE_TITLE,
|
||||
message: modal_body,
|
||||
closeButton: false,
|
||||
buttons: modal_buttons
|
||||
});
|
||||
} else {
|
||||
},
|
||||
error: function() {
|
||||
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
|
||||
},
|
||||
complete: function() {
|
||||
loadingDialog.modal('hide');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
|
||||
},
|
||||
complete: function() {
|
||||
loadingDialog.modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var domainType = getCurrentDomainIDType();
|
||||
if (domainType !== DOMAIN_ID_TYPE_UNKNOWN) {
|
||||
loadPlaces();
|
||||
} else {
|
||||
getDomainFromAPI(function(data) {
|
||||
if (data.status === 'success') {
|
||||
var domainType = getCurrentDomainIDType();
|
||||
loadPlaces();
|
||||
} else {
|
||||
loadingDialog.modal('hide');
|
||||
bootbox.confirm("We were not able to load your domain information from the Metaverse. Would you like to retry?", function(response) {
|
||||
if (response) {
|
||||
chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
bootbox.alert({
|
||||
|
|
|
@ -36,11 +36,6 @@
|
|||
</div>
|
||||
|
||||
<div class="col-md-9 col-sm-9 col-xs-12">
|
||||
|
||||
<div id="xs-advanced-container" class="col-xs-12 hidden-sm hidden-md hidden-lg">
|
||||
<button id="advanced-toggle-button-xs" class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
|
||||
|
|
|
@ -503,7 +503,7 @@ function showDomainCreationAlert(justConnected) {
|
|||
swal({
|
||||
title: 'Create new domain ID',
|
||||
type: 'input',
|
||||
text: 'Enter a short description for this machine.</br></br>This will help you identify which domain ID belongs to which machine.</br></br>',
|
||||
text: 'Enter a label this machine.</br></br>This will help you identify which domain ID belongs to which machine.</br></br>',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Create",
|
||||
closeOnConfirm: false,
|
||||
|
@ -527,13 +527,12 @@ function showDomainCreationAlert(justConnected) {
|
|||
function createNewDomainID(label, justConnected) {
|
||||
// get the JSON object ready that we'll use to create a new domain
|
||||
var domainJSON = {
|
||||
"label": label
|
||||
//"access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val()
|
||||
"label": label
|
||||
}
|
||||
|
||||
$.post("/api/domains", domainJSON, function(data){
|
||||
// we successfully created a domain ID, set it on that field
|
||||
var domainID = data.domain_id;
|
||||
var domainID = data.domain.id;
|
||||
console.log("Setting domain id to ", data, domainID);
|
||||
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
|
||||
|
||||
|
@ -620,18 +619,14 @@ function parseJSONResponse(xhr) {
|
|||
|
||||
function showOrHideLabel() {
|
||||
var type = getCurrentDomainIDType();
|
||||
if (!accessTokenIsSet() || (type !== DOMAIN_ID_TYPE_FULL && type !== DOMAIN_ID_TYPE_UNKNOWN)) {
|
||||
$(".panel#label").hide();
|
||||
return false;
|
||||
}
|
||||
$(".panel#label").show();
|
||||
return true;
|
||||
var shouldShow = accessTokenIsSet() && (type === DOMAIN_ID_TYPE_FULL || type === DOMAIN_ID_TYPE_UNKNOWN);
|
||||
$(".panel#label").toggle(shouldShow);
|
||||
$("li a[href='#label']").parent().toggle(shouldShow);
|
||||
return shouldShow;
|
||||
}
|
||||
|
||||
function setupDomainLabelSetting() {
|
||||
if (!showOrHideLabel()) {
|
||||
return;
|
||||
}
|
||||
showOrHideLabel();
|
||||
|
||||
var html = "<div>"
|
||||
html += "<label class='control-label'>Specify a label for your domain</label> <a class='domain-loading-hide' href='#'>Edit</a>";
|
||||
|
@ -654,6 +649,7 @@ function setupDomainLabelSetting() {
|
|||
title: 'Edit Label',
|
||||
message: modal_body,
|
||||
closeButton: false,
|
||||
onEscape: true,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Cancel',
|
||||
|
@ -742,7 +738,7 @@ function setupDomainNetworkingSettings() {
|
|||
var includeAddress = autoNetworkingSetting === 'disabled';
|
||||
|
||||
if (includeAddress) {
|
||||
var label = "Network Address and Port";
|
||||
var label = "Network Address:Port";
|
||||
} else {
|
||||
var label = "Network Port";
|
||||
}
|
||||
|
@ -777,6 +773,7 @@ function setupDomainNetworkingSettings() {
|
|||
title: 'Edit Network',
|
||||
message: modal_body,
|
||||
closeButton: false,
|
||||
onEscape: true,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Cancel',
|
||||
|
@ -924,6 +921,7 @@ function placeTableRow(name, path, isTemporary, placeID) {
|
|||
var dialog = bootbox.dialog({
|
||||
message: confirmString,
|
||||
closeButton: false,
|
||||
onEscape: true,
|
||||
buttons: [
|
||||
{
|
||||
label: Strings.REMOVE_PLACE_CANCEL_BUTTON,
|
||||
|
@ -980,20 +978,6 @@ function placeTableRowForPlaceObject(place) {
|
|||
return placeTableRow(place.name, placePathOrIndex, false, place.id);
|
||||
}
|
||||
|
||||
function getDomainFromAPI(callback) {
|
||||
var domainID = Settings.data.values.metaverse.id;
|
||||
$.ajax({
|
||||
url: "/api/domains/" + domainID,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
callback(data);
|
||||
},
|
||||
error: function() {
|
||||
callback({ status: 'fail' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function reloadDomainInfo() {
|
||||
$('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove();
|
||||
|
||||
|
@ -1010,7 +994,6 @@ function reloadDomainInfo() {
|
|||
// check if we have owner_places (for a real domain) or a name (for a temporary domain)
|
||||
if (data.status == "success") {
|
||||
$('.domain-loading-hide').show();
|
||||
DomainInfo = data.domain;
|
||||
if (data.domain.owner_places) {
|
||||
// add a table row for each of these names
|
||||
_.each(data.domain.owner_places, function(place){
|
||||
|
@ -1040,10 +1023,11 @@ function reloadDomainInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
appendAddButtonToPlacesTable();
|
||||
if (accessTokenIsSet()) {
|
||||
appendAddButtonToPlacesTable();
|
||||
}
|
||||
|
||||
} else {
|
||||
DomainInfo = null;
|
||||
$('.domain-loading-error').show();
|
||||
}
|
||||
})
|
||||
|
@ -1114,6 +1098,7 @@ function editHighFidelityPlace(placeID, name, path) {
|
|||
dialog = bootbox.dialog({
|
||||
title: Strings.EDIT_PLACE_TITLE,
|
||||
closeButton: false,
|
||||
onEscape: true,
|
||||
message: modal_body,
|
||||
buttons: modal_buttons
|
||||
})
|
||||
|
@ -1196,6 +1181,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
|||
|
||||
bootbox.dialog({
|
||||
title: "Choose matching domain",
|
||||
onEscape: true,
|
||||
message: modal_body,
|
||||
buttons: modal_buttons
|
||||
})
|
||||
|
|
|
@ -31,6 +31,12 @@ label {
|
|||
color: #373A3C;
|
||||
}
|
||||
|
||||
.wizard-link {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
color: #2F80ED;
|
||||
}
|
||||
|
||||
#admin-row {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
@ -84,6 +90,14 @@ label {
|
|||
height: 169px;
|
||||
}
|
||||
|
||||
#visit-domain-row {
|
||||
margin-bottom: 68px;
|
||||
#congratulation-text {
|
||||
margin-bottom: 59px;
|
||||
}
|
||||
|
||||
#visit-domain-checkbox {
|
||||
margin-bottom: 23px;
|
||||
}
|
||||
|
||||
#visit-domain-checkbox label {
|
||||
margin: 0 0;
|
||||
}
|
||||
|
|
|
@ -60,8 +60,7 @@
|
|||
</div>
|
||||
<div id="admin-row" class="row">
|
||||
<p class="col-md-6">
|
||||
<span id="admin-description" class="step-info"><b>Add your High Fidelity username</b> and any other usernames to grant administrator privileges.</span>
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle="tooltip" title="Users who will have all the permissions for this domain."></span>
|
||||
<span class="step-info"><span id="admin-description"><b>Add your High Fidelity username</b> and any other usernames</span> to grant <span class='wizard-link' data-toggle="tooltip" title="Users who will have all the permissions for this domain.">administrator privileges</span></span>
|
||||
</p>
|
||||
<div class="col-md-6">
|
||||
<input id="admin-usernames" type="text" class="form-control">
|
||||
|
@ -78,7 +77,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p id="connect-question" class="step-info">
|
||||
Who can connect to your domain?
|
||||
Who can <a href='#' class='wizard-link perms-link'>connect</a> to your domain?
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='You can set this to allow a user to connect to this domain.'></span>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -87,25 +86,21 @@
|
|||
<p class="col-md-2">
|
||||
<label>
|
||||
<input id="connect-none" name="connect-radio" type="radio" value="none" checked> None
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Only the admins of this domain'></span>
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-3">
|
||||
<label>
|
||||
<input id="connect-friends" name="connect-radio" type="radio" value="friends"> Friends
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Users who are your Friends in High Fidelity'></span>
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-5">
|
||||
<label>
|
||||
<input id="connect-logged-in" name="connect-radio" type="radio" value="logged-in"> Users logged in to High Fidelity
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Users who are currently logged into High Fidelity'></span>
|
||||
<input id="connect-logged-in" name="connect-radio" type="radio" value="logged-in"> Users logged into High Fidelity
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-2">
|
||||
<label>
|
||||
<input id="connect-everyone" name="connect-radio" type="radio" value="everyone"> Everyone
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title="Users who aren't logged into High Fidelity"></span>
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -113,7 +108,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="step-info">
|
||||
Who can rez items in your domain?
|
||||
Who can <a href='#' class='wizard-link perms-link'>rez items</a> in your domain?
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='You can set this to allow a user to create entities in this domain.'></span>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -122,33 +117,32 @@
|
|||
<p class="col-md-2">
|
||||
<label>
|
||||
<input id="rez-none" name="rez-radio" type="radio" value="none" checked> None
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Only the admins of this domain'></span>
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-3">
|
||||
<label>
|
||||
<input id="rez-friends" name="rez-radio" type="radio" value="friends"> Friends
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Users who are your Friends in High Fidelity'></span>
|
||||
<input id="rez-friends" name="rez-radio" type="radio" value="friends" disabled> Friends
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-5">
|
||||
<label>
|
||||
<input id="rez-logged-in" name="rez-radio" type="radio" value="logged-in"> Users logged in to High Fidelity
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Users who are currently logged into High Fidelity'></span>
|
||||
<input id="rez-logged-in" name="rez-radio" type="radio" value="logged-in" disabled> Users logged into High Fidelity
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-2">
|
||||
<label>
|
||||
<input id="rez-everyone" name="rez-radio" type="radio" value="everyone"> Everyone
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title="Users who aren't logged into High Fidelity"></span>
|
||||
<input id="rez-everyone" name="rez-radio" type="radio" value="everyone" disabled> Everyone
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-md-offset-9">
|
||||
<dd class="col-md-3">
|
||||
<button type="button" class="btn btn-md btn-block btn-default back-button">Back</button>
|
||||
</dd>
|
||||
<dd class="col-md-3 col-md-offset-6">
|
||||
<button id="save-permissions" type="button" class="btn btn-md btn-block btn-primary">Next</button>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -188,35 +182,37 @@
|
|||
</dl>
|
||||
|
||||
<dl class="row">
|
||||
<dd class="col-md-3 col-md-offset-9">
|
||||
<dd class="col-md-3">
|
||||
<button type="button" class="btn btn-md btn-block btn-default back-button">Back</button>
|
||||
</dd>
|
||||
<dd class="col-md-3 col-md-offset-6">
|
||||
<button id="save-username-password" type="button" class="btn btn-md btn-block btn-primary">Finish</button>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step cloud-only col-md-7 col-centered" style="display: none;">
|
||||
<div class="wizard-step cloud-only col-xs-12 col-sm-12 col-md-9 col-lg-7 col-centered" style="display: none;">
|
||||
<div class="row">
|
||||
<div class="col-xs-4 col-centered">
|
||||
<div class="col-xs-12" align="center">
|
||||
<img id="checkmark-image" src="../images/checkmark.svg">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="congratulation-text" class="row">
|
||||
<div class="col-xs-12">
|
||||
<p class="step-info">Congratulations! You have successfully setup and configured your cloud hosted domain.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="visit-domain-row" class="row">
|
||||
<div class="col-md-12">
|
||||
<label><input id="go-to-domain" class="form-check-input" type="checkbox"> Visit domain in VR now</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="row">
|
||||
<dd class="col-md-5 col-md-offset-7">
|
||||
<button id="explore-settings" type="button" class="btn btn-md btn-block btn-primary">Explore all domain server settings</button>
|
||||
</dd>
|
||||
<div class="col-xs-12">
|
||||
<div class="pull-right">
|
||||
<div id="visit-domain-checkbox">
|
||||
<label><input id="go-to-domain" class="form-check-input" type="checkbox"> Visit domain in VR now</label>
|
||||
</div>
|
||||
<button id="explore-settings" type="button" class="btn btn-md btn-primary">Explore all domain server settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ var Metaverse = {
|
|||
accessToken: null
|
||||
}
|
||||
|
||||
var currentStepNumber;
|
||||
|
||||
$(document).ready(function(){
|
||||
Strings.ADD_PLACE_NOT_CONNECTED_MESSAGE = "You must have an access token to query your High Fidelity places.<br><br>" +
|
||||
"Please go back and connect your account.";
|
||||
|
@ -9,6 +11,22 @@ $(document).ready(function(){
|
|||
$('#connect-account-btn').attr('href', URLs.METAVERSE_URL + "/user/tokens/new?for_domain_server=true");
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
$('.perms-link').on('click', function() {
|
||||
var modal_body = '<div>';
|
||||
modal_body += '<b>None</b> - No one will have permissions. Only you and the users your have given administrator privileges to will have permissions.</br></br>';
|
||||
modal_body += '<b>Friends</b> - Users who are your Friends in High Fidelity.</br></br>';
|
||||
modal_body += '<b>Users logged into High Fidelity</b> - Users who are currently logged into High Fidelity.</br></br>';
|
||||
modal_body += '<b>Everyone</b> - Anyone who uses High Fidelity.';
|
||||
modal_body += '</div>';
|
||||
|
||||
dialog = bootbox.dialog({
|
||||
title: "User definition",
|
||||
message: modal_body,
|
||||
closeButton: true
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('body').on('click', '.next-button', function() {
|
||||
goToNextStep();
|
||||
|
@ -56,8 +74,39 @@ $(document).ready(function(){
|
|||
exploreSettings();
|
||||
});
|
||||
|
||||
$('input[type=radio][name=connect-radio]').change(function() {
|
||||
var inputs = $('input[type=radio][name=rez-radio]');
|
||||
var disabled = [];
|
||||
|
||||
switch (this.value) {
|
||||
case 'none':
|
||||
disabled = inputs.splice(1);
|
||||
break;
|
||||
case 'friends':
|
||||
disabled = inputs.splice(2);
|
||||
break;
|
||||
case 'logged-in':
|
||||
disabled = inputs.splice(3);
|
||||
break;
|
||||
case 'everyone':
|
||||
disabled = inputs.splice(4);
|
||||
break;
|
||||
}
|
||||
|
||||
$.each(inputs, function() {
|
||||
$(this).prop('disabled', false);
|
||||
});
|
||||
$.each(disabled, function() {
|
||||
if ($(this).prop('checked')) {
|
||||
$(inputs.last()).prop('checked', true);
|
||||
}
|
||||
$(this).prop('disabled', true);
|
||||
});
|
||||
});
|
||||
|
||||
reloadSettings(function(success) {
|
||||
if (success) {
|
||||
getDomainFromAPI();
|
||||
setupWizardSteps();
|
||||
updatePlaceNameDisplay();
|
||||
updateUsernameDisplay();
|
||||
|
@ -72,12 +121,12 @@ $(document).ready(function(){
|
|||
});
|
||||
|
||||
function setupWizardSteps() {
|
||||
var stepsCompleted = Settings.data.values.wizard.steps_completed;
|
||||
currentStepNumber = Settings.data.values.wizard.steps_completed;
|
||||
var steps = null;
|
||||
|
||||
if (Settings.data.values.wizard.cloud_domain) {
|
||||
$('.desktop-only').remove();
|
||||
$('.wizard-step').find('.back-button').hide();
|
||||
$('.wizard-step:first').find('.back-button').hide();
|
||||
|
||||
steps = $('.wizard-step');
|
||||
$(steps).each(function(i) {
|
||||
|
@ -85,7 +134,7 @@ function setupWizardSteps() {
|
|||
});
|
||||
|
||||
$('#permissions-description').html('You <span id="username-display"></span>have been assigned administrator privileges to this domain.');
|
||||
$('#admin-description').html('Add more High Fidelity usernames to grant administrator privileges.');
|
||||
$('#admin-description').html('Add more High Fidelity usernames');
|
||||
} else {
|
||||
$('.cloud-only').remove();
|
||||
$('#save-permissions').text("Finish");
|
||||
|
@ -95,12 +144,12 @@ function setupWizardSteps() {
|
|||
$(this).children(".step-title").text("Step " + (i + 1) + " of " + steps.length);
|
||||
});
|
||||
|
||||
if (stepsCompleted == 0) {
|
||||
if (currentStepNumber == 0) {
|
||||
$('#skip-wizard-button').show();
|
||||
}
|
||||
}
|
||||
|
||||
var currentStep = steps[stepsCompleted];
|
||||
var currentStep = steps[currentStepNumber];
|
||||
$(currentStep).show();
|
||||
}
|
||||
|
||||
|
@ -203,7 +252,7 @@ function goToNextStep() {
|
|||
currentStep.hide();
|
||||
nextStep.show();
|
||||
|
||||
var currentStepNumber = parseInt(Settings.data.values.wizard.steps_completed) + 1;
|
||||
currentStepNumber += 1;
|
||||
|
||||
postSettings({
|
||||
"wizard": {
|
||||
|
@ -232,7 +281,7 @@ function goToPreviousStep() {
|
|||
currentStep.hide();
|
||||
previousStep.show();
|
||||
|
||||
var currentStepNumber = parseInt(Settings.data.values.wizard.steps_completed) - 1;
|
||||
currentStepNumber -= 1;
|
||||
|
||||
postSettings({
|
||||
"wizard": {
|
||||
|
@ -438,7 +487,7 @@ function saveUsernamePassword() {
|
|||
return;
|
||||
}
|
||||
|
||||
var currentStepNumber = parseInt(Settings.data.values.wizard.steps_completed) + 1;
|
||||
currentStepNumber += 1;
|
||||
|
||||
var formJSON = {
|
||||
"security": {
|
||||
|
|
|
@ -830,26 +830,6 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
|||
void DomainServer::updateICEServerAddresses() {
|
||||
if (_iceAddressLookupID == INVALID_ICE_LOOKUP_ID) {
|
||||
_iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo)));
|
||||
|
||||
// there seems to be a 5.9 bug where lookupHost never calls our slot
|
||||
// so we add a single shot manual "timeout" to fire it off again if it hasn't called back yet
|
||||
static const int ICE_ADDRESS_LOOKUP_TIMEOUT_MS = 5000;
|
||||
QTimer::singleShot(ICE_ADDRESS_LOOKUP_TIMEOUT_MS, this, &DomainServer::timeoutICEAddressLookup);
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::timeoutICEAddressLookup() {
|
||||
if (_iceAddressLookupID != INVALID_ICE_LOOKUP_ID) {
|
||||
// we waited 5s and didn't hear back for our ICE DNS lookup
|
||||
// so time that one out and kick off another
|
||||
|
||||
qDebug() << "IP address lookup timed out for" << _iceServerAddr << "- retrying";
|
||||
|
||||
QHostInfo::abortHostLookup(_iceAddressLookupID);
|
||||
|
||||
_iceAddressLookupID = INVALID_ICE_LOOKUP_ID;
|
||||
|
||||
updateICEServerAddresses();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3007,9 +2987,20 @@ void DomainServer::handleKeypairChange() {
|
|||
|
||||
void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
||||
// clear the ICE address lookup ID so that it can fire again
|
||||
_iceAddressLookupID = -1;
|
||||
_iceAddressLookupID = INVALID_ICE_LOOKUP_ID;
|
||||
|
||||
if (hostInfo.error() != QHostInfo::NoError) {
|
||||
// enumerate the returned addresses and collect only valid IPv4 addresses
|
||||
QList<QHostAddress> sanitizedAddresses = hostInfo.addresses();
|
||||
auto it = sanitizedAddresses.begin();
|
||||
while (it != sanitizedAddresses.end()) {
|
||||
if (!it->isNull() && it->protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
++it;
|
||||
} else {
|
||||
it = sanitizedAddresses.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
if (hostInfo.error() != QHostInfo::NoError || sanitizedAddresses.empty()) {
|
||||
qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
|
||||
|
||||
// if we don't have an ICE server to use yet, trigger a retry
|
||||
|
@ -3022,7 +3013,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
|||
} else {
|
||||
int countBefore = _iceServerAddresses.count();
|
||||
|
||||
_iceServerAddresses = hostInfo.addresses();
|
||||
_iceServerAddresses = sanitizedAddresses;
|
||||
|
||||
if (countBefore == 0) {
|
||||
qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;
|
||||
|
|
|
@ -116,8 +116,6 @@ private slots:
|
|||
void tokenGrantFinished();
|
||||
void profileRequestFinished();
|
||||
|
||||
void timeoutICEAddressLookup();
|
||||
|
||||
signals:
|
||||
void iceServerChanged();
|
||||
void userConnected();
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <NLPacketList.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <SettingHelpers.h>
|
||||
#include <AvatarData.h> //for KillAvatarReason
|
||||
#include <FingerprintUtils.h>
|
||||
#include "DomainServerNodeData.h"
|
||||
|
@ -43,12 +44,7 @@ const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
|||
|
||||
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
||||
|
||||
static Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
|
||||
|
||||
DomainServerSettingsManager::DomainServerSettingsManager() :
|
||||
_descriptionArray(),
|
||||
_configMap()
|
||||
{
|
||||
DomainServerSettingsManager::DomainServerSettingsManager() {
|
||||
// load the description object from the settings description
|
||||
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
||||
descriptionFile.open(QIODevice::ReadOnly);
|
||||
|
@ -100,12 +96,34 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
|
|||
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
||||
_argumentList = argumentList;
|
||||
|
||||
// after 1.7 we no longer use the master or merged configs - this is kept in place for migration
|
||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||
_configMap.loadConfig(_argumentList);
|
||||
|
||||
static const auto VERSION_SETTINGS_KEYPATH = "version";
|
||||
QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH);
|
||||
|
||||
if (!versionVariant) {
|
||||
versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH, true);
|
||||
*versionVariant = _descriptionVersion;
|
||||
persistToFile();
|
||||
qDebug() << "No version in config file, setting to current version" << _descriptionVersion;
|
||||
}
|
||||
|
||||
{
|
||||
// Backward compatibility migration code
|
||||
// The config version used to be stored in a different file
|
||||
// This moves it to the actual config file.
|
||||
Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
|
||||
if (JSON_SETTING_VERSION.isSet()) {
|
||||
auto version = JSON_SETTING_VERSION.get();
|
||||
*versionVariant = version;
|
||||
persistToFile();
|
||||
QFile::remove(settingsFilename());
|
||||
}
|
||||
}
|
||||
|
||||
// What settings version were we before and what are we using now?
|
||||
// Do we need to do any re-mapping?
|
||||
double oldVersion = JSON_SETTING_VERSION.get();
|
||||
double oldVersion = versionVariant->toDouble();
|
||||
|
||||
if (oldVersion != _descriptionVersion) {
|
||||
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
|
||||
|
@ -137,12 +155,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true);
|
||||
|
||||
*restrictedAccess = QVariant(true);
|
||||
|
||||
// write the new settings to the json file
|
||||
persistToFile();
|
||||
|
||||
// reload the master and user config so that the merged config is right
|
||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,12 +184,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
|
||||
*entityServerVariant = entityServerMap;
|
||||
}
|
||||
|
||||
// write the new settings to the json file
|
||||
persistToFile();
|
||||
|
||||
// reload the master and user config so that the merged config is right
|
||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -195,12 +201,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings.";
|
||||
|
||||
*passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
|
||||
|
||||
// write the new settings to file
|
||||
persistToFile();
|
||||
|
||||
// reload the master and user config so the merged config is correct
|
||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,19 +283,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
packPermissions();
|
||||
}
|
||||
|
||||
if (oldVersion < 1.7) {
|
||||
// This was prior to the removal of the master config file
|
||||
// So we write the merged config to the user config file, and stop reading from the user config file
|
||||
|
||||
qDebug() << "Migrating merged config to user config file. The master config file is deprecated.";
|
||||
|
||||
// replace the user config by the merged config
|
||||
_configMap.getConfig() = _configMap.getMergedConfig();
|
||||
|
||||
// persist the new config so the user config file has the correctly merged config
|
||||
persistToFile();
|
||||
}
|
||||
|
||||
if (oldVersion < 1.8) {
|
||||
unpackPermissions();
|
||||
// This was prior to addition of domain content replacement, add that to localhost permissions by default
|
||||
|
@ -316,16 +303,16 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true);
|
||||
|
||||
*wizardCompletedOnce = QVariant(true);
|
||||
|
||||
// write the new settings to the json file
|
||||
persistToFile();
|
||||
}
|
||||
|
||||
// write the current description version to our settings
|
||||
*versionVariant = _descriptionVersion;
|
||||
|
||||
// write the new settings to the json file
|
||||
persistToFile();
|
||||
}
|
||||
|
||||
unpackPermissions();
|
||||
|
||||
// write the current description version to our settings
|
||||
JSON_SETTING_VERSION.set(_descriptionVersion);
|
||||
}
|
||||
|
||||
QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
|
||||
|
@ -1289,9 +1276,6 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
}
|
||||
}
|
||||
|
||||
// re-merge the user and master configs after a settings change
|
||||
_configMap.mergeMasterAndUserConfigs();
|
||||
|
||||
return needRestart;
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
BIN
interface/resources/images/lowerKeyboard.png
Normal file
BIN
interface/resources/images/lowerKeyboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1 KiB |
|
@ -1,532 +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"
|
||||
import "styles-uit" as HifiStyles
|
||||
import "controls-uit" as HifiControls
|
||||
|
||||
Window {
|
||||
id: root
|
||||
HifiConstants { id: hifi }
|
||||
HifiStyles.HifiConstants { id: hifiStyleConstants }
|
||||
|
||||
objectName: "AddressBarDialog"
|
||||
title: "Go To:"
|
||||
|
||||
shown: false
|
||||
destroyOnHidden: false
|
||||
resizable: false
|
||||
pinnable: false;
|
||||
|
||||
width: addressBarDialog.implicitWidth
|
||||
height: addressBarDialog.implicitHeight
|
||||
property int gap: 14
|
||||
|
||||
onShownChanged: {
|
||||
addressBarDialog.keyboardEnabled = HMD.active;
|
||||
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 resetAfterTeleport() {
|
||||
storyCardFrame.shown = root.shown = false;
|
||||
}
|
||||
function goCard(targetString) {
|
||||
if (0 !== targetString.indexOf('hifi://')) {
|
||||
storyCardHTML.url = addressBarDialog.metaverseServerUrl + targetString;
|
||||
storyCardFrame.shown = true;
|
||||
return;
|
||||
}
|
||||
addressLine.text = targetString;
|
||||
toggleOrGo(true);
|
||||
clearAddressLineTimer.start();
|
||||
}
|
||||
property var allStories: [];
|
||||
property int cardWidth: 212;
|
||||
property int cardHeight: 152;
|
||||
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
|
||||
property bool isCursorVisible: false // Override default cursor visibility.
|
||||
|
||||
AddressBarDialog {
|
||||
id: addressBarDialog
|
||||
|
||||
property bool keyboardEnabled: false
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
|
||||
implicitWidth: backgroundImage.width
|
||||
implicitHeight: scroll.height + gap + backgroundImage.height + (keyboardEnabled ? keyboard.height : 0);
|
||||
|
||||
// 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;
|
||||
onReceivedHifiSchemeURL: resetAfterTeleport();
|
||||
|
||||
// Update location after using back and forward buttons.
|
||||
onHostChanged: updateLocationTextTimer.start();
|
||||
|
||||
ListModel { id: suggestions }
|
||||
|
||||
ListView {
|
||||
id: scroll
|
||||
height: cardHeight + scroll.stackedCardShadowHeight
|
||||
property int stackedCardShadowHeight: 10;
|
||||
spacing: gap;
|
||||
clip: true;
|
||||
anchors {
|
||||
left: backgroundImage.left
|
||||
right: swipe.left
|
||||
bottom: backgroundImage.top
|
||||
}
|
||||
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;
|
||||
thumbnail: model.thumbnail_url;
|
||||
imageUrl: model.image_url;
|
||||
action: model.action;
|
||||
timestamp: model.created_at;
|
||||
onlineUsers: model.online_users;
|
||||
storyId: model.metaverseId;
|
||||
drillDownToPlace: model.drillDownToPlace;
|
||||
shadowHeight: scroll.stackedCardShadowHeight;
|
||||
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: hifiStyleConstants.colors.blueHighlight; z: 1; }
|
||||
}
|
||||
Image { // Just a visual indicator that the user can swipe the cards over to see more.
|
||||
id: swipe;
|
||||
source: "../images/swipe-chevron.svg";
|
||||
width: 72;
|
||||
visible: suggestions.count > 3;
|
||||
anchors {
|
||||
right: backgroundImage.right;
|
||||
top: scroll.top;
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: scroll.currentIndex = (scroll.currentIndex < 0) ? 3 : (scroll.currentIndex + 3)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 2 * hifi.layout.spacing;
|
||||
anchors {
|
||||
top: parent.top;
|
||||
left: parent.left;
|
||||
leftMargin: 150;
|
||||
topMargin: -30;
|
||||
}
|
||||
property var selected: allTab;
|
||||
TextButton {
|
||||
id: allTab;
|
||||
text: "ALL";
|
||||
property string includeActions: 'snapshot,concurrency';
|
||||
selected: allTab === selectedTab;
|
||||
action: tabSelect;
|
||||
}
|
||||
TextButton {
|
||||
id: placeTab;
|
||||
text: "PLACES";
|
||||
property string includeActions: 'concurrency';
|
||||
selected: placeTab === selectedTab;
|
||||
action: tabSelect;
|
||||
}
|
||||
TextButton {
|
||||
id: snapsTab;
|
||||
text: "SNAPS";
|
||||
property string includeActions: 'snapshot';
|
||||
selected: snapsTab === selectedTab;
|
||||
action: tabSelect;
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: backgroundImage
|
||||
source: "../images/address-bar-856.svg"
|
||||
width: 856
|
||||
height: 100
|
||||
anchors {
|
||||
bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom;
|
||||
}
|
||||
property int inputAreaHeight: 70
|
||||
property int inputAreaStep: (height - inputAreaHeight) / 2
|
||||
|
||||
ToolbarButton {
|
||||
id: homeButton
|
||||
imageURL: "../images/home.svg"
|
||||
onClicked: {
|
||||
addressBarDialog.loadHome();
|
||||
root.shown = false;
|
||||
}
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: homeButton.width / 2
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarButton {
|
||||
id: backArrow;
|
||||
imageURL: "../images/backward.svg";
|
||||
onClicked: addressBarDialog.loadBack();
|
||||
anchors {
|
||||
left: homeButton.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
ToolbarButton {
|
||||
id: forwardArrow;
|
||||
imageURL: "../images/forward.svg";
|
||||
onClicked: addressBarDialog.loadForward();
|
||||
anchors {
|
||||
left: backArrow.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
HifiStyles.RalewayLight {
|
||||
id: notice;
|
||||
font.pixelSize: hifi.fonts.pixelSize * 0.50;
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: parent.inputAreaStep + 12
|
||||
left: addressLine.left
|
||||
right: addressLine.right
|
||||
}
|
||||
}
|
||||
HifiStyles.FiraSansRegular {
|
||||
id: location;
|
||||
font.pixelSize: addressLine.font.pixelSize;
|
||||
color: "gray";
|
||||
clip: true;
|
||||
anchors.fill: addressLine;
|
||||
visible: addressLine.text.length === 0
|
||||
}
|
||||
TextInput {
|
||||
id: addressLine
|
||||
focus: true
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: forwardArrow.right
|
||||
right: parent.right
|
||||
leftMargin: forwardArrow.width
|
||||
rightMargin: forwardArrow.width / 2
|
||||
topMargin: parent.inputAreaStep + (2 * hifi.layout.spacing)
|
||||
bottomMargin: parent.inputAreaStep
|
||||
}
|
||||
font.pixelSize: hifi.fonts.pixelSize * 0.75
|
||||
cursorVisible: false
|
||||
onTextChanged: {
|
||||
filterChoicesByText();
|
||||
updateLocationText(text.length > 0);
|
||||
if (!isCursorVisible && text.length > 0) {
|
||||
isCursorVisible = true;
|
||||
cursorVisible = true;
|
||||
}
|
||||
}
|
||||
onActiveFocusChanged: {
|
||||
cursorVisible = isCursorVisible && focus;
|
||||
}
|
||||
MouseArea {
|
||||
// If user clicks in address bar show cursor to indicate ability to enter address.
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
isCursorVisible = true;
|
||||
parent.cursorVisible = true;
|
||||
parent.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
// Delay updating location text a bit to avoid flicker of content and so that connection status is valid.
|
||||
id: updateLocationTextTimer
|
||||
running: false
|
||||
interval: 500 // ms
|
||||
repeat: false
|
||||
onTriggered: updateLocationText(false);
|
||||
}
|
||||
|
||||
Timer {
|
||||
// Delay clearing address line so as to avoid flicker of "not connected" being displayed after entering an address.
|
||||
id: clearAddressLineTimer
|
||||
running: false
|
||||
interval: 100 // ms
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
addressLine.text = "";
|
||||
isCursorVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
Window {
|
||||
width: 938
|
||||
height: 625
|
||||
HifiControls.WebView {
|
||||
anchors.fill: parent;
|
||||
id: storyCardHTML;
|
||||
}
|
||||
id: storyCardFrame;
|
||||
|
||||
shown: false;
|
||||
destroyOnCloseButton: false;
|
||||
pinnable: false;
|
||||
|
||||
anchors {
|
||||
verticalCenter: backgroundImage.verticalCenter;
|
||||
horizontalCenter: scroll.horizontalCenter;
|
||||
}
|
||||
z: 100
|
||||
}
|
||||
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled // Ignore keyboardRaised; keep keyboard raised if enabled (i.e., in HMD).
|
||||
numeric: parent.punctuationMode
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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 resolveUrl(url) {
|
||||
return (url.indexOf('/') === 0) ? (addressBarDialog.metaverseServerUrl + url) : url;
|
||||
}
|
||||
|
||||
function makeModelData(data) { // 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 = data.place_name,
|
||||
tags = data.tags || [data.action, data.username],
|
||||
description = data.description || "",
|
||||
thumbnail_url = data.thumbnail_url || "";
|
||||
return {
|
||||
place_name: name,
|
||||
username: data.username || "",
|
||||
path: data.path || "",
|
||||
created_at: data.created_at || "",
|
||||
action: data.action || "",
|
||||
thumbnail_url: resolveUrl(thumbnail_url),
|
||||
image_url: resolveUrl(data.details.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.details.concurrency || 0,
|
||||
drillDownToPlace: false,
|
||||
|
||||
searchText: [name].concat(tags, description || []).join(' ').toUpperCase()
|
||||
}
|
||||
}
|
||||
function suggestable(place) {
|
||||
if (place.action === 'snapshot') {
|
||||
return true;
|
||||
}
|
||||
return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain.
|
||||
}
|
||||
property var selectedTab: allTab;
|
||||
function tabSelect(textButton) {
|
||||
selectedTab = textButton;
|
||||
fillDestinations();
|
||||
}
|
||||
property var placeMap: ({});
|
||||
function addToSuggestions(place) {
|
||||
var collapse = allTab.selected && (place.action !== 'concurrency');
|
||||
if (collapse) {
|
||||
var existing = placeMap[place.place_name];
|
||||
if (existing) {
|
||||
existing.drillDownToPlace = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
suggestions.append(place);
|
||||
if (collapse) {
|
||||
placeMap[place.place_name] = suggestions.get(suggestions.count - 1);
|
||||
} else if (place.action === 'concurrency') {
|
||||
suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories).
|
||||
}
|
||||
}
|
||||
property int requestId: 0;
|
||||
function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model
|
||||
var options = [
|
||||
'now=' + new Date().toISOString(),
|
||||
'include_actions=' + selectedTab.includeActions,
|
||||
'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
|
||||
'require_online=true',
|
||||
'protocol=' + encodeURIComponent(AddressManager.protocolVersion()),
|
||||
'page=' + pageNumber
|
||||
];
|
||||
var url = metaverseBase + 'user_stories?' + options.join('&');
|
||||
var thisRequestId = ++requestId;
|
||||
getRequest(url, function (error, data) {
|
||||
if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) {
|
||||
return;
|
||||
}
|
||||
var stories = data.user_stories.map(function (story) { // explicit single-argument function
|
||||
return makeModelData(story, url);
|
||||
});
|
||||
allStories = allStories.concat(stories);
|
||||
stories.forEach(makeFilteredPlaceProcessor());
|
||||
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 makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches
|
||||
var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity),
|
||||
data = allStories;
|
||||
function matches(place) {
|
||||
if (!words.length) {
|
||||
return suggestable(place);
|
||||
}
|
||||
return words.every(function (word) {
|
||||
return place.searchText.indexOf(word) >= 0;
|
||||
});
|
||||
}
|
||||
return function (place) {
|
||||
if (matches(place)) {
|
||||
addToSuggestions(place);
|
||||
}
|
||||
};
|
||||
}
|
||||
function filterChoicesByText() {
|
||||
suggestions.clear();
|
||||
placeMap = {};
|
||||
allStories.forEach(makeFilteredPlaceProcessor());
|
||||
}
|
||||
|
||||
function fillDestinations() {
|
||||
allStories = [];
|
||||
suggestions.clear();
|
||||
placeMap = {};
|
||||
getUserStoryPage(1, function (error) {
|
||||
console.log('user stories query', error || 'ok', allStories.length);
|
||||
});
|
||||
}
|
||||
|
||||
function updateLocationText(enteringAddress) {
|
||||
if (enteringAddress) {
|
||||
notice.text = "Go to a place, @user, path or network address";
|
||||
notice.color = hifiStyleConstants.colors.baseGrayHighlight;
|
||||
} else {
|
||||
notice.text = AddressManager.isConnected ? "Your location:" : "Not Connected";
|
||||
notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.baseGrayHighlight : hifiStyleConstants.colors.redHighlight;
|
||||
// Display hostname, which includes ip address, localhost, and other non-placenames.
|
||||
location.text = (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : '');
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
updateLocationText(false);
|
||||
if (visible) {
|
||||
addressLine.forceActiveFocus();
|
||||
fillDestinations();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
clearAddressLineTimer.start();
|
||||
event.accepted = true
|
||||
break
|
||||
case Qt.Key_Enter:
|
||||
case Qt.Key_Return:
|
||||
toggleOrGo()
|
||||
clearAddressLineTimer.start();
|
||||
event.accepted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.2
|
||||
import QtWebChannel 1.0
|
||||
import QtWebEngine 1.2
|
||||
import QtWebEngine 1.5
|
||||
|
||||
import "controls-uit"
|
||||
import "styles" as HifiStyles
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick 2.7
|
||||
import QtWebEngine 1.5
|
||||
|
||||
WebEngineView {
|
||||
|
|
|
@ -5,10 +5,16 @@ Item {
|
|||
id: keyItem
|
||||
width: 45
|
||||
height: 50
|
||||
|
||||
property int contentPadding: 4
|
||||
property string glyph: "a"
|
||||
property bool toggle: false // does this button have the toggle behaivor?
|
||||
property bool toggled: false // is this button currently toggled?
|
||||
property alias mouseArea: mouseArea1
|
||||
property alias fontFamily: letter.font.family;
|
||||
property alias fontPixelSize: letter.font.pixelSize
|
||||
property alias verticalAlignment: letter.verticalAlignment
|
||||
property alias letterAnchors: letter.anchors
|
||||
|
||||
function resetToggledMode(mode) {
|
||||
toggled = mode;
|
||||
|
@ -105,14 +111,8 @@ Item {
|
|||
color: "#121212"
|
||||
radius: 2
|
||||
border.color: "#00000000"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 4
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 4
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 4
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 4
|
||||
anchors.fill: parent
|
||||
anchors.margins: contentPadding
|
||||
}
|
||||
|
||||
Text {
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.0
|
||||
import QtQuick 2.7
|
||||
import QtGraphicalEffects 1.0
|
||||
import "."
|
||||
|
||||
Rectangle {
|
||||
|
@ -55,6 +56,8 @@ Rectangle {
|
|||
return ">";
|
||||
} else if (str === "/") {
|
||||
return "?";
|
||||
} else if (str === "-") {
|
||||
return "_";
|
||||
} else {
|
||||
return str.toUpperCase(str);
|
||||
}
|
||||
|
@ -67,6 +70,8 @@ Rectangle {
|
|||
return ".";
|
||||
} else if (str === "?") {
|
||||
return "/";
|
||||
} else if (str === "_") {
|
||||
return "-";
|
||||
} else {
|
||||
return str.toLowerCase(str);
|
||||
}
|
||||
|
@ -85,7 +90,7 @@ Rectangle {
|
|||
|
||||
onShiftModeChanged: {
|
||||
forEachKey(function (key) {
|
||||
if (/[a-z]/i.test(key.glyph)) {
|
||||
if (/[a-z-_]/i.test(key.glyph)) {
|
||||
if (shiftMode) {
|
||||
key.glyph = keyboardBase.toUpper(key.glyph);
|
||||
} else {
|
||||
|
@ -112,8 +117,6 @@ Rectangle {
|
|||
}
|
||||
|
||||
Rectangle {
|
||||
y: 0
|
||||
x: 0
|
||||
height: showMirrorText ? mirrorTextHeight : 0
|
||||
width: keyboardWidth
|
||||
color: "#252525"
|
||||
|
@ -122,13 +125,18 @@ Rectangle {
|
|||
TextInput {
|
||||
id: mirrorText
|
||||
visible: showMirrorText
|
||||
FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; }
|
||||
font.family: ralewaySemiBold.name
|
||||
font.pointSize: 13.5
|
||||
FontLoader { id: font; source: "../../fonts/FiraSans-Regular.ttf"; }
|
||||
font.family: font.name
|
||||
font.pixelSize: 20
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: "#FFFFFF";
|
||||
anchors.fill: parent
|
||||
color: "#00B4EF";
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
readOnly: false // we need this to allow control to accept QKeyEvent
|
||||
selectByMouse: false
|
||||
|
@ -140,16 +148,15 @@ Rectangle {
|
|||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea { // ... and we need this mouse area to prevent mirrorText from getting mouse events to ensure it will never get focus
|
||||
anchors.fill: parent
|
||||
MouseArea { // ... and we need this mouse area to prevent mirrorText from getting mouse events to ensure it will never get focus
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: keyboardRect
|
||||
x: 0
|
||||
y: showMirrorText ? mirrorTextHeight : 0
|
||||
width: keyboardWidth
|
||||
height: raisedHeight
|
||||
|
@ -158,6 +165,8 @@ Rectangle {
|
|||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
|
||||
FontLoader { id: hiFiGlyphs; source: pathToFonts + "fonts/hifi-glyphs.ttf"; }
|
||||
|
||||
Column {
|
||||
id: columnAlpha
|
||||
width: keyboardWidth
|
||||
|
@ -221,7 +230,7 @@ Rectangle {
|
|||
Key { width: 43; glyph: "b"; }
|
||||
Key { width: 43; glyph: "n"; }
|
||||
Key { width: 43; glyph: "m"; }
|
||||
Key { width: 43; glyph: "_"; }
|
||||
Key { width: 43; glyph: "-"; }
|
||||
Key { width: 43; glyph: "/"; }
|
||||
Key { width: 43; glyph: "?"; }
|
||||
}
|
||||
|
@ -240,8 +249,13 @@ Rectangle {
|
|||
Key { width: 231; glyph: " "; }
|
||||
Key { width: 43; glyph: ","; }
|
||||
Key { width: 43; glyph: "."; }
|
||||
Key { width: 43; glyph: "\u276C"; }
|
||||
Key { width: 43; glyph: "\u276D"; }
|
||||
Key {
|
||||
fontFamily: hiFiGlyphs.name;
|
||||
fontPixelSize: 48;
|
||||
letterAnchors.topMargin: -4;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
width: 86; glyph: "\ue02b";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,8 +342,13 @@ Rectangle {
|
|||
Key { width: 231; glyph: " "; }
|
||||
Key { width: 43; glyph: ","; }
|
||||
Key { width: 43; glyph: "."; }
|
||||
Key { width: 43; glyph: "\u276C"; }
|
||||
Key { width: 43; glyph: "\u276D"; }
|
||||
Key {
|
||||
fontFamily: hiFiGlyphs.name;
|
||||
fontPixelSize: 48;
|
||||
letterAnchors.topMargin: -4;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
width: 86; glyph: "\ue02b";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,12 @@ Item {
|
|||
id: hifi
|
||||
}
|
||||
|
||||
function unfocus() {
|
||||
webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) {
|
||||
console.log('unfocus completed: ', result);
|
||||
});
|
||||
}
|
||||
|
||||
function onLoadingChanged(loadRequest) {
|
||||
if (WebEngineView.LoadStartedStatus === loadRequest.status) {
|
||||
|
||||
|
@ -129,4 +135,10 @@ Item {
|
|||
playing: visible
|
||||
z: 10000
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if ((event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) {
|
||||
webViewCore.focus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,11 @@ Item {
|
|||
property alias urlTag: webroot.urlTag
|
||||
property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false
|
||||
property bool keyboardRaised: false
|
||||
onKeyboardRaisedChanged: {
|
||||
if(!keyboardRaised) {
|
||||
webroot.unfocus();
|
||||
}
|
||||
}
|
||||
property bool punctuationMode: false
|
||||
|
||||
// FIXME - Keyboard HMD only: Make Interface either set keyboardRaised property directly in OffscreenQmlSurface
|
||||
|
|
|
@ -15,6 +15,11 @@ Item {
|
|||
property string scriptURL
|
||||
property bool keyboardEnabled: false
|
||||
property bool keyboardRaised: false
|
||||
onKeyboardRaisedChanged: {
|
||||
if(!keyboardRaised) {
|
||||
webroot.unfocus();
|
||||
}
|
||||
}
|
||||
property bool punctuationMode: false
|
||||
property bool passwordField: false
|
||||
property bool isDesktop: false
|
||||
|
|
|
@ -12,6 +12,11 @@ Item {
|
|||
property alias urlTag: webroot.urlTag
|
||||
property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false
|
||||
property bool keyboardRaised: false
|
||||
onKeyboardRaisedChanged: {
|
||||
if(!keyboardRaised) {
|
||||
webroot.unfocus();
|
||||
}
|
||||
}
|
||||
property bool punctuationMode: false
|
||||
property bool passwordField: false
|
||||
property alias flickable: webroot.interactive
|
||||
|
|
|
@ -51,19 +51,23 @@ FocusScope {
|
|||
|
||||
// The VR version of the primary menu
|
||||
property var rootMenu: Menu {
|
||||
id: rootMenuId
|
||||
objectName: "rootMenu"
|
||||
|
||||
// for some reasons it is not possible to use just '({})' here as it gets empty when passed to TableRoot/DesktopRoot
|
||||
property var exclusionGroupsByMenuItem : ListModel {}
|
||||
property var exclusionGroups: ({});
|
||||
property Component exclusiveGroupMaker: Component {
|
||||
ExclusiveGroup {
|
||||
}
|
||||
}
|
||||
|
||||
function addExclusionGroup(menuItem, exclusionGroup)
|
||||
{
|
||||
exclusionGroupsByMenuItem.append(
|
||||
{
|
||||
'menuItem' : menuItem.toString(),
|
||||
'exclusionGroup' : exclusionGroup.toString()
|
||||
}
|
||||
);
|
||||
function addExclusionGroup(qmlAction, exclusionGroup) {
|
||||
|
||||
var exclusionGroupId = exclusionGroup.toString();
|
||||
if(!exclusionGroups[exclusionGroupId]) {
|
||||
exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(rootMenuId);
|
||||
}
|
||||
|
||||
qmlAction.exclusiveGroup = exclusionGroups[exclusionGroupId]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,10 +16,10 @@ import Qt.labs.settings 1.0
|
|||
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
import "../windows"
|
||||
import "../windows" as Windows
|
||||
import "../dialogs"
|
||||
|
||||
ScrollingWindow {
|
||||
Windows.ScrollingWindow {
|
||||
id: root
|
||||
objectName: "AssetServer"
|
||||
title: "Asset Browser"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtWebEngine 1.1;
|
||||
import QtWebEngine 1.5;
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import "../desktop" as OriginalDesktop
|
||||
|
|
|
@ -432,7 +432,8 @@ Item {
|
|||
anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter
|
||||
x: 240
|
||||
onClicked: {
|
||||
AddressManager.goToUser(thisNameCard.userName);
|
||||
console.log("Vist user button clicked."); // Remove after debugging.
|
||||
AddressManager.goToUser(thisNameCard.userName, false);
|
||||
UserActivityLogger.palAction("go_to_user", thisNameCard.userName);
|
||||
}
|
||||
}
|
||||
|
@ -441,7 +442,7 @@ Item {
|
|||
Rectangle {
|
||||
id: nameCardVUMeter
|
||||
// Size
|
||||
width: isMyCard ? myDisplayName.width - 20 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * (gainSlider.width);
|
||||
width: ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * (gainSlider.width);
|
||||
height: 8
|
||||
// Anchors
|
||||
anchors.bottom: isMyCard ? avatarImage.bottom : parent.bottom;
|
||||
|
@ -525,16 +526,14 @@ Item {
|
|||
anchors.verticalCenter: nameCardVUMeter.verticalCenter;
|
||||
anchors.left: nameCardVUMeter.left;
|
||||
// Properties
|
||||
visible: !isMyCard && selected && pal.activeTab == "nearbyTab" && isPresent;
|
||||
visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent;
|
||||
value: Users.getAvatarGain(uuid)
|
||||
minimumValue: -60.0
|
||||
maximumValue: 20.0
|
||||
stepSize: 5
|
||||
updateValueWhileDragging: true
|
||||
onValueChanged: {
|
||||
if (uuid !== "") {
|
||||
updateGainFromQML(uuid, value, false);
|
||||
}
|
||||
updateGainFromQML(uuid, value, false);
|
||||
}
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
|
@ -574,7 +573,19 @@ Item {
|
|||
implicitHeight: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
RalewayRegular {
|
||||
// The slider for my card is special, it controls the master gain
|
||||
id: gainSliderText;
|
||||
visible: isMyCard;
|
||||
text: "master volume";
|
||||
size: hifi.fontSizes.tabularData;
|
||||
anchors.left: parent.right;
|
||||
anchors.leftMargin: 8;
|
||||
color: hifi.colors.baseGrayHighlight;
|
||||
horizontalAlignment: Text.AlignLeft;
|
||||
verticalAlignment: Text.AlignTop;
|
||||
}
|
||||
}
|
||||
|
||||
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
|
||||
Users.setAvatarGain(avatarUuid, sliderValue);
|
||||
|
@ -594,7 +605,10 @@ Item {
|
|||
// the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now.
|
||||
// FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script.
|
||||
// Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target.
|
||||
// Position avatar 2 metres from the target in the direction that target avatar was facing.
|
||||
MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2}));
|
||||
MyAvatar.orientation = Quat.multiply(avatar.orientation, {y: 1});
|
||||
|
||||
// Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch.
|
||||
MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -827,7 +827,7 @@ Rectangle {
|
|||
hoverEnabled: enabled
|
||||
enabled: connectionsNameCard.selected && pal.activeTab == "connectionsTab"
|
||||
onClicked: {
|
||||
AddressManager.goToUser(model.userName);
|
||||
AddressManager.goToUser(model.userName, false);
|
||||
UserActivityLogger.palAction("go_to_user", model.userName);
|
||||
}
|
||||
onEntered: connectionsLocationData.color = hifi.colors.blueHighlight;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
//
|
||||
// WebBrowser.qml
|
||||
//
|
||||
|
@ -9,12 +10,12 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.5 as QQControls
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2 as QQControls
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import QtWebEngine 1.2
|
||||
import QtWebEngine 1.5
|
||||
import QtWebChannel 1.0
|
||||
|
||||
import "../styles-uit"
|
||||
|
@ -22,6 +23,8 @@ import "../controls-uit" as HifiControls
|
|||
import "../windows"
|
||||
import "../controls"
|
||||
|
||||
import HifiWeb 1.0
|
||||
|
||||
Rectangle {
|
||||
id: root;
|
||||
|
||||
|
@ -32,214 +35,401 @@ Rectangle {
|
|||
property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
property var suggestionsList: []
|
||||
readonly property string searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q=";
|
||||
|
||||
|
||||
WebBrowserSuggestionsEngine {
|
||||
id: searchEngine
|
||||
|
||||
onSuggestions: {
|
||||
if (suggestions.length > 0) {
|
||||
suggestionsList = []
|
||||
suggestionsList.push(addressBarInput.text); //do not overwrite edit text
|
||||
for(var i = 0; i < suggestions.length; i++) {
|
||||
suggestionsList.push(suggestions[i]);
|
||||
}
|
||||
addressBar.model = suggestionsList
|
||||
if (!addressBar.popup.visible) {
|
||||
addressBar.popup.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: suggestionRequestTimer
|
||||
interval: 200
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (addressBar.editText !== "") {
|
||||
searchEngine.querySuggestions(addressBarInput.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
color: hifi.colors.baseGray;
|
||||
|
||||
// only show the title if loaded through a "loader"
|
||||
function goTo(url) {
|
||||
//must be valid attempt to open an site with dot
|
||||
var urlNew = url
|
||||
if (url.indexOf(".") > 0) {
|
||||
if (url.indexOf("http") < 0) {
|
||||
urlNew = "http://" + url;
|
||||
}
|
||||
} else {
|
||||
urlNew = searchUrlTemplate + url
|
||||
}
|
||||
|
||||
addressBar.model = []
|
||||
//need to rebind if binfing was broken by selecting from suggestions
|
||||
addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; });
|
||||
webStack.currentItem.webEngineView.url = urlNew
|
||||
suggestionRequestTimer.stop();
|
||||
addressBar.popup.close();
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
width: parent.width;
|
||||
|
||||
RowLayout {
|
||||
id: addressBarRow
|
||||
width: parent.width;
|
||||
height: 48
|
||||
|
||||
HifiControls.WebGlyphButton {
|
||||
enabled: webEngineView.canGoBack
|
||||
enabled: webStack.currentItem.webEngineView.canGoBack || webStack.depth > 1
|
||||
glyph: hifi.glyphs.backward;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
size: 38;
|
||||
onClicked: {
|
||||
webEngineView.goBack()
|
||||
if (webStack.currentItem.webEngineView.canGoBack) {
|
||||
webStack.currentItem.webEngineView.goBack();
|
||||
} else if (webStack.depth > 1) {
|
||||
webStack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.WebGlyphButton {
|
||||
enabled: webEngineView.canGoForward
|
||||
enabled: webStack.currentItem.webEngineView.canGoForward
|
||||
glyph: hifi.glyphs.forward;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
size: 38;
|
||||
onClicked: {
|
||||
webEngineView.goForward()
|
||||
webStack.currentItem.webEngineView.goForward();
|
||||
}
|
||||
}
|
||||
|
||||
QQControls.TextField {
|
||||
QQControls.ComboBox {
|
||||
id: addressBar
|
||||
|
||||
Image {
|
||||
anchors.verticalCenter: addressBar.verticalCenter;
|
||||
x: 5
|
||||
z: 2
|
||||
id: faviconImage
|
||||
width: 16; height: 16
|
||||
sourceSize: Qt.size(width, height)
|
||||
source: webEngineView.icon
|
||||
//selectByMouse: true
|
||||
focus: true
|
||||
|
||||
editable: true
|
||||
//flat: true
|
||||
indicator: Item {}
|
||||
background: Item {}
|
||||
onActivated: {
|
||||
goTo(textAt(index));
|
||||
}
|
||||
|
||||
HifiControls.WebGlyphButton {
|
||||
glyph: webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
width: hifi.dimensions.controlLineHeight
|
||||
z: 2
|
||||
x: addressBar.width - 28
|
||||
onClicked: {
|
||||
if (webEngineView.loading) {
|
||||
webEngineView.stop()
|
||||
} else {
|
||||
reloadTimer.start()
|
||||
onHighlightedIndexChanged: {
|
||||
if (highlightedIndex >= 0) {
|
||||
addressBar.editText = textAt(highlightedIndex)
|
||||
}
|
||||
}
|
||||
|
||||
popup.height: webStack.height
|
||||
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
addressBarInput.selectAll();
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: QQControls.TextField {
|
||||
id: addressBarInput
|
||||
leftPadding: 26
|
||||
rightPadding: hifi.dimensions.controlLineHeight + 5
|
||||
text: addressBar.editText
|
||||
placeholderText: qsTr("Enter URL")
|
||||
font: addressBar.font
|
||||
selectByMouse: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
selectAll();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onDeletePressed: {
|
||||
addressBarInput.text = ""
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_Return) {
|
||||
goTo(addressBarInput.text);
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
x: 5
|
||||
z: 2
|
||||
id: faviconImage
|
||||
width: 16; height: 16
|
||||
sourceSize: Qt.size(width, height)
|
||||
source: webStack.currentItem.webEngineView.icon
|
||||
}
|
||||
|
||||
HifiControls.WebGlyphButton {
|
||||
glyph: webStack.currentItem.webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
width: hifi.dimensions.controlLineHeight
|
||||
z: 2
|
||||
x: addressBarInput.width - implicitWidth
|
||||
onClicked: {
|
||||
if (webStack.currentItem.webEngineView.loading) {
|
||||
webStack.currentItem.webEngineView.stop();
|
||||
} else {
|
||||
webStack.currentItem.reloadTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style: TextFieldStyle {
|
||||
padding {
|
||||
left: 26;
|
||||
right: 26
|
||||
Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i");
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_Return) {
|
||||
goTo(addressBarInput.text);
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
focus: true
|
||||
|
||||
onEditTextChanged: {
|
||||
if (addressBar.editText !== "" && addressBar.editText !== webStack.currentItem.webEngineView.url.toString()) {
|
||||
suggestionRequestTimer.restart();
|
||||
} else {
|
||||
addressBar.model = []
|
||||
addressBar.popup.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
text: webEngineView.url
|
||||
onAccepted: webEngineView.url = text
|
||||
editText: webStack.currentItem.webEngineView.url
|
||||
onAccepted: goTo(addressBarInput.text);
|
||||
}
|
||||
|
||||
HifiControls.WebGlyphButton {
|
||||
checkable: true
|
||||
//only QtWebEngine 1.3
|
||||
//checked: webEngineView.audioMuted
|
||||
checked: webStack.currentItem.webEngineView.audioMuted
|
||||
glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
width: hifi.dimensions.controlLineHeight
|
||||
onClicked: {
|
||||
webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute)
|
||||
webStack.currentItem.webEngineView.audioMuted = !webStack.currentItem.webEngineView.audioMuted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQControls.ProgressBar {
|
||||
id: loadProgressBar
|
||||
style: ProgressBarStyle {
|
||||
background: Rectangle {
|
||||
color: "#6A6A6A"
|
||||
}
|
||||
progress: Rectangle{
|
||||
background: Rectangle {
|
||||
implicitHeight: 2
|
||||
color: "#6A6A6A"
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
implicitHeight: 2
|
||||
|
||||
Rectangle {
|
||||
width: loadProgressBar.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: "#00B4EF"
|
||||
}
|
||||
}
|
||||
|
||||
width: parent.width;
|
||||
minimumValue: 0
|
||||
maximumValue: 100
|
||||
value: webEngineView.loadProgress
|
||||
from: 0
|
||||
to: 100
|
||||
value: webStack.currentItem.webEngineView.loadProgress
|
||||
height: 2
|
||||
}
|
||||
|
||||
HifiControls.BaseWebView {
|
||||
id: webEngineView
|
||||
focus: true
|
||||
objectName: "tabletWebEngineView"
|
||||
Component {
|
||||
id: webViewComponent
|
||||
Rectangle {
|
||||
property alias webEngineView: webEngineView
|
||||
property alias reloadTimer: reloadTimer
|
||||
|
||||
url: "http://www.highfidelity.com"
|
||||
property real webViewHeight: root.height - loadProgressBar.height - 48 - 4
|
||||
property WebEngineNewViewRequest request: null
|
||||
|
||||
width: parent.width;
|
||||
height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight
|
||||
property bool isDialog: QQControls.StackView.index > 0
|
||||
property real margins: isDialog ? 10 : 0
|
||||
|
||||
profile: HFWebEngineProfile;
|
||||
color: "#d1d1d1"
|
||||
|
||||
property string userScriptUrl: ""
|
||||
|
||||
// creates a global EventBridge object.
|
||||
WebEngineScript {
|
||||
id: createGlobalEventBridge
|
||||
sourceCode: eventBridgeJavaScriptToInject
|
||||
injectionPoint: WebEngineScript.DocumentCreation
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
// detects when to raise and lower virtual keyboard
|
||||
WebEngineScript {
|
||||
id: raiseAndLowerKeyboard
|
||||
injectionPoint: WebEngineScript.Deferred
|
||||
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
// User script.
|
||||
WebEngineScript {
|
||||
id: userScript
|
||||
sourceUrl: webEngineView.userScriptUrl
|
||||
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
||||
|
||||
settings.autoLoadImages: true
|
||||
settings.javascriptEnabled: true
|
||||
settings.errorPageEnabled: true
|
||||
settings.pluginsEnabled: true
|
||||
settings.fullScreenSupportEnabled: false
|
||||
//from WebEngine 1.3
|
||||
// settings.autoLoadIconsForPage: false
|
||||
// settings.touchIconsEnabled: false
|
||||
|
||||
onCertificateError: {
|
||||
error.defer();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
webChannel.registerObject("eventBridge", eventBridge);
|
||||
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
||||
webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)";
|
||||
}
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
grantFeaturePermission(securityOrigin, feature, true);
|
||||
}
|
||||
|
||||
onNewViewRequested: {
|
||||
if (!request.userInitiated) {
|
||||
print("Warning: Blocked a popup window.");
|
||||
}
|
||||
}
|
||||
|
||||
onRenderProcessTerminated: {
|
||||
var status = "";
|
||||
switch (terminationStatus) {
|
||||
case WebEngineView.NormalTerminationStatus:
|
||||
status = "(normal exit)";
|
||||
break;
|
||||
case WebEngineView.AbnormalTerminationStatus:
|
||||
status = "(abnormal exit)";
|
||||
break;
|
||||
case WebEngineView.CrashedTerminationStatus:
|
||||
status = "(crashed)";
|
||||
break;
|
||||
case WebEngineView.KilledTerminationStatus:
|
||||
status = "(killed)";
|
||||
break;
|
||||
QQControls.StackView.onActivated: {
|
||||
addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; });
|
||||
}
|
||||
|
||||
print("Render process exited with code " + exitCode + " " + status);
|
||||
reloadTimer.running = true;
|
||||
}
|
||||
onRequestChanged: {
|
||||
if (isDialog && request !== null && request !== undefined) {//is Dialog ?
|
||||
request.openIn(webEngineView);
|
||||
}
|
||||
}
|
||||
|
||||
onWindowCloseRequested: {
|
||||
}
|
||||
HifiControls.BaseWebView {
|
||||
id: webEngineView
|
||||
anchors.fill: parent
|
||||
anchors.margins: parent.margins
|
||||
|
||||
Timer {
|
||||
id: reloadTimer
|
||||
interval: 0
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: webEngineView.reload()
|
||||
layer.enabled: parent.isDialog
|
||||
layer.effect: DropShadow {
|
||||
verticalOffset: 8
|
||||
horizontalOffset: 8
|
||||
color: "#330066ff"
|
||||
samples: 10
|
||||
spread: 0.5
|
||||
}
|
||||
|
||||
focus: true
|
||||
objectName: "tabletWebEngineView"
|
||||
|
||||
//profile: HFWebEngineProfile;
|
||||
profile.httpUserAgent: "Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0"
|
||||
|
||||
property string userScriptUrl: ""
|
||||
|
||||
onLoadingChanged: {
|
||||
if (!loading) {
|
||||
addressBarInput.cursorPosition = 0 //set input field cursot to beginning
|
||||
suggestionRequestTimer.stop();
|
||||
addressBar.popup.close();
|
||||
}
|
||||
}
|
||||
|
||||
onLinkHovered: {
|
||||
//TODO: change cursor shape?
|
||||
}
|
||||
|
||||
// creates a global EventBridge object.
|
||||
WebEngineScript {
|
||||
id: createGlobalEventBridge
|
||||
sourceCode: eventBridgeJavaScriptToInject
|
||||
injectionPoint: WebEngineScript.Deferred
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
// detects when to raise and lower virtual keyboard
|
||||
WebEngineScript {
|
||||
id: raiseAndLowerKeyboard
|
||||
injectionPoint: WebEngineScript.Deferred
|
||||
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
// User script.
|
||||
WebEngineScript {
|
||||
id: userScript
|
||||
sourceUrl: webEngineView.userScriptUrl
|
||||
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
||||
|
||||
settings.autoLoadImages: true
|
||||
settings.javascriptEnabled: true
|
||||
settings.errorPageEnabled: true
|
||||
settings.pluginsEnabled: true
|
||||
settings.fullScreenSupportEnabled: true
|
||||
settings.autoLoadIconsForPage: true
|
||||
settings.touchIconsEnabled: true
|
||||
|
||||
onCertificateError: {
|
||||
error.defer();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
webChannel.registerObject("eventBridge", eventBridge);
|
||||
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
||||
}
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
grantFeaturePermission(securityOrigin, feature, true);
|
||||
}
|
||||
|
||||
onNewViewRequested: {
|
||||
if (request.destination == WebEngineView.NewViewInDialog) {
|
||||
webStack.push(webViewComponent, {"request": request});
|
||||
} else {
|
||||
request.openIn(webEngineView);
|
||||
}
|
||||
}
|
||||
|
||||
onRenderProcessTerminated: {
|
||||
var status = "";
|
||||
switch (terminationStatus) {
|
||||
case WebEngineView.NormalTerminationStatus:
|
||||
status = "(normal exit)";
|
||||
break;
|
||||
case WebEngineView.AbnormalTerminationStatus:
|
||||
status = "(abnormal exit)";
|
||||
break;
|
||||
case WebEngineView.CrashedTerminationStatus:
|
||||
status = "(crashed)";
|
||||
break;
|
||||
case WebEngineView.KilledTerminationStatus:
|
||||
status = "(killed)";
|
||||
break;
|
||||
}
|
||||
|
||||
console.error("Render process exited with code " + exitCode + " " + status);
|
||||
reloadTimer.running = true;
|
||||
}
|
||||
|
||||
onFullScreenRequested: {
|
||||
if (request.toggleOn) {
|
||||
webEngineView.state = "FullScreen";
|
||||
} else {
|
||||
webEngineView.state = "";
|
||||
}
|
||||
request.accept();
|
||||
}
|
||||
|
||||
onWindowCloseRequested: {
|
||||
webStack.pop();
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
id: reloadTimer
|
||||
interval: 0
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: webEngineView.reload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQControls.StackView {
|
||||
id: webStack
|
||||
width: parent.width;
|
||||
property real webViewHeight: root.height - loadProgressBar.height - 48 - 4
|
||||
height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight
|
||||
|
||||
Component.onCompleted: webStack.push(webViewComponent, {"webEngineView.url": "https://www.highfidelity.com"});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
||||
|
|
|
@ -213,8 +213,8 @@ Rectangle {
|
|||
anchors.right: parent.right
|
||||
peak: model.peak;
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: (bar.currentIndex === 1 && selectedHMD && isVR) ||
|
||||
(bar.currentIndex === 0 && selectedDesktop && !isVR) &&
|
||||
visible: ((bar.currentIndex === 1 && isVR) ||
|
||||
(bar.currentIndex === 0 && !isVR)) &&
|
||||
Audio.devices.input.peakValuesAvailable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -596,9 +596,7 @@ Rectangle {
|
|||
anchors.right: parent.right;
|
||||
text: root.isWearable ? "Wear It" : "Rez It"
|
||||
onClicked: {
|
||||
if (urlHandler.canHandleUrl(root.itemHref)) {
|
||||
urlHandler.handleUrl(root.itemHref);
|
||||
}
|
||||
sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
|
||||
rezzedNotifContainer.visible = true;
|
||||
rezzedNotifContainerTimer.start();
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ Rectangle {
|
|||
property string dateOfPurchase: "--";
|
||||
property bool isLightbox: false;
|
||||
property bool isMyCert: false;
|
||||
property bool isCertificateInvalid: false;
|
||||
// Style
|
||||
color: hifi.colors.faintGray;
|
||||
Hifi.QmlCommerce {
|
||||
|
@ -44,10 +45,11 @@ Rectangle {
|
|||
} else {
|
||||
root.marketplaceUrl = result.data.marketplace_item_url;
|
||||
root.isMyCert = result.isMyCert ? result.isMyCert : false;
|
||||
root.itemOwner = root.isMyCert ? Account.username :
|
||||
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
||||
root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run);
|
||||
root.dateOfPurchase = getFormattedDate(result.data.transfer_created_at * 1000);
|
||||
root.itemOwner = root.isCertificateInvalid ? "--" : (root.isMyCert ? Account.username :
|
||||
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022");
|
||||
root.itemEdition = root.isCertificateInvalid ? "Uncertified Copy" :
|
||||
(result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run));
|
||||
root.dateOfPurchase = root.isCertificateInvalid ? "" : getFormattedDate(result.data.transfer_created_at * 1000);
|
||||
root.itemName = result.data.marketplace_item_name;
|
||||
|
||||
if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") {
|
||||
|
@ -65,6 +67,44 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
onUpdateCertificateStatus: {
|
||||
if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS
|
||||
// NOP
|
||||
} else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT
|
||||
root.isCertificateInvalid = true;
|
||||
errorText.text = "Verification of this certificate timed out.";
|
||||
errorText.color = hifi.colors.redHighlight;
|
||||
} else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED
|
||||
root.isCertificateInvalid = true;
|
||||
titleBarText.text = "Invalid Certificate";
|
||||
titleBarText.color = hifi.colors.redHighlight;
|
||||
|
||||
popText.text = "";
|
||||
root.itemOwner = "";
|
||||
dateOfPurchaseHeader.text = "";
|
||||
root.dateOfPurchase = "";
|
||||
root.itemEdition = "Uncertified Copy";
|
||||
|
||||
errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item.";
|
||||
errorText.color = hifi.colors.baseGray;
|
||||
} else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED
|
||||
root.isCertificateInvalid = true;
|
||||
titleBarText.text = "Invalid Certificate";
|
||||
titleBarText.color = hifi.colors.redHighlight;
|
||||
|
||||
popText.text = "";
|
||||
root.itemOwner = "";
|
||||
dateOfPurchaseHeader.text = "";
|
||||
root.dateOfPurchase = "";
|
||||
root.itemEdition = "Uncertified Copy";
|
||||
|
||||
errorText.text = "The avatar who rezzed this item doesn't own it.";
|
||||
errorText.color = hifi.colors.baseGray;
|
||||
} else {
|
||||
console.log("Unknown certificate status received from ledger signal!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCertificateIdChanged: {
|
||||
|
@ -73,21 +113,6 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
titleBarText.text = "Certificate";
|
||||
popText.text = "PROOF OF PURCHASE";
|
||||
root.certificateId = "";
|
||||
root.itemName = "--";
|
||||
root.itemOwner = "--";
|
||||
root.itemEdition = "--";
|
||||
root.dateOfPurchase = "--";
|
||||
root.marketplaceUrl = "";
|
||||
root.isMyCert = false;
|
||||
errorText.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
// This object is always used in a popup.
|
||||
// This MouseArea is used to prevent a user from being
|
||||
// able to click on a button/mouseArea underneath the popup.
|
||||
|
@ -216,7 +241,7 @@ Rectangle {
|
|||
}
|
||||
AnonymousProRegular {
|
||||
id: isMyCertText;
|
||||
visible: root.isMyCert;
|
||||
visible: root.isMyCert && !root.isCertificateInvalid;
|
||||
text: "(Private)";
|
||||
size: 18;
|
||||
// Anchors
|
||||
|
@ -380,6 +405,18 @@ Rectangle {
|
|||
case 'inspectionCertificate_setCertificateId':
|
||||
root.certificateId = message.certificateId;
|
||||
break;
|
||||
case 'inspectionCertificate_resetCert':
|
||||
titleBarText.text = "Certificate";
|
||||
popText.text = "PROOF OF PURCHASE";
|
||||
root.certificateId = "";
|
||||
root.itemName = "--";
|
||||
root.itemOwner = "--";
|
||||
root.itemEdition = "--";
|
||||
root.dateOfPurchase = "--";
|
||||
root.marketplaceUrl = "";
|
||||
root.isMyCert = false;
|
||||
errorText.text = "";
|
||||
break;
|
||||
default:
|
||||
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
|
||||
}
|
||||
|
|
|
@ -346,9 +346,7 @@ Item {
|
|||
enabled: (root.canRezCertifiedItems || root.isWearable) && root.purchaseStatus !== "invalidated";
|
||||
|
||||
onClicked: {
|
||||
if (urlHandler.canHandleUrl(root.itemHref)) {
|
||||
urlHandler.handleUrl(root.itemHref);
|
||||
}
|
||||
sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
|
||||
rezzedNotifContainer.visible = true;
|
||||
rezzedNotifContainerTimer.start();
|
||||
}
|
||||
|
|
|
@ -442,6 +442,8 @@ Rectangle {
|
|||
onSendToPurchases: {
|
||||
if (msg.method === 'purchases_itemInfoClicked') {
|
||||
sendToScript({method: 'purchases_itemInfoClicked', itemId: itemId});
|
||||
} else if (msg.method === "purchases_rezClicked") {
|
||||
sendToScript({method: 'purchases_rezClicked', itemHref: itemHref, isWearable: isWearable});
|
||||
} else if (msg.method === 'purchases_itemCertificateClicked') {
|
||||
inspectionCertificate.visible = true;
|
||||
inspectionCertificate.isLightbox = true;
|
||||
|
@ -638,7 +640,8 @@ Rectangle {
|
|||
if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
|
||||
if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) {
|
||||
filteredPurchasesModel.insert(0, purchasesModel.get(i));
|
||||
} else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === -1) || !root.isShowingMyItems) {
|
||||
} else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") ||
|
||||
(!root.isShowingMyItems && purchasesModel.get(i).edition_number !== "0")) {
|
||||
filteredPurchasesModel.append(purchasesModel.get(i));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,7 +196,38 @@ Item {
|
|||
anchors.bottom: parent.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 24;
|
||||
|
||||
Item {
|
||||
visible: transactionHistoryModel.count === 0 && root.historyReceived;
|
||||
anchors.centerIn: parent;
|
||||
width: parent.width - 12;
|
||||
height: parent.height;
|
||||
|
||||
HifiControlsUit.Separator {
|
||||
colorScheme: 1;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
anchors.top: parent.top;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: noActivityText;
|
||||
text: "<b>The Wallet app is in closed Beta.</b><br><br>To request entry and <b>receive free HFC</b>, please contact " +
|
||||
"<b>info@highfidelity.com</b> with your High Fidelity account username and the email address registered to that account.";
|
||||
// Text size
|
||||
size: 24;
|
||||
// Style
|
||||
color: hifi.colors.blueAccent;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 12;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 12;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
height: paintedHeight;
|
||||
wrapMode: Text.WordWrap;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: transactionHistory;
|
||||
|
@ -294,17 +325,6 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should never be visible (since you immediately get 100 HFC)
|
||||
FiraSansRegular {
|
||||
id: emptyTransationHistory;
|
||||
size: 24;
|
||||
visible: !transactionHistory.visible && root.historyReceived;
|
||||
text: "Recent Activity Unavailable";
|
||||
anchors.fill: parent;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -350,7 +370,9 @@ Item {
|
|||
}
|
||||
|
||||
root.pendingCount = pendingCount;
|
||||
transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"});
|
||||
if (pendingCount > 0) {
|
||||
transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -53,8 +53,6 @@ Item {
|
|||
onWalletAuthenticatedStatusResult: {
|
||||
if (isAuthenticated) {
|
||||
root.activeView = "step_4";
|
||||
} else {
|
||||
root.activeView = "step_3";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ Rectangle {
|
|||
|
||||
color: hifi.colors.baseGray
|
||||
|
||||
property bool keyboardEnabled: HMD.active
|
||||
property bool keyboardRaised: false
|
||||
|
||||
LetterboxMessage {
|
||||
id: letterBoxMessage
|
||||
|
@ -380,7 +382,7 @@ Rectangle {
|
|||
Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i")
|
||||
onActiveFocusChanged: {
|
||||
// raise the keyboard
|
||||
keyboard.raised = activeFocus;
|
||||
root.keyboardRaised = activeFocus;
|
||||
|
||||
// scroll to the bottom of the content area.
|
||||
if (activeFocus) {
|
||||
|
@ -481,7 +483,7 @@ Rectangle {
|
|||
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard
|
||||
raised: false
|
||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
||||
numeric: false
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
|
|
|
@ -11,8 +11,11 @@
|
|||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
|
||||
import "../../styles-uit"
|
||||
import "../../controls-uit"
|
||||
import "../dialogs"
|
||||
|
||||
Rectangle {
|
||||
id: newModelDialog
|
||||
|
@ -25,6 +28,15 @@ Rectangle {
|
|||
property bool punctuationMode: false
|
||||
property bool keyboardRasied: false
|
||||
|
||||
function errorMessageBox(message) {
|
||||
return desktop.messageBox({
|
||||
icon: hifi.icons.warning,
|
||||
defaultButton: OriginalDialogs.StandardButton.Ok,
|
||||
title: "Error",
|
||||
text: message
|
||||
});
|
||||
}
|
||||
|
||||
Item {
|
||||
id: column1
|
||||
anchors.rightMargin: 10
|
||||
|
@ -98,7 +110,6 @@ Rectangle {
|
|||
CheckBox {
|
||||
id: dynamic
|
||||
text: qsTr("Dynamic")
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
|
@ -117,6 +128,7 @@ Rectangle {
|
|||
Text {
|
||||
id: text2
|
||||
width: 160
|
||||
x: dynamic.width / 2
|
||||
color: "#ffffff"
|
||||
text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors")
|
||||
wrapMode: Text.WordWrap
|
||||
|
@ -139,15 +151,40 @@ Rectangle {
|
|||
|
||||
ComboBox {
|
||||
id: collisionType
|
||||
|
||||
property int priorIndex: 0
|
||||
property string staticMeshCollisionText: "Exact - All polygons"
|
||||
property var collisionArray: ["No Collision",
|
||||
"Basic - Whole model",
|
||||
"Good - Sub-meshes",
|
||||
staticMeshCollisionText,
|
||||
"Box",
|
||||
"Sphere"]
|
||||
|
||||
width: 200
|
||||
z: 100
|
||||
transformOrigin: Item.Center
|
||||
model: ["No Collision",
|
||||
"Basic - Whole model",
|
||||
"Good - Sub-meshes",
|
||||
"Exact - All polygons",
|
||||
"Box",
|
||||
"Sphere"]
|
||||
model: collisionArray
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
if (collisionArray[currentIndex] === staticMeshCollisionText) {
|
||||
|
||||
if (dynamic.checked) {
|
||||
currentIndex = priorIndex;
|
||||
|
||||
errorMessageBox("Models with Automatic Collisions set to \""
|
||||
+ staticMeshCollisionText + "\" cannot be dynamic.");
|
||||
//--EARLY EXIT--( Can't have a static mesh model that's dynamic )
|
||||
return;
|
||||
}
|
||||
|
||||
dynamic.enabled = false;
|
||||
} else {
|
||||
dynamic.enabled = true;
|
||||
}
|
||||
|
||||
priorIndex = currentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
|
@ -155,10 +192,10 @@ Rectangle {
|
|||
width: 200
|
||||
height: 400
|
||||
spacing: 5
|
||||
|
||||
anchors {
|
||||
rightMargin: 15
|
||||
}
|
||||
|
||||
anchors.horizontalCenter: column3.horizontalCenter
|
||||
anchors.horizontalCenterOffset: -20
|
||||
|
||||
Button {
|
||||
id: button1
|
||||
text: qsTr("Add")
|
||||
|
|
|
@ -8,8 +8,8 @@ import "../audio" as HifiAudio
|
|||
Item {
|
||||
id: tablet
|
||||
objectName: "tablet"
|
||||
property int rowIndex: 0
|
||||
property int columnIndex: 0
|
||||
property int rowIndex: 6 // by default
|
||||
property int columnIndex: 1 // point to 'go to location'
|
||||
property int count: (flowMain.children.length - 1)
|
||||
|
||||
// used to look up a button by its uuid
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
import Hifi 1.0
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../../controls"
|
||||
import "../../styles"
|
||||
|
@ -83,7 +84,6 @@ StackView {
|
|||
anchors.centerIn = parent;
|
||||
}
|
||||
|
||||
|
||||
function resetAfterTeleport() {
|
||||
//storyCardFrame.shown = root.shown = false;
|
||||
}
|
||||
|
@ -134,7 +134,8 @@ StackView {
|
|||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
onHostChanged: updateLocationTextTimer.start();
|
||||
onHostChanged: updateLocationTextTimer.restart();
|
||||
|
||||
Rectangle {
|
||||
id: navBar
|
||||
width: parent.width
|
||||
|
@ -205,16 +206,16 @@ StackView {
|
|||
anchors {
|
||||
top: parent.top;
|
||||
left: addressLineContainer.left;
|
||||
right: addressLineContainer.right;
|
||||
}
|
||||
}
|
||||
|
||||
HifiStyles.FiraSansRegular {
|
||||
id: location;
|
||||
anchors {
|
||||
left: addressLineContainer.left;
|
||||
leftMargin: 8;
|
||||
verticalCenter: addressLineContainer.verticalCenter;
|
||||
left: notice.right
|
||||
leftMargin: 8
|
||||
right: addressLineContainer.right
|
||||
verticalCenter: notice.verticalCenter
|
||||
}
|
||||
font.pixelSize: addressLine.font.pixelSize;
|
||||
color: "gray";
|
||||
|
@ -222,7 +223,7 @@ StackView {
|
|||
visible: addressLine.text.length === 0
|
||||
}
|
||||
|
||||
TextInput {
|
||||
TextField {
|
||||
id: addressLine
|
||||
width: addressLineContainer.width - addressLineContainer.anchors.leftMargin - addressLineContainer.anchors.rightMargin;
|
||||
anchors {
|
||||
|
@ -230,7 +231,6 @@ StackView {
|
|||
leftMargin: 8;
|
||||
verticalCenter: addressLineContainer.verticalCenter;
|
||||
}
|
||||
font.pixelSize: hifi.fonts.pixelSize * 0.75
|
||||
onTextChanged: {
|
||||
updateLocationText(text.length > 0);
|
||||
}
|
||||
|
@ -238,6 +238,17 @@ StackView {
|
|||
addressBarDialog.keyboardEnabled = false;
|
||||
toggleOrGo();
|
||||
}
|
||||
placeholderText: "Type domain address here"
|
||||
verticalAlignment: TextInput.AlignBottom
|
||||
style: TextFieldStyle {
|
||||
textColor: hifi.colors.text
|
||||
placeholderTextColor: "gray"
|
||||
font {
|
||||
family: hifi.fonts.fontFamily
|
||||
pixelSize: hifi.fonts.pixelSize * 0.75
|
||||
}
|
||||
background: Item {}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -347,7 +358,7 @@ StackView {
|
|||
// Delay updating location text a bit to avoid flicker of content and so that connection status is valid.
|
||||
id: updateLocationTextTimer
|
||||
running: false
|
||||
interval: 500 // ms
|
||||
interval: 1000 // ms
|
||||
repeat: false
|
||||
onTriggered: updateLocationText(false);
|
||||
}
|
||||
|
@ -366,7 +377,7 @@ StackView {
|
|||
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled
|
||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
||||
numeric: parent.punctuationMode
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
|
|
|
@ -5,7 +5,9 @@ import TabletScriptingInterface 1.0
|
|||
Item {
|
||||
id: tabletButton
|
||||
|
||||
property string captionColorOverride: ""
|
||||
property color defaultCaptionColor: "#ffffff"
|
||||
property color captionColor: defaultCaptionColor
|
||||
|
||||
property var uuid;
|
||||
property string icon: "icons/tablet-icons/edit-i.svg"
|
||||
property string hoverIcon: tabletButton.icon
|
||||
|
@ -105,7 +107,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: text
|
||||
color: captionColorOverride !== "" ? captionColorOverride: "#ffffff"
|
||||
color: captionColor
|
||||
text: tabletButton.text
|
||||
font.bold: true
|
||||
font.pixelSize: 18
|
||||
|
@ -171,7 +173,7 @@ Item {
|
|||
|
||||
PropertyChanges {
|
||||
target: text
|
||||
color: captionColorOverride !== "" ? captionColorOverride: "#ffffff"
|
||||
color: captionColor
|
||||
text: tabletButton.hoverText
|
||||
}
|
||||
|
||||
|
@ -197,7 +199,7 @@ Item {
|
|||
|
||||
PropertyChanges {
|
||||
target: text
|
||||
color: captionColorOverride !== "" ? captionColorOverride: "#333333"
|
||||
color: captionColor !== defaultCaptionColor ? captionColor : "#333333"
|
||||
text: tabletButton.activeText
|
||||
}
|
||||
|
||||
|
@ -228,7 +230,7 @@ Item {
|
|||
|
||||
PropertyChanges {
|
||||
target: text
|
||||
color: captionColorOverride !== "" ? captionColorOverride: "#333333"
|
||||
color: captionColor !== defaultCaptionColor ? captionColor : "#333333"
|
||||
text: tabletButton.activeHoverText
|
||||
}
|
||||
|
||||
|
|
|
@ -40,37 +40,29 @@ Item {
|
|||
|
||||
CheckBox {
|
||||
id: checkbox
|
||||
// FIXME: Should use radio buttons if source.exclusiveGroup.
|
||||
|
||||
width: 20
|
||||
visible: source !== null ?
|
||||
source.visible && source.type === 1 && source.checkable && !source.exclusiveGroup :
|
||||
false
|
||||
checked: setChecked()
|
||||
function setChecked() {
|
||||
if (!source || source.type !== 1 || !source.checkable) {
|
||||
return false;
|
||||
}
|
||||
// FIXME this works for native QML menus but I don't think it will
|
||||
// for proxied QML menus
|
||||
return source.checked;
|
||||
|
||||
Binding on checked {
|
||||
value: source.checked;
|
||||
when: source && source.type === 1 && source.checkable && !source.exclusiveGroup;
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
id: radiobutton
|
||||
// FIXME: Should use radio buttons if source.exclusiveGroup.
|
||||
|
||||
width: 20
|
||||
visible: source !== null ?
|
||||
source.visible && source.type === 1 && source.checkable && source.exclusiveGroup :
|
||||
false
|
||||
checked: setChecked()
|
||||
function setChecked() {
|
||||
if (!source || source.type !== 1 || !source.checkable) {
|
||||
return false;
|
||||
}
|
||||
// FIXME this works for native QML menus but I don't think it will
|
||||
// for proxied QML menus
|
||||
return source.checked;
|
||||
|
||||
Binding on checked {
|
||||
value: source.checked;
|
||||
when: source && source.type === 1 && source.checkable && source.exclusiveGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,6 @@ Item {
|
|||
|
||||
function toModel(items, newMenu) {
|
||||
var result = modelMaker.createObject(tabletMenu);
|
||||
var exclusionGroups = {};
|
||||
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
var item = items[i];
|
||||
|
@ -78,28 +77,6 @@ Item {
|
|||
if (item.text !== "Users Online") {
|
||||
result.append({"name": item.text, "item": item})
|
||||
}
|
||||
|
||||
for(var j = 0; j < tabletMenu.rootMenu.exclusionGroupsByMenuItem.count; ++j)
|
||||
{
|
||||
var entry = tabletMenu.rootMenu.exclusionGroupsByMenuItem.get(j);
|
||||
if(entry.menuItem == item.toString())
|
||||
{
|
||||
var exclusionGroupId = entry.exclusionGroup;
|
||||
console.debug('item exclusionGroupId: ', exclusionGroupId)
|
||||
|
||||
if(!exclusionGroups[exclusionGroupId])
|
||||
{
|
||||
exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(newMenu);
|
||||
console.debug('new exclusion group created: ', exclusionGroups[exclusionGroupId])
|
||||
}
|
||||
|
||||
var exclusionGroup = exclusionGroups[exclusionGroupId];
|
||||
|
||||
item.exclusiveGroup = exclusionGroup
|
||||
console.debug('item.exclusiveGroup: ', item.exclusiveGroup)
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case MenuItemType.Separator:
|
||||
result.append({"name": "", "item": item})
|
||||
|
|
|
@ -4,7 +4,9 @@ import QtQuick.Controls 1.4
|
|||
StateImage {
|
||||
id: button
|
||||
|
||||
property string captionColorOverride: ""
|
||||
property color defaultCaptionColor: "#ffffff"
|
||||
property color captionColor: defaultCaptionColor
|
||||
|
||||
property bool buttonEnabled: true
|
||||
property bool isActive: false
|
||||
property bool isEntered: false
|
||||
|
@ -98,7 +100,7 @@ StateImage {
|
|||
|
||||
Text {
|
||||
id: caption
|
||||
color: captionColorOverride !== "" ? captionColorOverride: (button.isActive ? "#000000" : "#ffffff")
|
||||
color: button.isActive ? "#000000" : captionColor
|
||||
text: button.isActive ? (button.isEntered ? button.activeHoverText : button.activeText) : (button.isEntered ? button.hoverText : button.text)
|
||||
font.bold: false
|
||||
font.pixelSize: 9
|
||||
|
|
|
@ -203,6 +203,8 @@
|
|||
#include "commerce/Wallet.h"
|
||||
#include "commerce/QmlCommerce.h"
|
||||
|
||||
#include "webbrowser/WebBrowserSuggestionsEngine.h"
|
||||
|
||||
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
|
||||
// FIXME seems to be broken.
|
||||
#if defined(Q_OS_WIN)
|
||||
|
@ -1392,7 +1394,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
// Make sure we don't time out during slow operations at startup
|
||||
updateHeartbeat();
|
||||
|
||||
|
||||
QTimer* settingsTimer = new QTimer();
|
||||
moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{
|
||||
connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{
|
||||
|
@ -1700,8 +1701,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
lastLeftHandPose = leftHandPose;
|
||||
lastRightHandPose = rightHandPose;
|
||||
|
||||
properties["local_socket_changes"] = DependencyManager::get<StatTracker>()->getStat(LOCAL_SOCKET_CHANGE_STAT).toInt();
|
||||
|
||||
UserActivityLogger::getInstance().logAction("stats", properties);
|
||||
});
|
||||
sendStatsTimer->start();
|
||||
|
@ -1825,6 +1824,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
// Preload Tablet sounds
|
||||
DependencyManager::get<TabletScriptingInterface>()->preloadSounds();
|
||||
|
||||
_pendingIdleEvent = false;
|
||||
_pendingRenderEvent = false;
|
||||
|
||||
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
|
||||
}
|
||||
|
||||
|
@ -2223,6 +2225,7 @@ void Application::initializeUi() {
|
|||
QmlCommerce::registerType();
|
||||
qmlRegisterType<ResourceImageItem>("Hifi", 1, 0, "ResourceImageItem");
|
||||
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
|
||||
qmlRegisterType<WebBrowserSuggestionsEngine>("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine");
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->create();
|
||||
|
@ -2693,7 +2696,7 @@ bool Application::importFromZIP(const QString& filePath) {
|
|||
qDebug() << "A zip file has been dropped in: " << filePath;
|
||||
QUrl empty;
|
||||
// handle Blocks download from Marketplace
|
||||
if (filePath.contains("vr.google.com/downloads")) {
|
||||
if (filePath.contains("poly.google.com/downloads")) {
|
||||
addAssetToWorldFromURL(filePath);
|
||||
} else {
|
||||
qApp->getFileDownloadInterface()->runUnzip(filePath, empty, true, true, false);
|
||||
|
@ -4441,7 +4444,7 @@ void Application::cameraModeChanged() {
|
|||
void Application::cameraMenuChanged() {
|
||||
auto menu = Menu::getInstance();
|
||||
if (menu->isOptionChecked(MenuOption::FullscreenMirror)) {
|
||||
if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
|
||||
if (!isHMDMode() && _myCamera.getMode() != CAMERA_MODE_MIRROR) {
|
||||
_myCamera.setMode(CAMERA_MODE_MIRROR);
|
||||
getMyAvatar()->reset(false, false, false); // to reset any active MyAvatar::FollowHelpers
|
||||
}
|
||||
|
@ -4481,8 +4484,11 @@ void Application::resetPhysicsReadyInformation() {
|
|||
|
||||
void Application::reloadResourceCaches() {
|
||||
resetPhysicsReadyInformation();
|
||||
|
||||
// Query the octree to refresh everything in view
|
||||
_lastQueriedTime = 0;
|
||||
_octreeQuery.incrementConnectionID();
|
||||
|
||||
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions);
|
||||
|
||||
DependencyManager::get<AssetClient>()->clearCache();
|
||||
|
@ -5543,6 +5549,7 @@ void Application::nodeActivated(SharedNodePointer node) {
|
|||
// so we will do a proper query during update
|
||||
if (node->getType() == NodeType::EntityServer) {
|
||||
_lastQueriedTime = 0;
|
||||
_octreeQuery.incrementConnectionID();
|
||||
}
|
||||
|
||||
if (node->getType() == NodeType::AudioMixer) {
|
||||
|
@ -6235,7 +6242,7 @@ void Application::addAssetToWorldFromURL(QString url) {
|
|||
if (url.contains("filename")) {
|
||||
filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL.
|
||||
}
|
||||
if (url.contains("vr.google.com/downloads")) {
|
||||
if (url.contains("poly.google.com/downloads")) {
|
||||
filename = url.section('/', -1);
|
||||
if (url.contains("noDownload")) {
|
||||
filename.remove(".zip?noDownload=false");
|
||||
|
@ -6270,7 +6277,7 @@ void Application::addAssetToWorldFromURLRequestFinished() {
|
|||
if (url.contains("filename")) {
|
||||
filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL.
|
||||
}
|
||||
if (url.contains("vr.google.com/downloads")) {
|
||||
if (url.contains("poly.google.com/downloads")) {
|
||||
filename = url.section('/', -1);
|
||||
if (url.contains("noDownload")) {
|
||||
filename.remove(".zip?noDownload=false");
|
||||
|
@ -7082,6 +7089,7 @@ DisplayPluginPointer Application::getActiveDisplayPlugin() const {
|
|||
return _displayPlugin;
|
||||
}
|
||||
|
||||
static const char* EXCLUSION_GROUP_KEY = "exclusionGroup";
|
||||
|
||||
static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) {
|
||||
auto menu = Menu::getInstance();
|
||||
|
@ -7117,6 +7125,8 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
|
|||
action->setCheckable(true);
|
||||
action->setChecked(active);
|
||||
displayPluginGroup->addAction(action);
|
||||
|
||||
action->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(displayPluginGroup));
|
||||
Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name));
|
||||
}
|
||||
|
||||
|
@ -7271,6 +7281,10 @@ void Application::updateDisplayMode() {
|
|||
menu->setIsOptionChecked(MenuOption::FirstPerson, true);
|
||||
cameraMenuChanged();
|
||||
}
|
||||
|
||||
// Remove the mirror camera option from menu if in HMD mode
|
||||
auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror);
|
||||
mirrorAction->setVisible(!isHmd);
|
||||
|
||||
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
|
||||
}
|
||||
|
|
|
@ -543,7 +543,7 @@ private:
|
|||
ViewFrustum _displayViewFrustum;
|
||||
quint64 _lastQueriedTime;
|
||||
|
||||
OctreeQuery _octreeQuery; // NodeData derived class for querying octee cells from octree servers
|
||||
OctreeQuery _octreeQuery { true }; // NodeData derived class for querying octee cells from octree servers
|
||||
|
||||
std::shared_ptr<controller::StateController> _applicationStateDevice; // Default ApplicationDevice reflecting the state of different properties of the session
|
||||
std::shared_ptr<KeyboardMouseDevice> _keyboardMouseDevice; // Default input device, the good old keyboard mouse and maybe touchpad
|
||||
|
@ -708,7 +708,7 @@ private:
|
|||
|
||||
friend class RenderEventHandler;
|
||||
|
||||
std::atomic<bool> _pendingIdleEvent { false };
|
||||
std::atomic<bool> _pendingRenderEvent { false };
|
||||
std::atomic<bool> _pendingIdleEvent { true };
|
||||
std::atomic<bool> _pendingRenderEvent { true };
|
||||
};
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -54,7 +54,9 @@ void LODManager::autoAdjustLOD(float batchTime, float engineRunTime, float delta
|
|||
float renderTime = batchTime + OVERLAY_AND_SWAP_TIME_BUDGET;
|
||||
float maxTime = glm::max(renderTime, engineRunTime);
|
||||
const float BLEND_TIMESCALE = 0.3f; // sec
|
||||
float blend = BLEND_TIMESCALE / deltaTimeSec;
|
||||
const float MIN_DELTA_TIME = 0.001f;
|
||||
const float safeDeltaTime = glm::max(deltaTimeSec, MIN_DELTA_TIME);
|
||||
float blend = BLEND_TIMESCALE / safeDeltaTime;
|
||||
if (blend > 1.0f) {
|
||||
blend = 1.0f;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ Menu* Menu::getInstance() {
|
|||
return dynamic_cast<Menu*>(qApp->getWindow()->menuBar());
|
||||
}
|
||||
|
||||
const char* exclusionGroupKey = "exclusionGroup";
|
||||
const char* EXCLUSION_GROUP_KEY = "exclusionGroup";
|
||||
|
||||
Menu::Menu() {
|
||||
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
||||
|
@ -228,21 +228,21 @@ Menu::Menu() {
|
|||
viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F,
|
||||
true, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
firstPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup));
|
||||
firstPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Third Person
|
||||
auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
thirdPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup));
|
||||
thirdPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Mirror
|
||||
auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
viewMirrorAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup));
|
||||
viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Independent [advanced]
|
||||
auto viewIndependentAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
|
||||
|
@ -250,7 +250,7 @@ Menu::Menu() {
|
|||
false, qApp, SLOT(cameraMenuChanged()),
|
||||
UNSPECIFIED_POSITION, "Advanced"));
|
||||
|
||||
viewIndependentAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup));
|
||||
viewIndependentAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Entity Camera [advanced]
|
||||
auto viewEntityCameraAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
|
||||
|
@ -258,7 +258,7 @@ Menu::Menu() {
|
|||
false, qApp, SLOT(cameraMenuChanged()),
|
||||
UNSPECIFIED_POSITION, "Advanced"));
|
||||
|
||||
viewEntityCameraAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup));
|
||||
viewEntityCameraAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
viewMenu->addSeparator();
|
||||
|
||||
|
|
|
@ -179,6 +179,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
const AvatarPriority& sortData = sortedAvatars.top();
|
||||
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
|
||||
|
||||
bool ignoring = DependencyManager::get<NodeList>()->isPersonalMutingNode(avatar->getID());
|
||||
if (ignoring) {
|
||||
sortedAvatars.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// for ALL avatars...
|
||||
if (_shouldRender) {
|
||||
avatar->ensureInScene(avatar, qApp->getMain3DScene());
|
||||
|
|
|
@ -135,7 +135,7 @@ MyAvatar::MyAvatar(QThread* thread) :
|
|||
connect(&domainHandler, &DomainHandler::settingsReceived, this, &MyAvatar::restrictScaleFromDomainSettings);
|
||||
|
||||
// when we leave a domain we lift whatever restrictions that domain may have placed on our scale
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::clearScaleRestriction);
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::leaveDomain);
|
||||
|
||||
_bodySensorMatrix = deriveBodyFromHMDSensor();
|
||||
|
||||
|
@ -2189,6 +2189,7 @@ void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) {
|
|||
|
||||
setTargetScale(clampedTargetScale);
|
||||
qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale);
|
||||
emit(scaleChanged());
|
||||
}
|
||||
|
||||
float MyAvatar::getDomainMinScale() {
|
||||
|
@ -2278,6 +2279,18 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
|
|||
settings.endGroup();
|
||||
}
|
||||
|
||||
void MyAvatar::leaveDomain() {
|
||||
clearScaleRestriction();
|
||||
saveAvatarScale();
|
||||
}
|
||||
|
||||
void MyAvatar::saveAvatarScale() {
|
||||
Settings settings;
|
||||
settings.beginGroup("Avatar");
|
||||
settings.setValue("scale", _targetScale);
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void MyAvatar::clearScaleRestriction() {
|
||||
_domainMinimumScale = MIN_AVATAR_SCALE;
|
||||
_domainMaximumScale = MAX_AVATAR_SCALE;
|
||||
|
|
|
@ -620,6 +620,10 @@ signals:
|
|||
void dominantHandChanged(const QString& hand);
|
||||
void sensorToWorldScaleChanged(float sensorToWorldScale);
|
||||
void attachmentsChanged();
|
||||
void scaleChanged();
|
||||
|
||||
private slots:
|
||||
void leaveDomain();
|
||||
|
||||
private:
|
||||
|
||||
|
@ -637,6 +641,8 @@ private:
|
|||
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
|
||||
virtual glm::vec3 getSkeletonPosition() const override;
|
||||
|
||||
void saveAvatarScale();
|
||||
|
||||
glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; }
|
||||
float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; }
|
||||
QString getScriptedMotorFrame() const;
|
||||
|
|
|
@ -35,6 +35,14 @@ public:
|
|||
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
|
||||
void certificateInfo(const QString& certificateId);
|
||||
|
||||
enum CertificateStatus {
|
||||
CERTIFICATE_STATUS_UNKNOWN = 0,
|
||||
CERTIFICATE_STATUS_VERIFICATION_SUCCESS,
|
||||
CERTIFICATE_STATUS_VERIFICATION_TIMEOUT,
|
||||
CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED,
|
||||
CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED,
|
||||
};
|
||||
|
||||
signals:
|
||||
void buyResult(QJsonObject result);
|
||||
void receiveAtResult(QJsonObject result);
|
||||
|
@ -45,6 +53,8 @@ signals:
|
|||
void locationUpdateResult(QJsonObject result);
|
||||
void certificateInfoResult(QJsonObject result);
|
||||
|
||||
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||
|
||||
public slots:
|
||||
void buySuccess(QNetworkReply& reply);
|
||||
void buyFailure(QNetworkReply& reply);
|
||||
|
|
|
@ -30,6 +30,12 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
|
|||
connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult);
|
||||
connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult);
|
||||
connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
|
||||
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
|
||||
setPassphrase("");
|
||||
});
|
||||
}
|
||||
|
||||
void QmlCommerce::getWalletStatus() {
|
||||
|
|
|
@ -45,6 +45,8 @@ signals:
|
|||
void accountResult(QJsonObject result);
|
||||
void certificateInfoResult(QJsonObject result);
|
||||
|
||||
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||
|
||||
protected:
|
||||
Q_INVOKABLE void getWalletStatus();
|
||||
|
||||
|
|
|
@ -319,6 +319,7 @@ Wallet::Wallet() {
|
|||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket");
|
||||
|
||||
connect(ledger.data(), &Ledger::accountResult, this, [&]() {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
|
@ -717,50 +718,86 @@ bool Wallet::changePassphrase(const QString& newPassphrase) {
|
|||
}
|
||||
|
||||
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
|
||||
unsigned char decryptedText[64];
|
||||
int certIDByteArraySize;
|
||||
int encryptedTextByteArraySize;
|
||||
int challengingNodeUUIDByteArraySize;
|
||||
|
||||
packet->readPrimitive(&certIDByteArraySize);
|
||||
packet->readPrimitive(&encryptedTextByteArraySize);
|
||||
if (challengeOriginatedFromClient) {
|
||||
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
|
||||
}
|
||||
|
||||
QByteArray certID = packet->read(certIDByteArraySize);
|
||||
QByteArray encryptedText = packet->read(encryptedTextByteArraySize);
|
||||
QByteArray challengingNodeUUID;
|
||||
if (challengeOriginatedFromClient) {
|
||||
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
|
||||
}
|
||||
|
||||
RSA* rsa = readKeys(keyFilePath().toStdString().c_str());
|
||||
int decryptionStatus = -1;
|
||||
|
||||
if (rsa) {
|
||||
const int decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize,
|
||||
ERR_clear_error();
|
||||
decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize,
|
||||
reinterpret_cast<const unsigned char*>(encryptedText.constData()),
|
||||
decryptedText,
|
||||
rsa,
|
||||
RSA_PKCS1_OAEP_PADDING);
|
||||
|
||||
RSA_free(rsa);
|
||||
|
||||
if (decryptionStatus != -1) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
QByteArray decryptedTextByteArray = QByteArray(reinterpret_cast<const char*>(decryptedText), decryptionStatus);
|
||||
int decryptedTextByteArraySize = decryptedTextByteArray.size();
|
||||
int certIDSize = certID.size();
|
||||
// setup the packet
|
||||
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true);
|
||||
|
||||
decryptedTextPacket->writePrimitive(certIDSize);
|
||||
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
|
||||
decryptedTextPacket->write(certID);
|
||||
decryptedTextPacket->write(decryptedTextByteArray);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
|
||||
|
||||
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
|
||||
} else {
|
||||
qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed.";
|
||||
}
|
||||
} else {
|
||||
qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed.";
|
||||
}
|
||||
|
||||
QByteArray decryptedTextByteArray;
|
||||
if (decryptionStatus > -1) {
|
||||
decryptedTextByteArray = QByteArray(reinterpret_cast<const char*>(decryptedText), decryptionStatus);
|
||||
}
|
||||
int decryptedTextByteArraySize = decryptedTextByteArray.size();
|
||||
int certIDSize = certID.size();
|
||||
// setup the packet
|
||||
if (challengeOriginatedFromClient) {
|
||||
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
|
||||
certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
|
||||
true);
|
||||
|
||||
decryptedTextPacket->writePrimitive(certIDSize);
|
||||
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
|
||||
decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize);
|
||||
decryptedTextPacket->write(certID);
|
||||
decryptedTextPacket->write(decryptedTextByteArray);
|
||||
decryptedTextPacket->write(challengingNodeUUID);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
|
||||
|
||||
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
|
||||
} else {
|
||||
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true);
|
||||
|
||||
decryptedTextPacket->writePrimitive(certIDSize);
|
||||
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
|
||||
decryptedTextPacket->write(certID);
|
||||
decryptedTextPacket->write(decryptedTextByteArray);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
|
||||
|
||||
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
|
||||
}
|
||||
|
||||
if (decryptionStatus == -1) {
|
||||
qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed.";
|
||||
long error = ERR_get_error();
|
||||
if (error != 0) {
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
qCWarning(entities) << "RSA error:" << error_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Wallet::account() {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include "RayPickScriptingInterface.h"
|
||||
|
||||
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
|
||||
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled) :
|
||||
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool scaleWithAvatar, const bool enabled) :
|
||||
_renderingEnabled(enabled),
|
||||
_renderStates(renderStates),
|
||||
_defaultRenderStates(defaultRenderStates),
|
||||
|
@ -23,9 +23,9 @@ LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& rende
|
|||
_centerEndY(centerEndY),
|
||||
_lockEnd(lockEnd),
|
||||
_distanceScaleEnd(distanceScaleEnd),
|
||||
_scaleWithAvatar(scaleWithAvatar),
|
||||
_rayPickUID(DependencyManager::get<RayPickScriptingInterface>()->createRayPick(rayProps))
|
||||
{
|
||||
|
||||
|
||||
for (auto& state : _renderStates) {
|
||||
if (!enabled || state.first != _currentRenderState) {
|
||||
|
@ -95,6 +95,10 @@ void LaserPointer::editRenderState(const std::string& state, const QVariant& sta
|
|||
if (endDim.isValid()) {
|
||||
_renderStates[state].setEndDim(vec3FromVariant(endDim));
|
||||
}
|
||||
QVariant lineWidth = pathProps.toMap()["lineWidth"];
|
||||
if (lineWidth.isValid()) {
|
||||
_renderStates[state].setLineWidth(lineWidth.toFloat());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -119,23 +123,25 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
|
|||
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
|
||||
}
|
||||
glm::vec3 endVec;
|
||||
if (((defaultState || !_lockEnd) && _objectLockEnd.first.isNull()) || type == IntersectionType::HUD) {
|
||||
if (((defaultState || !_lockEnd) && _lockEndObject.id.isNull()) || type == IntersectionType::HUD) {
|
||||
endVec = pickRay.origin + pickRay.direction * distance;
|
||||
} else {
|
||||
if (!_objectLockEnd.first.isNull()) {
|
||||
if (!_lockEndObject.id.isNull()) {
|
||||
glm::vec3 pos;
|
||||
glm::quat rot;
|
||||
glm::vec3 dim;
|
||||
glm::vec3 registrationPoint;
|
||||
if (_objectLockEnd.second) {
|
||||
pos = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "position").value);
|
||||
rot = quatFromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "rotation").value);
|
||||
dim = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "dimensions").value);
|
||||
if (_lockEndObject.isOverlay) {
|
||||
pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value);
|
||||
rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value);
|
||||
dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value);
|
||||
registrationPoint = glm::vec3(0.5f);
|
||||
} else {
|
||||
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_objectLockEnd.first);
|
||||
pos = props.getPosition();
|
||||
rot = props.getRotation();
|
||||
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_lockEndObject.id);
|
||||
glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
|
||||
glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
|
||||
pos = extractTranslation(finalPosAndRotMat);
|
||||
rot = glmExtractRotation(finalPosAndRotMat);
|
||||
dim = props.getDimensions();
|
||||
registrationPoint = props.getRegistrationPoint();
|
||||
}
|
||||
|
@ -151,6 +157,7 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant end = vec3toVariant(endVec);
|
||||
if (!renderState.getPathID().isNull()) {
|
||||
QVariantMap pathProps;
|
||||
|
@ -158,6 +165,9 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
|
|||
pathProps.insert("end", end);
|
||||
pathProps.insert("visible", true);
|
||||
pathProps.insert("ignoreRayIntersection", renderState.doesPathIgnoreRays());
|
||||
if (_scaleWithAvatar) {
|
||||
pathProps.insert("lineWidth", renderState.getLineWidth() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale());
|
||||
}
|
||||
qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps);
|
||||
}
|
||||
if (!renderState.getEndID().isNull()) {
|
||||
|
@ -165,7 +175,7 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
|
|||
glm::quat faceAvatarRotation = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f)));
|
||||
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value);
|
||||
if (_distanceScaleEnd) {
|
||||
dim = renderState.getEndDim() * glm::distance(pickRay.origin, endVec) * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
|
||||
dim = renderState.getEndDim() * glm::distance(pickRay.origin, endVec);
|
||||
endProps.insert("dimensions", vec3toVariant(dim));
|
||||
}
|
||||
if (_centerEndY) {
|
||||
|
@ -209,7 +219,7 @@ void LaserPointer::update() {
|
|||
withReadLock([&] {
|
||||
RayPickResult prevRayPickResult = qApp->getRayPickManager().getPrevRayPickResult(_rayPickUID);
|
||||
if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
|
||||
(prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) {
|
||||
(prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) {
|
||||
float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult.distance;
|
||||
updateRenderState(_renderStates[_currentRenderState], prevRayPickResult.type, distance, prevRayPickResult.objectID, prevRayPickResult.searchRay, false);
|
||||
disableRenderState(_defaultRenderStates[_currentRenderState].second);
|
||||
|
@ -233,9 +243,11 @@ void LaserPointer::setLaserLength(const float laserLength) {
|
|||
});
|
||||
}
|
||||
|
||||
void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay) {
|
||||
void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay, const glm::mat4& offsetMat) {
|
||||
withWriteLock([&] {
|
||||
_objectLockEnd = std::pair<QUuid, bool>(objectID, isOverlay);
|
||||
_lockEndObject.id = objectID;
|
||||
_lockEndObject.isOverlay = isOverlay;
|
||||
_lockEndObject.offsetMat = offsetMat;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -255,6 +267,7 @@ RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, cons
|
|||
}
|
||||
if (!_pathID.isNull()) {
|
||||
_pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool();
|
||||
_lineWidth = qApp->getOverlays().getProperty(_pathID, "lineWidth").value.toFloat();
|
||||
}
|
||||
if (!_endID.isNull()) {
|
||||
_endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value);
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
|
||||
class RayPickResult;
|
||||
|
||||
struct LockEndObject {
|
||||
QUuid id { QUuid() };
|
||||
bool isOverlay { false };
|
||||
glm::mat4 offsetMat { glm::mat4() };
|
||||
};
|
||||
|
||||
class RenderState {
|
||||
|
||||
public:
|
||||
|
@ -37,6 +43,9 @@ public:
|
|||
void setEndDim(const glm::vec3& endDim) { _endDim = endDim; }
|
||||
const glm::vec3& getEndDim() const { return _endDim; }
|
||||
|
||||
void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; }
|
||||
const float& getLineWidth() const { return _lineWidth; }
|
||||
|
||||
void deleteOverlays();
|
||||
|
||||
private:
|
||||
|
@ -48,6 +57,7 @@ private:
|
|||
bool _endIgnoreRays;
|
||||
|
||||
glm::vec3 _endDim;
|
||||
float _lineWidth;
|
||||
};
|
||||
|
||||
|
||||
|
@ -60,7 +70,7 @@ public:
|
|||
typedef std::unordered_map<std::string, std::pair<float, RenderState>> DefaultRenderStateMap;
|
||||
|
||||
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
|
||||
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled);
|
||||
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool scaleWithAvatar, const bool enabled);
|
||||
~LaserPointer();
|
||||
|
||||
QUuid getRayUID() { return _rayPickUID; }
|
||||
|
@ -74,7 +84,7 @@ public:
|
|||
|
||||
void setPrecisionPicking(const bool precisionPicking);
|
||||
void setLaserLength(const float laserLength);
|
||||
void setLockEndUUID(QUuid objectID, const bool isOverlay);
|
||||
void setLockEndUUID(QUuid objectID, const bool isOverlay, const glm::mat4& offsetMat = glm::mat4());
|
||||
|
||||
void setIgnoreItems(const QVector<QUuid>& ignoreItems) const;
|
||||
void setIncludeItems(const QVector<QUuid>& includeItems) const;
|
||||
|
@ -91,7 +101,8 @@ private:
|
|||
bool _centerEndY;
|
||||
bool _lockEnd;
|
||||
bool _distanceScaleEnd;
|
||||
std::pair<QUuid, bool> _objectLockEnd { std::pair<QUuid, bool>(QUuid(), false)};
|
||||
bool _scaleWithAvatar;
|
||||
LockEndObject _lockEndObject;
|
||||
|
||||
const QUuid _rayPickUID;
|
||||
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
#include "LaserPointerManager.h"
|
||||
|
||||
QUuid LaserPointerManager::createLaserPointer(const QVariant& rayProps, const LaserPointer::RenderStateMap& renderStates, const LaserPointer::DefaultRenderStateMap& defaultRenderStates,
|
||||
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled) {
|
||||
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool scaleWithAvatar, const bool enabled) {
|
||||
QUuid result;
|
||||
std::shared_ptr<LaserPointer> laserPointer = std::make_shared<LaserPointer>(rayProps, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, distanceScaleEnd, enabled);
|
||||
std::shared_ptr<LaserPointer> laserPointer = std::make_shared<LaserPointer>(rayProps, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled);
|
||||
if (!laserPointer->getRayUID().isNull()) {
|
||||
result = QUuid::createUuid();
|
||||
withWriteLock([&] { _laserPointers[result] = laserPointer; });
|
||||
|
@ -113,9 +113,9 @@ void LaserPointerManager::setIncludeItems(const QUuid& uid, const QVector<QUuid>
|
|||
}
|
||||
}
|
||||
|
||||
void LaserPointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay) const {
|
||||
void LaserPointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) const {
|
||||
auto laserPointer = find(uid);
|
||||
if (laserPointer) {
|
||||
laserPointer->setLockEndUUID(objectID, isOverlay);
|
||||
laserPointer->setLockEndUUID(objectID, isOverlay, offsetMat);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class LaserPointerManager : protected ReadWriteLockable {
|
|||
|
||||
public:
|
||||
QUuid createLaserPointer(const QVariant& rayProps, const LaserPointer::RenderStateMap& renderStates, const LaserPointer::DefaultRenderStateMap& defaultRenderStates,
|
||||
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled);
|
||||
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool scaleWithAvatar, const bool enabled);
|
||||
|
||||
void removeLaserPointer(const QUuid& uid);
|
||||
void enableLaserPointer(const QUuid& uid) const;
|
||||
|
@ -39,7 +39,7 @@ public:
|
|||
void setIgnoreItems(const QUuid& uid, const QVector<QUuid>& ignoreEntities) const;
|
||||
void setIncludeItems(const QUuid& uid, const QVector<QUuid>& includeEntities) const;
|
||||
|
||||
void setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay) const;
|
||||
void setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const;
|
||||
|
||||
void update();
|
||||
|
||||
|
|
|
@ -51,6 +51,11 @@ QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& propert
|
|||
enabled = propertyMap["enabled"].toBool();
|
||||
}
|
||||
|
||||
bool scaleWithAvatar = false;
|
||||
if (propertyMap["scaleWithAvatar"].isValid()) {
|
||||
scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool();
|
||||
}
|
||||
|
||||
LaserPointer::RenderStateMap renderStates;
|
||||
if (propertyMap["renderStates"].isValid()) {
|
||||
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
|
||||
|
@ -80,7 +85,7 @@ QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& propert
|
|||
}
|
||||
}
|
||||
|
||||
return qApp->getLaserPointerManager().createLaserPointer(properties, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, distanceScaleEnd, enabled);
|
||||
return qApp->getLaserPointerManager().createLaserPointer(properties, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled);
|
||||
}
|
||||
|
||||
void LaserPointerScriptingInterface::editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const {
|
||||
|
|
|
@ -35,7 +35,7 @@ public slots:
|
|||
Q_INVOKABLE void setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreEntities) const;
|
||||
Q_INVOKABLE void setIncludeItems(const QUuid& uid, const QScriptValue& includeEntities) const;
|
||||
|
||||
Q_INVOKABLE void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay) const { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay); }
|
||||
Q_INVOKABLE void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay, offsetMat); }
|
||||
|
||||
private:
|
||||
static RenderState buildRenderState(const QVariantMap& propMap);
|
||||
|
|
|
@ -71,6 +71,12 @@ bool SelectionScriptingInterface::removeFromSelectedItemsList(const QString& lis
|
|||
return false;
|
||||
}
|
||||
|
||||
bool SelectionScriptingInterface::clearSelectedItemsList(const QString& listName) {
|
||||
_selectedItemsListMap.insert(listName, GameplayObjects());
|
||||
emit selectedItemsListChanged(listName);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T> bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) {
|
||||
GameplayObjects currentList = _selectedItemsListMap.value(listName);
|
||||
currentList.addToGameplayObjects(idToAdd);
|
||||
|
|
|
@ -56,11 +56,45 @@ public:
|
|||
|
||||
GameplayObjects getList(const QString& listName);
|
||||
|
||||
/**jsdoc
|
||||
* Prints out the list of avatars, entities and overlays stored in a particular selection.
|
||||
* @function Selection.printList
|
||||
* @param listName {string} name of the selection
|
||||
*/
|
||||
Q_INVOKABLE void printList(const QString& listName);
|
||||
/**jsdoc
|
||||
* Removes a named selection from the list of selections.
|
||||
* @function Selection.removeListFromMap
|
||||
* @param listName {string} name of the selection
|
||||
* @returns {bool} true if the selection existed and was successfully removed.
|
||||
*/
|
||||
Q_INVOKABLE bool removeListFromMap(const QString& listName);
|
||||
|
||||
/**jsdoc
|
||||
* Add an item in a selection.
|
||||
* @function Selection.addToSelectedItemsList
|
||||
* @param listName {string} name of the selection
|
||||
* @param itemType {string} the type of the item (one of "avatar", "entity" or "overlay")
|
||||
* @param id {EntityID} the Id of the item to add to the selection
|
||||
* @returns {bool} true if the item was successfully added.
|
||||
*/
|
||||
Q_INVOKABLE bool addToSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id);
|
||||
/**jsdoc
|
||||
* Remove an item from a selection.
|
||||
* @function Selection.removeFromSelectedItemsList
|
||||
* @param listName {string} name of the selection
|
||||
* @param itemType {string} the type of the item (one of "avatar", "entity" or "overlay")
|
||||
* @param id {EntityID} the Id of the item to remove
|
||||
* @returns {bool} true if the item was successfully removed.
|
||||
*/
|
||||
Q_INVOKABLE bool removeFromSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id);
|
||||
/**jsdoc
|
||||
* Remove all items from a selection.
|
||||
* @function Selection.clearSelectedItemsList
|
||||
* @param listName {string} name of the selection
|
||||
* @returns {bool} true if the item was successfully cleared.
|
||||
*/
|
||||
Q_INVOKABLE bool clearSelectedItemsList(const QString& listName);
|
||||
|
||||
signals:
|
||||
void selectedItemsListChanged(const QString& listName);
|
||||
|
|
|
@ -40,6 +40,10 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare
|
|||
_backEnabled = !(DependencyManager::get<AddressManager>()->getBackStack().isEmpty());
|
||||
_forwardEnabled = !(DependencyManager::get<AddressManager>()->getForwardStack().isEmpty());
|
||||
connect(addressManager.data(), &AddressManager::hostChanged, this, &AddressBarDialog::hostChanged);
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &AddressBarDialog::hostChanged);
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &AddressBarDialog::hostChanged);
|
||||
connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::setUseFeed, this, &AddressBarDialog::setUseFeed);
|
||||
connect(qApp, &Application::receivedHifiSchemeURL, this, &AddressBarDialog::receivedHifiSchemeURL);
|
||||
}
|
||||
|
|
|
@ -16,13 +16,11 @@
|
|||
#include "Application.h"
|
||||
|
||||
|
||||
const float DEFAULT_LINE_WIDTH = 1.0f;
|
||||
const bool DEFAULT_IS_SOLID = false;
|
||||
const bool DEFAULT_IS_DASHED_LINE = false;
|
||||
|
||||
Base3DOverlay::Base3DOverlay() :
|
||||
SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()),
|
||||
_lineWidth(DEFAULT_LINE_WIDTH),
|
||||
_isSolid(DEFAULT_IS_SOLID),
|
||||
_isDashedLine(DEFAULT_IS_DASHED_LINE),
|
||||
_ignoreRayIntersection(false),
|
||||
|
@ -34,7 +32,6 @@ Base3DOverlay::Base3DOverlay() :
|
|||
Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) :
|
||||
Overlay(base3DOverlay),
|
||||
SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()),
|
||||
_lineWidth(base3DOverlay->_lineWidth),
|
||||
_isSolid(base3DOverlay->_isSolid),
|
||||
_isDashedLine(base3DOverlay->_isDashedLine),
|
||||
_ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection),
|
||||
|
@ -153,12 +150,6 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
setLocalOrientation(quatFromVariant(properties["orientation"]));
|
||||
needRenderItemUpdate = true;
|
||||
}
|
||||
|
||||
if (properties["lineWidth"].isValid()) {
|
||||
setLineWidth(properties["lineWidth"].toFloat());
|
||||
needRenderItemUpdate = true;
|
||||
}
|
||||
|
||||
if (properties["isSolid"].isValid()) {
|
||||
setIsSolid(properties["isSolid"].toBool());
|
||||
}
|
||||
|
@ -225,10 +216,7 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
|
|||
if (property == "localRotation" || property == "localOrientation") {
|
||||
return quatToVariant(getLocalOrientation());
|
||||
}
|
||||
if (property == "lineWidth") {
|
||||
return _lineWidth;
|
||||
}
|
||||
if (property == "isSolid" || property == "isFilled" || property == "solid" || property == "filed") {
|
||||
if (property == "isSolid" || property == "isFilled" || property == "solid" || property == "filled" || property == "filed") {
|
||||
return _isSolid;
|
||||
}
|
||||
if (property == "isWire" || property == "wire") {
|
||||
|
@ -265,7 +253,7 @@ void Base3DOverlay::locationChanged(bool tellPhysics) {
|
|||
SpatiallyNestable::locationChanged(tellPhysics);
|
||||
|
||||
// Force the actual update of the render transform through the notify call
|
||||
notifyRenderTransformChange();
|
||||
notifyRenderVariableChange();
|
||||
}
|
||||
|
||||
void Base3DOverlay::parentDeleted() {
|
||||
|
@ -275,18 +263,21 @@ void Base3DOverlay::parentDeleted() {
|
|||
void Base3DOverlay::update(float duration) {
|
||||
// In Base3DOverlay, if its location or bound changed, the renderTrasnformDirty flag is true.
|
||||
// then the correct transform used for rendering is computed in the update transaction and assigned.
|
||||
if (_renderTransformDirty) {
|
||||
if (_renderVariableDirty) {
|
||||
auto itemID = getRenderItemID();
|
||||
if (render::Item::isValidID(itemID)) {
|
||||
// Capture the render transform value in game loop before
|
||||
auto latestTransform = evalRenderTransform();
|
||||
_renderTransformDirty = false;
|
||||
bool latestVisible = getVisible();
|
||||
_renderVariableDirty = false;
|
||||
render::ScenePointer scene = qApp->getMain3DScene();
|
||||
render::Transaction transaction;
|
||||
transaction.updateItem<Overlay>(itemID, [latestTransform](Overlay& data) {
|
||||
transaction.updateItem<Overlay>(itemID, [latestTransform, latestVisible](Overlay& data) {
|
||||
auto overlay3D = dynamic_cast<Base3DOverlay*>(&data);
|
||||
if (overlay3D) {
|
||||
// TODO: overlays need to communicate all relavent render properties through transactions
|
||||
overlay3D->setRenderTransform(latestTransform);
|
||||
overlay3D->setRenderVisible(latestVisible);
|
||||
}
|
||||
});
|
||||
scene->enqueueTransaction(transaction);
|
||||
|
@ -294,8 +285,8 @@ void Base3DOverlay::update(float duration) {
|
|||
}
|
||||
}
|
||||
|
||||
void Base3DOverlay::notifyRenderTransformChange() const {
|
||||
_renderTransformDirty = true;
|
||||
void Base3DOverlay::notifyRenderVariableChange() const {
|
||||
_renderVariableDirty = true;
|
||||
}
|
||||
|
||||
Transform Base3DOverlay::evalRenderTransform() {
|
||||
|
@ -306,8 +297,17 @@ void Base3DOverlay::setRenderTransform(const Transform& transform) {
|
|||
_renderTransform = transform;
|
||||
}
|
||||
|
||||
void Base3DOverlay::setRenderVisible(bool visible) {
|
||||
_renderVisible = visible;
|
||||
}
|
||||
|
||||
SpatialParentTree* Base3DOverlay::getParentTree() const {
|
||||
auto entityTreeRenderer = qApp->getEntities();
|
||||
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
|
||||
return entityTree.get();
|
||||
}
|
||||
|
||||
void Base3DOverlay::setVisible(bool visible) {
|
||||
Parent::setVisible(visible);
|
||||
notifyRenderVariableChange();
|
||||
}
|
|
@ -18,11 +18,14 @@
|
|||
|
||||
class Base3DOverlay : public Overlay, public SpatiallyNestable {
|
||||
Q_OBJECT
|
||||
using Parent = Overlay;
|
||||
|
||||
public:
|
||||
Base3DOverlay();
|
||||
Base3DOverlay(const Base3DOverlay* base3DOverlay);
|
||||
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); }
|
||||
void setOverlayID(OverlayID overlayID) override { setID(overlayID); }
|
||||
|
||||
|
@ -35,7 +38,6 @@ public:
|
|||
// TODO: consider implementing registration points in this class
|
||||
glm::vec3 getCenter() const { return getWorldPosition(); }
|
||||
|
||||
float getLineWidth() const { return _lineWidth; }
|
||||
bool getIsSolid() const { return _isSolid; }
|
||||
bool getIsDashedLine() const { return _isDashedLine; }
|
||||
bool getIsSolidLine() const { return !_isDashedLine; }
|
||||
|
@ -44,7 +46,6 @@ public:
|
|||
bool getDrawHUDLayer() const { return _drawHUDLayer; }
|
||||
bool getIsGrabbable() const { return _isGrabbable; }
|
||||
|
||||
void setLineWidth(float lineWidth) { _lineWidth = lineWidth; }
|
||||
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
|
||||
void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; }
|
||||
void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; }
|
||||
|
@ -56,7 +57,7 @@ public:
|
|||
|
||||
void update(float deltatime) override;
|
||||
|
||||
void notifyRenderTransformChange() const;
|
||||
void notifyRenderVariableChange() const;
|
||||
|
||||
void setProperties(const QVariantMap& properties) override;
|
||||
QVariant getProperty(const QString& property) override;
|
||||
|
@ -76,18 +77,19 @@ protected:
|
|||
virtual void parentDeleted() override;
|
||||
|
||||
mutable Transform _renderTransform;
|
||||
mutable bool _renderVisible;
|
||||
virtual Transform evalRenderTransform();
|
||||
virtual void setRenderTransform(const Transform& transform);
|
||||
void setRenderVisible(bool visible);
|
||||
const Transform& getRenderTransform() const { return _renderTransform; }
|
||||
|
||||
float _lineWidth;
|
||||
bool _isSolid;
|
||||
bool _isDashedLine;
|
||||
bool _ignoreRayIntersection;
|
||||
bool _drawInFront;
|
||||
bool _drawHUDLayer;
|
||||
bool _isGrabbable { false };
|
||||
mutable bool _renderTransformDirty{ true };
|
||||
mutable bool _renderVariableDirty { true };
|
||||
|
||||
QString _name;
|
||||
};
|
||||
|
|
|
@ -59,7 +59,7 @@ Circle3DOverlay::~Circle3DOverlay() {
|
|||
}
|
||||
}
|
||||
void Circle3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible) {
|
||||
if (!_renderVisible) {
|
||||
return; // do nothing if we're not visible
|
||||
}
|
||||
|
||||
|
@ -259,7 +259,7 @@ void Circle3DOverlay::render(RenderArgs* args) {
|
|||
}
|
||||
|
||||
const render::ShapeKey Circle3DOverlay::getShapeKey() {
|
||||
auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit();
|
||||
auto builder = render::ShapeKey::Builder().withoutCullFace();
|
||||
if (isTransparent()) {
|
||||
builder.withTranslucent();
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
|
||||
#include <EntityTreeRenderer.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <commerce/Ledger.h>
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
|
@ -34,14 +38,14 @@ ContextOverlayInterface::ContextOverlayInterface() {
|
|||
_tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
_selectionScriptingInterface = DependencyManager::get<SelectionScriptingInterface>();
|
||||
|
||||
_selectionToSceneHandler.initialize("contextOverlayHighlightList");
|
||||
|
||||
_entityPropertyFlags += PROP_POSITION;
|
||||
_entityPropertyFlags += PROP_ROTATION;
|
||||
_entityPropertyFlags += PROP_MARKETPLACE_ID;
|
||||
_entityPropertyFlags += PROP_DIMENSIONS;
|
||||
_entityPropertyFlags += PROP_REGISTRATION_POINT;
|
||||
_entityPropertyFlags += PROP_CERTIFICATE_ID;
|
||||
_entityPropertyFlags += PROP_CLIENT_ONLY;
|
||||
_entityPropertyFlags += PROP_OWNING_AVATAR_ID;
|
||||
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data();
|
||||
connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay);
|
||||
|
@ -53,19 +57,39 @@ ContextOverlayInterface::ContextOverlayInterface() {
|
|||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
glm::quat cameraOrientation = qApp->getCamera().getOrientation();
|
||||
QVariantMap props;
|
||||
props.insert("position", vec3toVariant(myAvatar->getEyePosition() + glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_OFFSET, 0.0f))) * (CONTEXT_OVERLAY_TABLET_DISTANCE * (cameraOrientation * Vectors::FRONT))));
|
||||
float sensorToWorldScale = myAvatar->getSensorToWorldScale();
|
||||
props.insert("position", vec3toVariant(myAvatar->getEyePosition() + glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_OFFSET, 0.0f))) * ((CONTEXT_OVERLAY_TABLET_DISTANCE * sensorToWorldScale) * (cameraOrientation * Vectors::FRONT))));
|
||||
props.insert("orientation", quatToVariant(cameraOrientation * glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_ORIENTATION, 0.0f)))));
|
||||
qApp->getOverlays().editOverlay(tabletFrameID, props);
|
||||
_contextOverlayJustClicked = false;
|
||||
}
|
||||
});
|
||||
connect(entityScriptingInterface, &EntityScriptingInterface::deletingEntity, this, &ContextOverlayInterface::deletingEntity);
|
||||
|
||||
connect(&qApp->getOverlays(), &Overlays::mousePressOnOverlay, this, &ContextOverlayInterface::contextOverlays_mousePressOnOverlay);
|
||||
connect(&qApp->getOverlays(), &Overlays::hoverEnterOverlay, this, &ContextOverlayInterface::contextOverlays_hoverEnterOverlay);
|
||||
connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay);
|
||||
|
||||
connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged);
|
||||
{
|
||||
render::Transaction transaction;
|
||||
initializeSelectionToSceneHandler(_selectionToSceneHandlers[0], "contextOverlayHighlightList", transaction);
|
||||
for (auto i = 1; i < MAX_SELECTION_COUNT; i++) {
|
||||
auto selectionName = QString("highlightList") + QString::number(i);
|
||||
initializeSelectionToSceneHandler(_selectionToSceneHandlers[i], selectionName, transaction);
|
||||
}
|
||||
const render::ScenePointer& scene = qApp->getMain3DScene();
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket");
|
||||
_challengeOwnershipTimeoutTimer.setSingleShot(true);
|
||||
}
|
||||
|
||||
void ContextOverlayInterface::initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction) {
|
||||
handler.initialize(selectionName);
|
||||
connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &handler, &SelectionToSceneHandler::selectedItemsListChanged);
|
||||
transaction.resetSelectionHighlight(selectionName.toStdString());
|
||||
}
|
||||
|
||||
static const uint32_t MOUSE_HW_ID = 0;
|
||||
|
@ -260,6 +284,89 @@ void ContextOverlayInterface::openInspectionCertificate() {
|
|||
auto tablet = dynamic_cast<TabletProxy*>(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH);
|
||||
_hmdScriptingInterface->openTablet();
|
||||
|
||||
setLastInspectedEntity(_currentEntityWithContextOverlay);
|
||||
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
if (entityProperties.getClientOnly()) {
|
||||
if (entityProperties.verifyStaticCertificateProperties()) {
|
||||
SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer);
|
||||
|
||||
if (entityServer) {
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
|
||||
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer");
|
||||
QJsonObject request;
|
||||
request["certificate_id"] = entityProperties.getCertificateID();
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, [=]() {
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
|
||||
jsonObject = jsonObject["data"].toObject();
|
||||
|
||||
if (networkReply->error() == QNetworkReply::NoError) {
|
||||
if (!jsonObject["invalid_reason"].toString().isEmpty()) {
|
||||
qCDebug(entities) << "invalid_reason not empty";
|
||||
} else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") {
|
||||
qCDebug(entities) << "'transfer_status' is 'failed'";
|
||||
} else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") {
|
||||
qCDebug(entities) << "'transfer_status' is 'pending'";
|
||||
} else {
|
||||
QString ownerKey = jsonObject["transfer_recipient_key"].toString();
|
||||
|
||||
QByteArray certID = entityProperties.getCertificateID().toUtf8();
|
||||
QByteArray encryptedText = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeEncryptedNonce(certID, ownerKey);
|
||||
QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122();
|
||||
|
||||
int certIDByteArraySize = certID.length();
|
||||
int encryptedTextByteArraySize = encryptedText.length();
|
||||
int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length();
|
||||
|
||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
|
||||
certIDByteArraySize + encryptedTextByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
|
||||
true);
|
||||
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize);
|
||||
challengeOwnershipPacket->write(certID);
|
||||
challengeOwnershipPacket->write(encryptedText);
|
||||
challengeOwnershipPacket->write(nodeToChallengeByteArray);
|
||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer);
|
||||
|
||||
// Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer");
|
||||
return;
|
||||
} else {
|
||||
startChallengeOwnershipTimer();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() <<
|
||||
"More info:" << networkReply->readAll();
|
||||
}
|
||||
|
||||
networkReply->deleteLater();
|
||||
});
|
||||
} else {
|
||||
qCWarning(context_overlay) << "Couldn't get Entity Server!";
|
||||
}
|
||||
} else {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
_challengeOwnershipTimeoutTimer.stop();
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
|
||||
qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,3 +400,39 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) {
|
|||
destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent());
|
||||
}
|
||||
}
|
||||
|
||||
void ContextOverlayInterface::startChallengeOwnershipTimer() {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
|
||||
|
||||
connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
|
||||
qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity;
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
|
||||
});
|
||||
|
||||
_challengeOwnershipTimeoutTimer.start(5000);
|
||||
}
|
||||
|
||||
void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
|
||||
_challengeOwnershipTimeoutTimer.stop();
|
||||
|
||||
int certIDByteArraySize;
|
||||
int decryptedTextByteArraySize;
|
||||
|
||||
packet->readPrimitive(&certIDByteArraySize);
|
||||
packet->readPrimitive(&decryptedTextByteArraySize);
|
||||
|
||||
QString certID(packet->read(certIDByteArraySize));
|
||||
QString decryptedText(packet->read(decryptedTextByteArraySize));
|
||||
|
||||
EntityItemID id;
|
||||
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyDecryptedNonce(certID, decryptedText, id);
|
||||
|
||||
if (verificationSuccess) {
|
||||
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
|
||||
} else {
|
||||
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,10 +47,12 @@ class ContextOverlayInterface : public QObject, public Dependency {
|
|||
OverlayID _contextOverlayID { UNKNOWN_OVERLAY_ID };
|
||||
std::shared_ptr<Image3DOverlay> _contextOverlay { nullptr };
|
||||
public:
|
||||
|
||||
ContextOverlayInterface();
|
||||
|
||||
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
|
||||
void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; }
|
||||
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; }
|
||||
void setEnabled(bool enabled);
|
||||
bool getEnabled() { return _enabled; }
|
||||
bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; }
|
||||
|
@ -70,10 +72,19 @@ public slots:
|
|||
void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event);
|
||||
bool contextOverlayFilterPassed(const EntityItemID& entityItemID);
|
||||
|
||||
private slots:
|
||||
void handleChallengeOwnershipReplyPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
|
||||
private:
|
||||
|
||||
enum {
|
||||
MAX_SELECTION_COUNT = 16
|
||||
};
|
||||
|
||||
bool _verboseLogging { true };
|
||||
bool _enabled { true };
|
||||
QUuid _currentEntityWithContextOverlay{};
|
||||
EntityItemID _currentEntityWithContextOverlay{};
|
||||
EntityItemID _lastInspectedEntity{};
|
||||
QString _entityMarketplaceID;
|
||||
bool _contextOverlayJustClicked { false };
|
||||
|
||||
|
@ -85,8 +96,12 @@ private:
|
|||
void disableEntityHighlight(const EntityItemID& entityItemID);
|
||||
|
||||
void deletingEntity(const EntityItemID& entityItemID);
|
||||
void initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction);
|
||||
|
||||
SelectionToSceneHandler _selectionToSceneHandler;
|
||||
SelectionToSceneHandler _selectionToSceneHandlers[MAX_SELECTION_COUNT];
|
||||
|
||||
Q_INVOKABLE void startChallengeOwnershipTimer();
|
||||
QTimer _challengeOwnershipTimeoutTimer;
|
||||
};
|
||||
|
||||
#endif // hifi_ContextOverlayInterface_h
|
||||
|
|
|
@ -44,7 +44,7 @@ Cube3DOverlay::~Cube3DOverlay() {
|
|||
}
|
||||
|
||||
void Cube3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible) {
|
||||
if (!_renderVisible) {
|
||||
return; // do nothing if we're not visible
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ AABox Grid3DOverlay::getBounds() const {
|
|||
}
|
||||
|
||||
void Grid3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible) {
|
||||
if (!_renderVisible) {
|
||||
return; // do nothing if we're not visible
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ void Image3DOverlay::update(float deltatime) {
|
|||
}
|
||||
|
||||
void Image3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible || !getParentVisible() || !_texture || !_texture->isLoaded()) {
|
||||
if (!_renderVisible || !getParentVisible() || !_texture || !_texture->isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,8 +33,8 @@ Line3DOverlay::Line3DOverlay(const Line3DOverlay* line3DOverlay) :
|
|||
_length = line3DOverlay->getLength();
|
||||
_endParentID = line3DOverlay->getEndParentID();
|
||||
_endParentJointIndex = line3DOverlay->getEndJointIndex();
|
||||
_lineWidth = line3DOverlay->getLineWidth();
|
||||
_glow = line3DOverlay->getGlow();
|
||||
_glowWidth = line3DOverlay->getGlowWidth();
|
||||
}
|
||||
|
||||
Line3DOverlay::~Line3DOverlay() {
|
||||
|
@ -96,7 +96,7 @@ void Line3DOverlay::setEnd(const glm::vec3& end) {
|
|||
} else {
|
||||
_direction = glm::vec3(0.0f);
|
||||
}
|
||||
notifyRenderTransformChange();
|
||||
notifyRenderVariableChange();
|
||||
}
|
||||
|
||||
void Line3DOverlay::setLocalEnd(const glm::vec3& localEnd) {
|
||||
|
@ -123,7 +123,7 @@ AABox Line3DOverlay::getBounds() const {
|
|||
}
|
||||
|
||||
void Line3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible) {
|
||||
if (!_renderVisible) {
|
||||
return; // do nothing if we're not visible
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ void Line3DOverlay::render(RenderArgs* args) {
|
|||
geometryCache->renderDashedLine(*batch, start, end, colorv4, _geometryCacheID);
|
||||
} else {
|
||||
// renderGlowLine handles both glow = 0 and glow > 0 cases
|
||||
geometryCache->renderGlowLine(*batch, start, end, colorv4, _glow, _glowWidth, _geometryCacheID);
|
||||
geometryCache->renderGlowLine(*batch, start, end, colorv4, _glow, _lineWidth, _geometryCacheID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -239,11 +239,10 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
}
|
||||
}
|
||||
|
||||
auto glowWidth = properties["glowWidth"];
|
||||
if (glowWidth.isValid()) {
|
||||
setGlowWidth(glowWidth.toFloat());
|
||||
auto lineWidth = properties["lineWidth"];
|
||||
if (lineWidth.isValid()) {
|
||||
setLineWidth(lineWidth.toFloat());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QVariant Line3DOverlay::getProperty(const QString& property) {
|
||||
|
@ -262,6 +261,9 @@ QVariant Line3DOverlay::getProperty(const QString& property) {
|
|||
if (property == "length") {
|
||||
return QVariant(getLength());
|
||||
}
|
||||
if (property == "lineWidth") {
|
||||
return _lineWidth;
|
||||
}
|
||||
|
||||
return Base3DOverlay::getProperty(property);
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ public:
|
|||
// getters
|
||||
glm::vec3 getStart() const;
|
||||
glm::vec3 getEnd() const;
|
||||
const float& getLineWidth() const { return _lineWidth; }
|
||||
const float& getGlow() const { return _glow; }
|
||||
const float& getGlowWidth() const { return _glowWidth; }
|
||||
|
||||
// setters
|
||||
void setStart(const glm::vec3& start);
|
||||
|
@ -41,8 +41,8 @@ public:
|
|||
void setLocalStart(const glm::vec3& localStart) { setLocalPosition(localStart); }
|
||||
void setLocalEnd(const glm::vec3& localEnd);
|
||||
|
||||
void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; }
|
||||
void setGlow(const float& glow) { _glow = glow; }
|
||||
void setGlowWidth(const float& glowWidth) { _glowWidth = glowWidth; }
|
||||
|
||||
void setProperties(const QVariantMap& properties) override;
|
||||
QVariant getProperty(const QString& property) override;
|
||||
|
@ -70,8 +70,9 @@ private:
|
|||
glm::vec3 _direction; // in parent frame
|
||||
float _length { 1.0 }; // in parent frame
|
||||
|
||||
const float DEFAULT_LINE_WIDTH = 0.02f;
|
||||
float _lineWidth { DEFAULT_LINE_WIDTH };
|
||||
float _glow { 0.0 };
|
||||
float _glowWidth { 0.0 };
|
||||
int _geometryCacheID;
|
||||
};
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ AABox Planar3DOverlay::getBounds() const {
|
|||
|
||||
void Planar3DOverlay::setDimensions(const glm::vec2& value) {
|
||||
_dimensions = value;
|
||||
notifyRenderTransformChange();
|
||||
notifyRenderVariableChange();
|
||||
}
|
||||
|
||||
void Planar3DOverlay::setProperties(const QVariantMap& properties) {
|
||||
|
|
|
@ -23,7 +23,6 @@ Rectangle3DOverlay::Rectangle3DOverlay() :
|
|||
for (size_t i = 0; i < _rectGeometryIds.size(); ++i) {
|
||||
_rectGeometryIds[i] = geometryCache->allocateID();
|
||||
}
|
||||
qDebug() << "Building rect3d overlay";
|
||||
}
|
||||
|
||||
Rectangle3DOverlay::Rectangle3DOverlay(const Rectangle3DOverlay* rectangle3DOverlay) :
|
||||
|
@ -34,11 +33,9 @@ Rectangle3DOverlay::Rectangle3DOverlay(const Rectangle3DOverlay* rectangle3DOver
|
|||
for (size_t i = 0; i < _rectGeometryIds.size(); ++i) {
|
||||
_rectGeometryIds[i] = geometryCache->allocateID();
|
||||
}
|
||||
qDebug() << "Building rect3d overlay";
|
||||
}
|
||||
|
||||
Rectangle3DOverlay::~Rectangle3DOverlay() {
|
||||
qDebug() << "Destryoing rect3d overlay";
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
if (geometryCache) {
|
||||
geometryCache->releaseID(_geometryCacheID);
|
||||
|
@ -49,7 +46,7 @@ Rectangle3DOverlay::~Rectangle3DOverlay() {
|
|||
}
|
||||
|
||||
void Rectangle3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible) {
|
||||
if (!_renderVisible) {
|
||||
return; // do nothing if we're not visible
|
||||
}
|
||||
|
||||
|
@ -58,18 +55,11 @@ void Rectangle3DOverlay::render(RenderArgs* args) {
|
|||
const float MAX_COLOR = 255.0f;
|
||||
glm::vec4 rectangleColor(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
|
||||
|
||||
glm::vec3 position = getWorldPosition();
|
||||
glm::vec2 dimensions = getDimensions();
|
||||
glm::vec2 halfDimensions = dimensions * 0.5f;
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
|
||||
auto batch = args->_batch;
|
||||
|
||||
if (batch) {
|
||||
// FIXME Start using the _renderTransform instead of calling for Transform and Dimensions from here, do the custom things needed in evalRenderTransform()
|
||||
Transform transform;
|
||||
transform.setTranslation(position);
|
||||
transform.setRotation(rotation);
|
||||
Transform transform = getRenderTransform();
|
||||
glm::vec2 halfDimensions = transform.getScale() * 0.5f;
|
||||
transform.setScale(1.0f);
|
||||
|
||||
batch->setModelTransform(transform);
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
@ -124,8 +114,3 @@ void Rectangle3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
Rectangle3DOverlay* Rectangle3DOverlay::createClone() const {
|
||||
return new Rectangle3DOverlay(this);
|
||||
}
|
||||
|
||||
Transform Rectangle3DOverlay::evalRenderTransform() {
|
||||
return getTransform();
|
||||
}
|
||||
|
||||
|
|
|
@ -29,9 +29,6 @@ public:
|
|||
|
||||
virtual Rectangle3DOverlay* createClone() const override;
|
||||
|
||||
protected:
|
||||
Transform evalRenderTransform() override;
|
||||
|
||||
private:
|
||||
int _geometryCacheID;
|
||||
std::array<int, 4> _rectGeometryIds;
|
||||
|
|
|
@ -24,7 +24,7 @@ Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay) :
|
|||
}
|
||||
|
||||
void Shape3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible) {
|
||||
if (!_renderVisible) {
|
||||
return; // do nothing if we're not visible
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) :
|
|||
}
|
||||
|
||||
void Sphere3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible) {
|
||||
if (!_renderVisible) {
|
||||
return; // do nothing if we're not visible
|
||||
}
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ void Text3DOverlay::update(float deltatime) {
|
|||
}
|
||||
|
||||
void Text3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible || !getParentVisible()) {
|
||||
if (!_renderVisible || !getParentVisible()) {
|
||||
return; // do nothing if we're not visible
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ AABox Volume3DOverlay::getBounds() const {
|
|||
|
||||
void Volume3DOverlay::setDimensions(const glm::vec3& value) {
|
||||
_localBoundingBox.setBox(-value / 2.0f, value);
|
||||
notifyRenderTransformChange();
|
||||
notifyRenderVariableChange();
|
||||
}
|
||||
|
||||
void Volume3DOverlay::setProperties(const QVariantMap& properties) {
|
||||
|
|
|
@ -268,7 +268,7 @@ Q_INVOKABLE int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) {
|
|||
}
|
||||
|
||||
void Web3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible || !getParentVisible()) {
|
||||
if (!_renderVisible || !getParentVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue