mirror of
https://github.com/overte-org/overte.git
synced 2025-04-08 07:12:40 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into blue
This commit is contained in:
commit
2ecda8b64e
18 changed files with 651 additions and 75 deletions
|
@ -908,31 +908,11 @@ function validateInputs() {
|
|||
}
|
||||
|
||||
_.each(tables, function(table) {
|
||||
var inputs = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ':not([data-category]) input[data-changed="true"]');
|
||||
|
||||
var empty = false;
|
||||
|
||||
_.each(inputs, function(input){
|
||||
var inputVal = $(input).val();
|
||||
|
||||
if (inputVal.length === 0) {
|
||||
empty = true
|
||||
|
||||
markParentRowInvalid(input);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (empty) {
|
||||
showErrorMessage("Error", "Empty field(s)");
|
||||
inputsValid = false;
|
||||
return
|
||||
}
|
||||
|
||||
// validate keys specificially for spaces and equality to an existing key
|
||||
var newKeys = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ' td.key');
|
||||
|
||||
var keyWithSpaces = false;
|
||||
var empty = false;
|
||||
var duplicateKey = false;
|
||||
|
||||
_.each(newKeys, function(keyCell) {
|
||||
|
@ -944,6 +924,14 @@ function validateInputs() {
|
|||
return;
|
||||
}
|
||||
|
||||
// make sure the key isn't empty
|
||||
if (keyVal.length === 0) {
|
||||
empty = true
|
||||
|
||||
markParentRowInvalid(input);
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we don't have duplicate keys in the table
|
||||
var otherKeys = $(table).find('td.key').not(keyCell);
|
||||
_.each(otherKeys, function(otherKeyCell) {
|
||||
|
@ -971,6 +959,12 @@ function validateInputs() {
|
|||
return
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
showErrorMessage("Error", "Empty field(s)");
|
||||
inputsValid = false;
|
||||
return
|
||||
}
|
||||
|
||||
if (duplicateKey) {
|
||||
showErrorMessage("Error", "Two keys cannot be identical");
|
||||
inputsValid = false;
|
||||
|
|
|
@ -34,6 +34,7 @@ Item {
|
|||
property bool isMyCard: false
|
||||
property bool selected: false
|
||||
property bool isAdmin: false
|
||||
property bool currentlyEditingDisplayName: false
|
||||
|
||||
/* User image commented out for now - will probably be re-introduced later.
|
||||
Column {
|
||||
|
@ -104,6 +105,7 @@ Item {
|
|||
focus = false
|
||||
myDisplayName.border.width = 0
|
||||
color = hifi.colors.darkGray
|
||||
currentlyEditingDisplayName = false
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
|
@ -115,10 +117,12 @@ Item {
|
|||
myDisplayNameText.focus ? myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX, mouseY, TextInput.CursorOnCharacter) : myDisplayNameText.selectAll();
|
||||
myDisplayNameText.focus = true
|
||||
myDisplayNameText.color = "black"
|
||||
currentlyEditingDisplayName = true
|
||||
}
|
||||
onDoubleClicked: {
|
||||
myDisplayNameText.selectAll();
|
||||
myDisplayNameText.focus = true;
|
||||
currentlyEditingDisplayName = true
|
||||
}
|
||||
onEntered: myDisplayName.color = hifi.colors.lightGrayText
|
||||
onExited: myDisplayName.color = hifi.colors.textFieldLightBackground
|
||||
|
|
|
@ -51,6 +51,7 @@ Rectangle {
|
|||
|
||||
// This is the container for the PAL
|
||||
Rectangle {
|
||||
property bool punctuationMode: false
|
||||
id: palContainer
|
||||
// Size
|
||||
width: pal.width - 50
|
||||
|
@ -421,6 +422,16 @@ Rectangle {
|
|||
onExited: adminHelpText.color = hifi.colors.redHighlight
|
||||
}
|
||||
}
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard
|
||||
raised: myCard.currentlyEditingDisplayName && HMD.active
|
||||
numeric: parent.punctuationMode
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
// Timer used when selecting table rows that aren't yet present in the model
|
||||
// (i.e. when selecting avatars using edit.js or sphere overlays)
|
||||
|
|
|
@ -1202,8 +1202,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
|
||||
[this](const EntityItemID& entityItemID, const PointerEvent& event) {
|
||||
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
|
||||
setKeyboardFocusEntity(entityItemID);
|
||||
auto entity = getEntities()->getTree()->findEntityByID(entityItemID);
|
||||
if (entity && entity->wantsKeyboardFocus()) {
|
||||
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
|
||||
setKeyboardFocusEntity(entityItemID);
|
||||
}
|
||||
});
|
||||
|
||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [=](const EntityItemID& entityItemID) {
|
||||
|
@ -4165,6 +4168,8 @@ void Application::setKeyboardFocusOverlay(unsigned int overlayID) {
|
|||
auto size = overlay->getSize() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR;
|
||||
const float OVERLAY_DEPTH = 0.0105f;
|
||||
setKeyboardFocusHighlight(overlay->getPosition(), overlay->getRotation(), glm::vec3(size.x, size.y, OVERLAY_DEPTH));
|
||||
} else if (_keyboardFocusHighlight) {
|
||||
_keyboardFocusHighlight->setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
QString const ModelOverlay::TYPE = "model";
|
||||
|
||||
ModelOverlay::ModelOverlay()
|
||||
: _model(std::make_shared<Model>(std::make_shared<Rig>())),
|
||||
: _model(std::make_shared<Model>(std::make_shared<Rig>(), nullptr, this)),
|
||||
_modelTextures(QVariantMap())
|
||||
{
|
||||
_model->init();
|
||||
|
@ -27,7 +27,7 @@ ModelOverlay::ModelOverlay()
|
|||
|
||||
ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
|
||||
Volume3DOverlay(modelOverlay),
|
||||
_model(std::make_shared<Model>(std::make_shared<Rig>())),
|
||||
_model(std::make_shared<Model>(std::make_shared<Rig>(), nullptr, this)),
|
||||
_modelTextures(QVariantMap()),
|
||||
_url(modelOverlay->_url),
|
||||
_updateModel(false)
|
||||
|
|
|
@ -540,7 +540,7 @@ void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const Sha
|
|||
std::static_pointer_cast<EntityTree>(_tree)->processEraseMessage(message, sourceNode);
|
||||
}
|
||||
|
||||
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, float loadingPriority) {
|
||||
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, float loadingPriority, SpatiallyNestable* spatiallyNestableOverride) {
|
||||
ModelPointer model = nullptr;
|
||||
|
||||
// Only create and delete models on the thread that owns the EntityTreeRenderer
|
||||
|
@ -552,7 +552,7 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, float loading
|
|||
return model;
|
||||
}
|
||||
|
||||
model = std::make_shared<Model>(std::make_shared<Rig>());
|
||||
model = std::make_shared<Model>(std::make_shared<Rig>(), nullptr, spatiallyNestableOverride);
|
||||
model->setLoadingPriority(loadingPriority);
|
||||
model->init();
|
||||
model->setURL(QUrl(url));
|
||||
|
|
|
@ -77,7 +77,7 @@ public:
|
|||
void reloadEntityScripts();
|
||||
|
||||
/// if a renderable entity item needs a model, we will allocate it for them
|
||||
Q_INVOKABLE ModelPointer allocateModel(const QString& url, float loadingPriority = 0.0f);
|
||||
Q_INVOKABLE ModelPointer allocateModel(const QString& url, float loadingPriority = 0.0f, SpatiallyNestable* spatiallyNestableOverride = nullptr);
|
||||
|
||||
/// if a renderable entity item needs to update the URL of a model, we will handle that for the entity
|
||||
Q_INVOKABLE ModelPointer updateModel(ModelPointer original, const QString& newUrl);
|
||||
|
|
|
@ -504,8 +504,7 @@ ModelPointer RenderableModelEntityItem::getModel(QSharedPointer<EntityTreeRender
|
|||
if (!getModelURL().isEmpty()) {
|
||||
// If we don't have a model, allocate one *immediately*
|
||||
if (!_model) {
|
||||
_model = _myRenderer->allocateModel(getModelURL(), renderer->getEntityLoadingPriority(*this));
|
||||
_model->setSpatiallyNestableOverride(shared_from_this());
|
||||
_model = _myRenderer->allocateModel(getModelURL(), renderer->getEntityLoadingPriority(*this), this);
|
||||
_needsInitialSimulation = true;
|
||||
// If we need to change URLs, update it *after rendering* (to avoid access violations)
|
||||
} else if (QUrl(getModelURL()) != _model->getURL()) {
|
||||
|
|
|
@ -651,6 +651,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
// NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data
|
||||
// even when we would otherwise ignore the rest of the packet.
|
||||
|
||||
bool filterRejection = false;
|
||||
if (propertyFlags.getHasProperty(PROP_SIMULATION_OWNER)) {
|
||||
|
||||
QByteArray simOwnerData;
|
||||
|
@ -663,6 +664,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
if (wantTerseEditLogging() && _simulationOwner != newSimOwner) {
|
||||
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << newSimOwner;
|
||||
}
|
||||
// This is used in the custom physics setters, below. When an entity-server filter alters
|
||||
// or rejects a set of properties, it clears this. In such cases, we don't want those custom
|
||||
// setters to ignore what the server says.
|
||||
filterRejection = newSimOwner.getID().isNull();
|
||||
if (weOwnSimulation) {
|
||||
if (newSimOwner.getID().isNull() && !_simulationOwner.pendingRelease(lastEditedFromBufferAdjusted)) {
|
||||
// entity-server is trying to clear our ownership (probably at our own request)
|
||||
|
@ -715,55 +720,45 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
// Note: duplicate packets are expected and not wrong. They may be sent for any number of
|
||||
// reasons and the contract is that the client handles them in an idempotent manner.
|
||||
auto lastEdited = lastEditedFromBufferAdjusted;
|
||||
auto customUpdatePositionFromNetwork = [this, lastEdited, overwriteLocalData, weOwnSimulation](glm::vec3 value){
|
||||
bool simulationChanged = lastEdited > _lastUpdatedPositionTimestamp;
|
||||
bool valueChanged = value != _lastUpdatedPositionValue;
|
||||
bool shouldUpdate = overwriteLocalData && !weOwnSimulation && simulationChanged && valueChanged;
|
||||
if (shouldUpdate) {
|
||||
bool otherOverwrites = overwriteLocalData && !weOwnSimulation;
|
||||
auto shouldUpdate = [lastEdited, otherOverwrites, filterRejection](quint64 updatedTimestamp, bool valueChanged) {
|
||||
bool simulationChanged = lastEdited > updatedTimestamp;
|
||||
return otherOverwrites && simulationChanged && (valueChanged || filterRejection);
|
||||
};
|
||||
auto customUpdatePositionFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value){
|
||||
if (shouldUpdate(_lastUpdatedPositionTimestamp, value != _lastUpdatedPositionValue)) {
|
||||
updatePositionFromNetwork(value);
|
||||
_lastUpdatedPositionTimestamp = lastEdited;
|
||||
_lastUpdatedPositionValue = value;
|
||||
}
|
||||
};
|
||||
|
||||
auto customUpdateRotationFromNetwork = [this, lastEdited, overwriteLocalData, weOwnSimulation](glm::quat value){
|
||||
bool simulationChanged = lastEdited > _lastUpdatedRotationTimestamp;
|
||||
bool valueChanged = value != _lastUpdatedRotationValue;
|
||||
bool shouldUpdate = overwriteLocalData && !weOwnSimulation && simulationChanged && valueChanged;
|
||||
if (shouldUpdate) {
|
||||
auto customUpdateRotationFromNetwork = [this, shouldUpdate, lastEdited](glm::quat value){
|
||||
if (shouldUpdate(_lastUpdatedRotationTimestamp, value != _lastUpdatedRotationValue)) {
|
||||
updateRotationFromNetwork(value);
|
||||
_lastUpdatedRotationTimestamp = lastEdited;
|
||||
_lastUpdatedRotationValue = value;
|
||||
}
|
||||
};
|
||||
|
||||
auto customUpdateVelocityFromNetwork = [this, lastEdited, overwriteLocalData, weOwnSimulation](glm::vec3 value){
|
||||
bool simulationChanged = lastEdited > _lastUpdatedVelocityTimestamp;
|
||||
bool valueChanged = value != _lastUpdatedVelocityValue;
|
||||
bool shouldUpdate = overwriteLocalData && !weOwnSimulation && simulationChanged && valueChanged;
|
||||
if (shouldUpdate) {
|
||||
auto customUpdateVelocityFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value){
|
||||
if (shouldUpdate(_lastUpdatedVelocityTimestamp, value != _lastUpdatedVelocityValue)) {
|
||||
updateVelocityFromNetwork(value);
|
||||
_lastUpdatedVelocityTimestamp = lastEdited;
|
||||
_lastUpdatedVelocityValue = value;
|
||||
}
|
||||
};
|
||||
|
||||
auto customUpdateAngularVelocityFromNetwork = [this, lastEdited, overwriteLocalData, weOwnSimulation](glm::vec3 value){
|
||||
bool simulationChanged = lastEdited > _lastUpdatedAngularVelocityTimestamp;
|
||||
bool valueChanged = value != _lastUpdatedAngularVelocityValue;
|
||||
bool shouldUpdate = overwriteLocalData && !weOwnSimulation && simulationChanged && valueChanged;
|
||||
if (shouldUpdate) {
|
||||
auto customUpdateAngularVelocityFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value){
|
||||
if (shouldUpdate(_lastUpdatedAngularVelocityTimestamp, value != _lastUpdatedAngularVelocityValue)) {
|
||||
updateAngularVelocityFromNetwork(value);
|
||||
_lastUpdatedAngularVelocityTimestamp = lastEdited;
|
||||
_lastUpdatedAngularVelocityValue = value;
|
||||
}
|
||||
};
|
||||
|
||||
auto customSetAcceleration = [this, lastEdited, overwriteLocalData, weOwnSimulation](glm::vec3 value){
|
||||
bool simulationChanged = lastEdited > _lastUpdatedAccelerationTimestamp;
|
||||
bool valueChanged = value != _lastUpdatedAccelerationValue;
|
||||
bool shouldUpdate = overwriteLocalData && !weOwnSimulation && simulationChanged && valueChanged;
|
||||
if (shouldUpdate) {
|
||||
auto customSetAcceleration = [this, shouldUpdate, lastEdited](glm::vec3 value){
|
||||
if (shouldUpdate(_lastUpdatedAccelerationTimestamp, value != _lastUpdatedAccelerationValue)) {
|
||||
setAcceleration(value);
|
||||
_lastUpdatedAccelerationTimestamp = lastEdited;
|
||||
_lastUpdatedAccelerationValue = value;
|
||||
|
|
|
@ -73,6 +73,9 @@ void KeyboardMouseDevice::mousePressEvent(QMouseEvent* event) {
|
|||
_mousePressTime = usecTimestampNow();
|
||||
_mouseMoved = false;
|
||||
|
||||
_mousePressPos = event->pos();
|
||||
_clickDeadspotActive = true;
|
||||
|
||||
eraseMouseClicked();
|
||||
}
|
||||
|
||||
|
@ -84,9 +87,11 @@ void KeyboardMouseDevice::mouseReleaseEvent(QMouseEvent* event) {
|
|||
// input for this button we might want to add some small tolerance to this so if you do a small drag it
|
||||
// still counts as a click.
|
||||
static const int CLICK_TIME = USECS_PER_MSEC * 500; // 500 ms to click
|
||||
if (!_mouseMoved && (usecTimestampNow() - _mousePressTime < CLICK_TIME)) {
|
||||
if (_clickDeadspotActive && (usecTimestampNow() - _mousePressTime < CLICK_TIME)) {
|
||||
_inputDevice->_buttonPressedMap.insert(_inputDevice->makeInput((Qt::MouseButton) event->button(), true).getChannel());
|
||||
}
|
||||
|
||||
_clickDeadspotActive = false;
|
||||
}
|
||||
|
||||
void KeyboardMouseDevice::eraseMouseClicked() {
|
||||
|
@ -109,9 +114,14 @@ void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) {
|
|||
// outside of the application window, because we don't get MouseEvents when the cursor is outside
|
||||
// of the application window.
|
||||
_lastCursor = currentPos;
|
||||
|
||||
_mouseMoved = true;
|
||||
|
||||
eraseMouseClicked();
|
||||
const int CLICK_EVENT_DEADSPOT = 6; // pixels
|
||||
if (_clickDeadspotActive && (_mousePressPos - currentPos).manhattanLength() > CLICK_EVENT_DEADSPOT) {
|
||||
eraseMouseClicked();
|
||||
_clickDeadspotActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) {
|
||||
|
|
|
@ -118,8 +118,10 @@ public:
|
|||
|
||||
protected:
|
||||
QPoint _lastCursor;
|
||||
QPoint _mousePressPos;
|
||||
quint64 _mousePressTime;
|
||||
bool _mouseMoved;
|
||||
bool _clickDeadspotActive;
|
||||
glm::vec2 _lastTouch;
|
||||
std::shared_ptr<InputDevice> _inputDevice { std::make_shared<InputDevice>() };
|
||||
|
||||
|
|
|
@ -78,11 +78,12 @@ void initCollisionMaterials() {
|
|||
}
|
||||
}
|
||||
|
||||
Model::Model(RigPointer rig, QObject* parent) :
|
||||
Model::Model(RigPointer rig, QObject* parent, SpatiallyNestable* spatiallyNestableOverride) :
|
||||
QObject(parent),
|
||||
_renderGeometry(),
|
||||
_collisionGeometry(),
|
||||
_renderWatcher(_renderGeometry),
|
||||
_spatiallyNestableOverride(spatiallyNestableOverride),
|
||||
_translation(0.0f),
|
||||
_rotation(),
|
||||
_scale(1.0f, 1.0f, 1.0f),
|
||||
|
@ -133,16 +134,10 @@ void Model::setRotation(const glm::quat& rotation) {
|
|||
updateRenderItems();
|
||||
}
|
||||
|
||||
void Model::setSpatiallyNestableOverride(SpatiallyNestablePointer override) {
|
||||
_spatiallyNestableOverride = override;
|
||||
updateRenderItems();
|
||||
}
|
||||
|
||||
Transform Model::getTransform() const {
|
||||
SpatiallyNestablePointer spatiallyNestableOverride = _spatiallyNestableOverride.lock();
|
||||
if (spatiallyNestableOverride) {
|
||||
if (_spatiallyNestableOverride) {
|
||||
bool success;
|
||||
Transform transform = spatiallyNestableOverride->getTransform(success);
|
||||
Transform transform = _spatiallyNestableOverride->getTransform(success);
|
||||
if (success) {
|
||||
transform.setScale(getScale());
|
||||
return transform;
|
||||
|
|
|
@ -67,7 +67,7 @@ public:
|
|||
|
||||
static void setAbstractViewStateInterface(AbstractViewStateInterface* viewState) { _viewState = viewState; }
|
||||
|
||||
Model(RigPointer rig, QObject* parent = nullptr);
|
||||
Model(RigPointer rig, QObject* parent = nullptr, SpatiallyNestable* spatiallyNestableOverride = nullptr);
|
||||
virtual ~Model();
|
||||
|
||||
inline ModelPointer getThisPointer() const {
|
||||
|
@ -205,7 +205,6 @@ public:
|
|||
|
||||
void setTranslation(const glm::vec3& translation);
|
||||
void setRotation(const glm::quat& rotation);
|
||||
void setSpatiallyNestableOverride(SpatiallyNestablePointer ptr);
|
||||
|
||||
const glm::vec3& getTranslation() const { return _translation; }
|
||||
const glm::quat& getRotation() const { return _rotation; }
|
||||
|
@ -292,12 +291,11 @@ protected:
|
|||
|
||||
GeometryResourceWatcher _renderWatcher;
|
||||
|
||||
SpatiallyNestable* _spatiallyNestableOverride;
|
||||
|
||||
glm::vec3 _translation;
|
||||
glm::quat _rotation;
|
||||
glm::vec3 _scale;
|
||||
|
||||
SpatiallyNestableWeakPointer _spatiallyNestableOverride;
|
||||
|
||||
glm::vec3 _offset;
|
||||
|
||||
static float FAKE_DIMENSION_PLACEHOLDER;
|
||||
|
|
|
@ -24,7 +24,7 @@ var DEFAULT_SCRIPTS = [
|
|||
"system/goto.js",
|
||||
"system/marketplaces/marketplaces.js",
|
||||
"system/edit.js",
|
||||
"system/users.js",
|
||||
"system/tablet-users.js",
|
||||
"system/selectAudioDevice.js",
|
||||
"system/notifications.js",
|
||||
"system/controllers/controllerDisplayManager.js",
|
||||
|
|
|
@ -853,7 +853,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.setState = function(newState, reason) {
|
||||
if (isInEditMode() && (newState !== STATE_OFF &&
|
||||
if ((isInEditMode() && this.grabbedEntity !== HMD.tabletID )&& (newState !== STATE_OFF &&
|
||||
newState !== STATE_SEARCHING &&
|
||||
newState !== STATE_OVERLAY_STYLUS_TOUCHING)) {
|
||||
return;
|
||||
|
@ -1703,7 +1703,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.isTablet = function (entityID) {
|
||||
if (entityID === HMD.tabletID) { // XXX what's a better way to know this?
|
||||
if (entityID === HMD.tabletID) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
465
scripts/system/html/users.html
Normal file
465
scripts/system/html/users.html
Normal file
|
@ -0,0 +1,465 @@
|
|||
<!--
|
||||
// users.html
|
||||
//
|
||||
// Created by Faye Li on 18 Jan 2017
|
||||
// Copyright 2017 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
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Users Online</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600,700"" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-family: 'Raleway', sans-serif;
|
||||
color: white;
|
||||
background: linear-gradient(#2b2b2b, #0f212e);
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
background: linear-gradient(#2b2b2b, #1e1e1e);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.top-bar .myContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-left: 30px;
|
||||
margin-right: 30px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#refresh-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
#user-info-div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
#visibility-toggle {
|
||||
font-family: 'Raleway';
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
vertical-align: top;
|
||||
height: 28px;
|
||||
min-width: 120px;
|
||||
padding: 0px 18px;
|
||||
margin-right: 0px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
color: #121212;
|
||||
background-color: #afafaf;
|
||||
background: linear-gradient(#fff 20%, #afafaf 100%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#visibility-toggle:enabled:hover {
|
||||
background: linear-gradient(#fff, #fff);
|
||||
border: none;
|
||||
}
|
||||
|
||||
#visibility-toggle:active {
|
||||
background: linear-gradient(#afafaf, #afafaf);
|
||||
}
|
||||
|
||||
#visibility-toggle span {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabs li {
|
||||
display: inline-block;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.tabs li.current {
|
||||
background: rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.current {
|
||||
display: inherit;
|
||||
background: rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
.tab-content ul {
|
||||
list-style: none;
|
||||
padding: 15px 0px 15px 15px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tab-content ul li {
|
||||
padding: 2px 0px;
|
||||
}
|
||||
|
||||
input[type=button] {
|
||||
font-family: 'Raleway';
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
vertical-align: top;
|
||||
height: 28px;
|
||||
min-width: 120px;
|
||||
padding: 0px 18px;
|
||||
margin-right: 6px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
background: linear-gradient(#343434 20%, #000 100%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type=button].red {
|
||||
color: #fff;
|
||||
background-color: #94132e;
|
||||
background: linear-gradient(#d42043 20%, #94132e 100%);
|
||||
}
|
||||
input[type=button].blue {
|
||||
color: #fff;
|
||||
background-color: #1080b8;
|
||||
background: linear-gradient(#00b4ef 20%, #1080b8 100%);
|
||||
}
|
||||
input[type=button].white {
|
||||
color: #121212;
|
||||
background-color: #afafaf;
|
||||
background: linear-gradient(#fff 20%, #afafaf 100%);
|
||||
}
|
||||
|
||||
input[type=button]:enabled:hover {
|
||||
background: linear-gradient(#000, #000);
|
||||
border: none;
|
||||
}
|
||||
input[type=button].red:enabled:hover {
|
||||
background: linear-gradient(#d42043, #d42043);
|
||||
border: none;
|
||||
}
|
||||
input[type=button].blue:enabled:hover {
|
||||
background: linear-gradient(#00b4ef, #00b4ef);
|
||||
border: none;
|
||||
}
|
||||
input[type=button].white:enabled:hover {
|
||||
background: linear-gradient(#fff, #fff);
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type=button]:active {
|
||||
background: linear-gradient(#343434, #343434);
|
||||
}
|
||||
input[type=button].red:active {
|
||||
background: linear-gradient(#94132e, #94132e);
|
||||
}
|
||||
input[type=button].blue:active {
|
||||
background: linear-gradient(#1080b8, #1080b8);
|
||||
}
|
||||
input[type=button].white:active {
|
||||
background: linear-gradient(#afafaf, #afafaf);
|
||||
}
|
||||
|
||||
input[type=button]:disabled {
|
||||
color: #252525;
|
||||
background: linear-gradient(#575757 20%, #252525 100%);
|
||||
}
|
||||
|
||||
input[type=button][pressed=pressed] {
|
||||
color: #00b4ef;
|
||||
}
|
||||
|
||||
#friends-button {
|
||||
margin: 0px 0px 15px 10px;
|
||||
}
|
||||
|
||||
/*Vertically Center Modal*/
|
||||
.modal {
|
||||
color: black;
|
||||
text-align: center;
|
||||
padding: 0!important;
|
||||
}
|
||||
|
||||
.modal:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.dropdown-menu li {
|
||||
color: #333;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.dropdown-menu .divider {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.dropdown-menu li:hover {
|
||||
background: #dcdcdc;
|
||||
}
|
||||
|
||||
.dropdown-menu li h6 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dropdown-menu li p {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<div class="myContainer">
|
||||
<div>Users Online</div>
|
||||
<img id="refresh-button" onclick="pollUsers()" src="https://hifi-content.s3.amazonaws.com/faye/tablet-dev/refresh-icon.svg"></img>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div id="user-info-div">
|
||||
<h4></h4>
|
||||
<div class="dropdown">
|
||||
<button id="visibility-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Online
|
||||
<span class="glyphicon glyphicon-menu-down"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="visibility-toggle">
|
||||
<li class="visibility-option" data-visibility="all">
|
||||
<h6>Online</h6>
|
||||
<p>You will be shown online to everyone else. Anybody will be able to find you from the users online list and jump to your current location.</p>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li class="visibility-option" data-visibility="friends">
|
||||
<h6>Available to Friends Only</h6>
|
||||
<p>You will be shown online only to users you have added as friends. Other users may still interact with you in the same domain, but they won't be able to find you from the users online list.</p>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li class="visibility-option" data-visibility="none">
|
||||
<h6>Appear Offline</h6>
|
||||
<p>No one will be able to find you from the users online list. However, this does not prevent other users in the same domain from interacting with you. For a complete "Do not disturb" mode, you may want to go to your own private domain and set allow entering to no one.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="tabs">
|
||||
<li tab-id="tab-1" class="current">Everyone (0)</li>
|
||||
<li tab-id="tab-2">Friends (0)</li>
|
||||
</ul>
|
||||
<div id="tab-1" class="tab-content current">
|
||||
<ul></ul>
|
||||
</div>
|
||||
<div id="tab-2" class="tab-content">
|
||||
<ul></ul>
|
||||
<input type="button" class="blue" id="friends-button" value="Add/Remove Friends">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="myModalLabel">Jump to username @ Placename</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
This will teleport you to a new location and possibly another domain. Are you sure?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="button" data-dismiss="modal" value="Cancel">
|
||||
<input type="button" data-dismiss="modal" id="jump-to-confirm-button" value="OK">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
var METAVERSE_API_URL = "https://metaverse.highfidelity.com/api/v1/users?status=online";
|
||||
var FRIENDS_FILTER = "&filter=friends";
|
||||
var myUsername = null;
|
||||
var myVisibility = null;
|
||||
|
||||
function displayUsers(data, element) {
|
||||
element.empty();
|
||||
for (var i = 0; i < data.users.length; i++) {
|
||||
// Don't display users who aren't in a domain
|
||||
if (typeof data.users[i].location.root.name === "undefined") {
|
||||
console.log(data.users[i].username + "is online but not in a domain");
|
||||
$("#dev-div").append("<p>" + data.users[i].username + "is online but not in a domain</p>");
|
||||
} else {
|
||||
$("#dev-div").append("<li>" + data.users[i].username + " @ " + data.users[i].location.root.name + "</li>");
|
||||
if (data.users[i].username === myUsername) {
|
||||
$("#user-info-div h4").text(data.users[i].username + " @ " + data.users[i].location.root.name);
|
||||
} else {
|
||||
console.log(data.users[i].username + " @ " + data.users[i].location.root.name);
|
||||
// Create a list item and put user info in data-* attributes, also make it trigger the jump to confirmation modal
|
||||
$("<li></li>", {
|
||||
"data-toggle": "modal",
|
||||
"data-target": "#myModal",
|
||||
"data-username": data.users[i].username,
|
||||
"data-placename": data.users[i].location.root.name,
|
||||
text: data.users[i].username + " @ " + data.users[i].location.root.name
|
||||
}).appendTo(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processData(data, type) {
|
||||
var num = data.users.length;
|
||||
if (type === "everyone") {
|
||||
$(".tabs li:nth-child(1)").text("Everyone (" + num + ")");
|
||||
displayUsers(data, $("#tab-1 ul"));
|
||||
} else if (type === "friends") {
|
||||
$(".tabs li:nth-child(2)").text("Friends (" + num + ")");
|
||||
displayUsers(data, $("#tab-2 ul"));
|
||||
}
|
||||
}
|
||||
|
||||
function pollUsers() {
|
||||
$("#dev-div").append("<p>polling users..</p>");
|
||||
$.ajax({
|
||||
url: METAVERSE_API_URL,
|
||||
success: function(response) {
|
||||
console.log(response);
|
||||
$("#dev-div").append("<p>polling everyone sucess</p>");
|
||||
processData(response.data, "everyone");
|
||||
}
|
||||
});
|
||||
$.ajax({
|
||||
url: METAVERSE_API_URL + FRIENDS_FILTER,
|
||||
success: function(response) {
|
||||
console.log(response);
|
||||
$("#dev-div").append("<p>polling friends sucess</p>");
|
||||
processData(response.data, "friends");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onScriptEventReceived(event) {
|
||||
$("#dev-div").append("<p>Received a script event, its type is " + typeof event + "</p>");
|
||||
if (typeof event === "string") {
|
||||
// Parse the string into an object
|
||||
event = JSON.parse(event);
|
||||
}
|
||||
if (event.type === "user-info") {
|
||||
myUsername = event.data.username;
|
||||
myVisibility = event.data.visibility;
|
||||
$("#user-info-div h4").text(myUsername);
|
||||
var buttonText = "Online";
|
||||
if (myVisibility === "none") {
|
||||
buttonText = "Appear Offline";
|
||||
} else if (myVisibility === "friends") {
|
||||
buttonText = "Available To Friends Only";
|
||||
}
|
||||
$("#visibility-toggle").html(buttonText + "<span class='glyphicon glyphicon-menu-down'></span>")
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#dev-div").append("<p>ready</p>");
|
||||
// Auto-load user lists when page loads
|
||||
pollUsers();
|
||||
|
||||
// Click listener for tabs
|
||||
$(".tabs li").click(function() {
|
||||
var tabID = $(this).attr("tab-id");
|
||||
$(".tab-content").removeClass("current");
|
||||
$("#" + tabID).addClass("current");
|
||||
$(".tabs li").removeClass("current");
|
||||
$(this).addClass("current");
|
||||
});
|
||||
|
||||
// Jump to user: Fill confirmation modal with data of the selected user
|
||||
$("#myModal").on("show.bs.modal", function (event) {
|
||||
// Get the element that triggered the modal
|
||||
var li = $(event.relatedTarget);
|
||||
// Extract info from data-* attributes
|
||||
var username = li.data("username");
|
||||
var placename = li.data("placename");
|
||||
// Write info to the modal
|
||||
var modal = $(this);
|
||||
modal.find(".modal-title").text("Jump to " + username + " @ " + placename);
|
||||
$("#jump-to-confirm-button").data("username", username);
|
||||
})
|
||||
|
||||
$("#jump-to-confirm-button").click(function() {
|
||||
var jumpToObject = {
|
||||
"type": "jump-to",
|
||||
"data": {
|
||||
"username": $(this).data("username")
|
||||
}
|
||||
}
|
||||
EventBridge.emitWebEvent(JSON.stringify(jumpToObject));
|
||||
});
|
||||
|
||||
// Click listener for toggling who can see me
|
||||
$(".visibility-option").click(function() {
|
||||
myVisibility = $(this).data("visibility");
|
||||
var newButtonText = $(this).find("h6").text();
|
||||
$("#visibility-toggle").html(newButtonText + "<span class='glyphicon glyphicon-menu-down'></span>");
|
||||
var visibilityObject = {
|
||||
"type": "toggle-visibility",
|
||||
"data": {
|
||||
"visibility": myVisibility
|
||||
}
|
||||
}
|
||||
EventBridge.emitWebEvent(JSON.stringify(visibilityObject));
|
||||
});
|
||||
|
||||
// Listen for events from hifi
|
||||
EventBridge.scriptEventReceived.connect(onScriptEventReceived);
|
||||
|
||||
// Send a ready event to hifi
|
||||
var eventObject = {"type": "ready"};
|
||||
EventBridge.emitWebEvent(JSON.stringify(eventObject));
|
||||
|
||||
// Click listener for add/remove friends button
|
||||
$("#friends-button").click(function() {
|
||||
// Send a manage friends event to hifi
|
||||
eventObject = {"type": "manage-friends"};
|
||||
EventBridge.emitWebEvent(JSON.stringify(eventObject));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1038,7 +1038,7 @@ SelectionDisplay = (function() {
|
|||
if (entityIntersection.intersects &&
|
||||
(!overlayIntersection.intersects || (entityIntersection.distance < overlayIntersection.distance))) {
|
||||
|
||||
if (HMD.tabletID == entityIntersection.entityID) {
|
||||
if (HMD.tabletID === entityIntersection.entityID) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
98
scripts/system/tablet-users.js
Normal file
98
scripts/system/tablet-users.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
"use strict";
|
||||
|
||||
//
|
||||
// users.js
|
||||
//
|
||||
// Created by Faye Li on 18 Jan 2017.
|
||||
// Copyright 2017 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
|
||||
//
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
var USERS_URL = "https://hifi-content.s3.amazonaws.com/faye/tablet-dev/users.html";
|
||||
|
||||
var FRIENDS_WINDOW_URL = "https://metaverse.highfidelity.com/user/friends";
|
||||
var FRIENDS_WINDOW_WIDTH = 290;
|
||||
var FRIENDS_WINDOW_HEIGHT = 500;
|
||||
var FRIENDS_WINDOW_TITLE = "Add/Remove Friends";
|
||||
|
||||
// Initialise visibility based on global service
|
||||
var VISIBILITY_VALUES_SET = {};
|
||||
VISIBILITY_VALUES_SET["all"] = true;
|
||||
VISIBILITY_VALUES_SET["friends"] = true;
|
||||
VISIBILITY_VALUES_SET["none"] = true;
|
||||
var myVisibility;
|
||||
if (GlobalServices.findableBy in VISIBILITY_VALUES_SET) {
|
||||
myVisibility = GlobalServices.findableBy;
|
||||
} else {
|
||||
// default to friends if it can't be determined
|
||||
myVisibility = "friends";
|
||||
GlobalServices.findableBy = myVisibilty;
|
||||
}
|
||||
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/people-i.svg",
|
||||
text: "Users"
|
||||
});
|
||||
|
||||
function onClicked() {
|
||||
tablet.gotoWebScreen(USERS_URL);
|
||||
}
|
||||
|
||||
function onWebEventReceived(event) {
|
||||
print("Script received a web event, its type is " + typeof event);
|
||||
if (typeof event === "string") {
|
||||
event = JSON.parse(event);
|
||||
}
|
||||
if (event.type === "ready") {
|
||||
// send username to html
|
||||
var myUsername = GlobalServices.username;
|
||||
var object = {
|
||||
"type": "user-info",
|
||||
"data": {
|
||||
"username": myUsername,
|
||||
"visibility": myVisibility
|
||||
}
|
||||
};
|
||||
tablet.emitScriptEvent(JSON.stringify(object));
|
||||
}
|
||||
if (event.type === "manage-friends") {
|
||||
// open a web overlay to metaverse friends page
|
||||
var friendsWindow = new OverlayWebWindow({
|
||||
title: FRIENDS_WINDOW_TITLE,
|
||||
width: FRIENDS_WINDOW_WIDTH,
|
||||
height: FRIENDS_WINDOW_HEIGHT,
|
||||
visible: false
|
||||
});
|
||||
friendsWindow.setURL(FRIENDS_WINDOW_URL);
|
||||
friendsWindow.setVisible(true);
|
||||
friendsWindow.raise();
|
||||
}
|
||||
if (event.type === "jump-to") {
|
||||
if (typeof event.data.username !== undefined) {
|
||||
// teleport to selected user from the online users list
|
||||
location.goToUser(event.data.username);
|
||||
}
|
||||
}
|
||||
if (event.type === "toggle-visibility") {
|
||||
if (typeof event.data.visibility !== undefined) {
|
||||
// update your visibility (all, friends, or none)
|
||||
myVisibility = event.data.visibility;
|
||||
GlobalServices.findableBy = myVisibility;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.clicked.connect(onClicked);
|
||||
tablet.webEventReceived.connect(onWebEventReceived);
|
||||
|
||||
function cleanup() {
|
||||
button.clicked.disconnect(onClicked);
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}()); // END LOCAL_SCOPE
|
Loading…
Reference in a new issue