mirror of
https://github.com/JulianGro/overte.git
synced 2025-05-01 06:27:31 +02:00
Merge branch 'master' into vs2015
This commit is contained in:
commit
1846ead27c
658 changed files with 39140 additions and 16840 deletions
BUILD_OSX.mdBUILD_WIN.mdCMakeLists.txt
assignment-client/src
Agent.cppAssignmentClient.cppAssignmentClient.hAssignmentClientApp.cppAssignmentClientApp.hAssignmentClientMonitor.cppAssignmentClientMonitor.hAssignmentThread.cppAssignmentThread.h
audio
avatars
entities
main.cppoctree
cmake
domain-server
CMakeLists.txt
resources
describe-settings.json
web
src
examples
acScripts
avatarSelector.jsblockWorld.jscontrollers/hydra
defaultScripts.jsdialTone.jsdice.jsedit.jsentityScripts
chessPiece.jsinspect.jslightController.jsmovable.jsplaySoundOnClick.jsplaySoundOnEnterOrLeave.jssitOnEntity.js
example
|
@ -12,6 +12,12 @@ We have a [homebrew formulas repository](https://github.com/highfidelity/homebre
|
|||
|
||||
*Our [qt5 homebrew formula](https://raw.github.com/highfidelity/homebrew-formulas/master/qt5.rb) is for a patched version of Qt 5.4.x stable that removes wireless network scanning that can reduce real-time audio performance. We recommended you use this formula to install Qt.*
|
||||
|
||||
###Qt
|
||||
|
||||
Assuming you've installed Qt 5 using the homebrew instructions above, you'll need to set QT_CMAKE_PREFIX_PATH so CMake can find your installation of Qt. For Qt 5.4.1 installed via homebrew, set QT_CMAKE_PREFIX_PATH as follows.
|
||||
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.4.1/lib/cmake
|
||||
|
||||
###Xcode
|
||||
If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles.
|
||||
|
||||
|
@ -19,4 +25,4 @@ If Xcode is your editor of choice, you can ask CMake to generate Xcode project f
|
|||
|
||||
After running cmake, you will have the make files or Xcode project file necessary to build all of the components. Open the hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target drop down), and click Run.
|
||||
|
||||
If the build completes successfully, you will have built targets for all components located in the `build/${target_name}/Debug` directories.
|
||||
If the build completes successfully, you will have built targets for all components located in the `build/${target_name}/Debug` directories.
|
||||
|
|
15
BUILD_WIN.md
15
BUILD_WIN.md
|
@ -75,21 +75,6 @@ To prevent these problems, install OpenSSL yourself. Download the following bina
|
|||
|
||||
Install OpenSSL into the Windows system directory, to make sure that Qt uses the version that you've just installed, and not some other version.
|
||||
|
||||
###vhacd
|
||||
Download it directly from https://github.com/virneo/v-hacd
|
||||
|
||||
To build it run the following commands
|
||||
1. cd src\
|
||||
2. mkdir build
|
||||
3. cd build
|
||||
4. cmake ..
|
||||
|
||||
Build using visual studio 2013. Build ALL_BUILD and INSTALL targets both in Release and Debug.
|
||||
|
||||
This will create an output folder with include and lib directory inside it.
|
||||
|
||||
Either copy the contents of output folder to ENV %HIFI_LIB_DIR%/vhacd or create an environment variable VHACD_ROOT_DIR to this output directory.
|
||||
|
||||
###Build High Fidelity using Visual Studio
|
||||
Follow the same build steps from the CMake section of [BUILD.md](BUILD.md), but pass a different generator to CMake.
|
||||
|
||||
|
|
|
@ -40,15 +40,17 @@ if (WIN32)
|
|||
endif ()
|
||||
message (WINDOW_SDK_PATH= ${WINDOW_SDK_PATH})
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH})
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||
# /wd4351 disables warning C4351: new behavior: elements of array will be default initialized
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351")
|
||||
# /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory.
|
||||
# Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables
|
||||
# TODO: Remove when building 64-bit.
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE")
|
||||
elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
#SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic")
|
||||
#SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter -ggdb")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter")
|
||||
if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb")
|
||||
endif ()
|
||||
endif(WIN32)
|
||||
|
||||
if ((NOT MSVC12) AND (NOT MSVC14))
|
||||
|
@ -176,8 +178,13 @@ option(GET_GVERB "Get Gverb library automatically as external project" 1)
|
|||
option(GET_SOXR "Get Soxr library automatically as external project" 1)
|
||||
option(GET_TBB "Get Threading Building Blocks library automatically as external project" 1)
|
||||
option(GET_LIBOVR "Get LibOVR library automatically as external project" 1)
|
||||
option(USE_NSIGHT "Attempt to find the nSight libraries" 1)
|
||||
option(GET_VHACD "Get V-HACD library automatically as external project" 1)
|
||||
option(GET_POLYVOX "Get polyvox library automatically as external project" 1)
|
||||
option(GET_OPENVR "Get OpenVR library automatically as external project" 1)
|
||||
option(GET_BOOSTCONFIG "Get Boost-config library automatically as external project" 1)
|
||||
option(GET_OGLPLUS "Get OGLplus library automatically as external project" 1)
|
||||
|
||||
option(USE_NSIGHT "Attempt to find the nSight libraries" 1)
|
||||
|
||||
if (WIN32)
|
||||
option(GET_GLEW "Get GLEW library automatically as external project" 1)
|
||||
|
|
|
@ -73,17 +73,6 @@ void Agent::readPendingDatagrams() {
|
|||
}
|
||||
}
|
||||
|
||||
} else if (datagramPacketType == PacketTypeEntityAddResponse) {
|
||||
// this will keep creatorTokenIDs to IDs mapped correctly
|
||||
EntityItemID::handleAddEntityResponse(receivedPacket);
|
||||
|
||||
// also give our local entity tree a chance to remap any internal locally created entities
|
||||
_entityViewer.getTree()->handleAddEntityResponse(receivedPacket);
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
sourceNode->setLastHeardMicrostamp(usecTimestampNow());
|
||||
|
||||
} else if (datagramPacketType == PacketTypeOctreeStats
|
||||
|| datagramPacketType == PacketTypeEntityData
|
||||
|| datagramPacketType == PacketTypeEntityErase
|
||||
|
@ -224,5 +213,7 @@ void Agent::run() {
|
|||
|
||||
void Agent::aboutToFinish() {
|
||||
_scriptEngine.stop();
|
||||
NetworkAccessManager::getInstance().clearAccessCache();
|
||||
|
||||
// our entity tree is going to go away so tell that to the EntityScriptingInterface
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(NULL);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
#include <QSharedMemory>
|
||||
|
@ -18,6 +20,7 @@
|
|||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <Assignment.h>
|
||||
#include <AvatarHashMap.h>
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <LogHandler.h>
|
||||
#include <LogUtils.h>
|
||||
|
@ -29,31 +32,30 @@
|
|||
#include <SoundCache.h>
|
||||
|
||||
#include "AssignmentFactory.h"
|
||||
#include "AssignmentThread.h"
|
||||
|
||||
#include "AssignmentClient.h"
|
||||
|
||||
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
|
||||
const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000;
|
||||
|
||||
SharedAssignmentPointer AssignmentClient::_currentAssignment;
|
||||
|
||||
int hifiSockAddrMeta = qRegisterMetaType<HifiSockAddr>("HifiSockAddr");
|
||||
|
||||
AssignmentClient::AssignmentClient(int ppid, Assignment::Type requestAssignmentType, QString assignmentPool,
|
||||
QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort) :
|
||||
_assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME),
|
||||
_localASPortSharedMem(NULL),
|
||||
_localACMPortSharedMem(NULL)
|
||||
AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QString assignmentPool,
|
||||
QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort,
|
||||
quint16 assignmentMonitorPort) :
|
||||
_assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME)
|
||||
{
|
||||
LogUtils::init();
|
||||
|
||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
|
||||
|
||||
// create a NodeList as an unassigned client
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Unassigned);
|
||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Unassigned); // Order is important
|
||||
|
||||
auto animationCache = DependencyManager::set<AnimationCache>();
|
||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>();
|
||||
|
||||
// make up a uuid for this child so the parent can tell us apart. This id will be changed
|
||||
|
@ -73,18 +75,18 @@ AssignmentClient::AssignmentClient(int ppid, Assignment::Type requestAssignmentT
|
|||
qDebug() << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID);
|
||||
_requestAssignment.setWalletUUID(walletUUID);
|
||||
}
|
||||
|
||||
|
||||
// check for an overriden assignment server hostname
|
||||
if (assignmentServerHostname != "") {
|
||||
// change the hostname for our assignment server
|
||||
_assignmentServerHostname = assignmentServerHostname;
|
||||
}
|
||||
|
||||
|
||||
_assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerPort, true);
|
||||
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
|
||||
|
||||
qDebug() << "Assignment server socket is" << _assignmentServerSocket;
|
||||
|
||||
|
||||
// call a timer function every ASSIGNMENT_REQUEST_INTERVAL_MSECS to ask for assignment, if required
|
||||
qDebug() << "Waiting for assignment -" << _requestAssignment;
|
||||
|
||||
|
@ -101,32 +103,57 @@ AssignmentClient::AssignmentClient(int ppid, Assignment::Type requestAssignmentT
|
|||
// connections to AccountManager for authentication
|
||||
connect(&AccountManager::getInstance(), &AccountManager::authRequired,
|
||||
this, &AssignmentClient::handleAuthenticationRequest);
|
||||
|
||||
|
||||
// Create Singleton objects on main thread
|
||||
NetworkAccessManager::getInstance();
|
||||
|
||||
// Hook up a timer to send this child's status to the Monitor once per second
|
||||
setUpStatsToMonitor(ppid);
|
||||
// did we get an assignment-client monitor port?
|
||||
if (assignmentMonitorPort > 0) {
|
||||
_assignmentClientMonitorSocket = HifiSockAddr(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME, assignmentMonitorPort);
|
||||
|
||||
qDebug() << "Assignment-client monitor socket is" << _assignmentClientMonitorSocket;
|
||||
|
||||
// Hook up a timer to send this child's status to the Monitor once per second
|
||||
setUpStatsToMonitor();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AssignmentClient::stopAssignmentClient() {
|
||||
qDebug() << "Exiting.";
|
||||
qDebug() << "Forced stop of assignment-client.";
|
||||
|
||||
_requestTimer.stop();
|
||||
_statsTimerACM.stop();
|
||||
QCoreApplication::quit();
|
||||
|
||||
if (_currentAssignment) {
|
||||
// grab the thread for the current assignment
|
||||
QThread* currentAssignmentThread = _currentAssignment->thread();
|
||||
|
||||
// ask the current assignment to stop
|
||||
QMetaObject::invokeMethod(_currentAssignment, "stop", Qt::BlockingQueuedConnection);
|
||||
|
||||
// ask the current assignment to delete itself on its thread
|
||||
_currentAssignment->deleteLater();
|
||||
|
||||
// when this thread is destroyed we don't need to run our assignment complete method
|
||||
disconnect(currentAssignmentThread, &QThread::destroyed, this, &AssignmentClient::assignmentCompleted);
|
||||
|
||||
// wait on the thread from that assignment - it will be gone once the current assignment deletes
|
||||
currentAssignmentThread->quit();
|
||||
currentAssignmentThread->wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AssignmentClient::setUpStatsToMonitor(int ppid) {
|
||||
// Figure out the address to send out stats to
|
||||
quint16 localMonitorServerPort = DEFAULT_ASSIGNMENT_CLIENT_MONITOR_PORT;
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
void AssignmentClient::aboutToQuit() {
|
||||
stopAssignmentClient();
|
||||
|
||||
nodeList->getLocalServerPortFromSharedMemory(QString(ASSIGNMENT_CLIENT_MONITOR_LOCAL_PORT_SMEM_KEY) + "-" +
|
||||
QString::number(ppid), _localACMPortSharedMem, localMonitorServerPort);
|
||||
_assignmentClientMonitorSocket = HifiSockAddr(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME, localMonitorServerPort, true);
|
||||
// clear the log handler so that Qt doesn't call the destructor on LogHandler
|
||||
qInstallMessageHandler(0);
|
||||
}
|
||||
|
||||
|
||||
void AssignmentClient::setUpStatsToMonitor() {
|
||||
// send a stats packet every 1 seconds
|
||||
connect(&_statsTimerACM, &QTimer::timeout, this, &AssignmentClient::sendStatsPacketToACM);
|
||||
_statsTimerACM.start(1000);
|
||||
|
@ -147,24 +174,27 @@ void AssignmentClient::sendStatsPacketToACM() {
|
|||
|
||||
void AssignmentClient::sendAssignmentRequest() {
|
||||
if (!_currentAssignment) {
|
||||
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
||||
if (_assignmentServerHostname == "localhost") {
|
||||
// we want to check again for the local domain-server port in case the DS has restarted
|
||||
quint16 localAssignmentServerPort;
|
||||
if (nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, _localASPortSharedMem,
|
||||
localAssignmentServerPort)) {
|
||||
if (nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, localAssignmentServerPort)) {
|
||||
if (localAssignmentServerPort != _assignmentServerSocket.getPort()) {
|
||||
qDebug() << "Port for local assignment server read from shared memory is"
|
||||
<< localAssignmentServerPort;
|
||||
|
||||
|
||||
_assignmentServerSocket.setPort(localAssignmentServerPort);
|
||||
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Failed to read local assignment server port from shared memory"
|
||||
<< "- will send assignment request to previous assignment server socket.";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
nodeList->sendAssignment(_requestAssignment);
|
||||
}
|
||||
}
|
||||
|
@ -182,8 +212,11 @@ void AssignmentClient::readPendingDatagrams() {
|
|||
|
||||
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
||||
if (packetTypeForPacket(receivedPacket) == PacketTypeCreateAssignment) {
|
||||
|
||||
qDebug() << "Received a PacketTypeCreateAssignment - attempting to unpack.";
|
||||
|
||||
// construct the deployed assignment from the packet data
|
||||
_currentAssignment = SharedAssignmentPointer(AssignmentFactory::unpackAssignment(receivedPacket));
|
||||
_currentAssignment = AssignmentFactory::unpackAssignment(receivedPacket);
|
||||
|
||||
if (_currentAssignment) {
|
||||
qDebug() << "Received an assignment -" << *_currentAssignment;
|
||||
|
@ -196,14 +229,26 @@ void AssignmentClient::readPendingDatagrams() {
|
|||
qDebug() << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString();
|
||||
|
||||
// start the deployed assignment
|
||||
AssignmentThread* workerThread = new AssignmentThread(_currentAssignment, this);
|
||||
QThread* workerThread = new QThread;
|
||||
workerThread->setObjectName("ThreadedAssignment Worker");
|
||||
|
||||
connect(workerThread, &QThread::started, _currentAssignment.data(), &ThreadedAssignment::run);
|
||||
connect(_currentAssignment.data(), &ThreadedAssignment::finished, workerThread, &QThread::quit);
|
||||
connect(_currentAssignment.data(), &ThreadedAssignment::finished,
|
||||
this, &AssignmentClient::assignmentCompleted);
|
||||
|
||||
// Once the ThreadedAssignment says it is finished - we ask it to deleteLater
|
||||
// This is a queued connection so that it is put into the event loop to be processed by the worker
|
||||
// thread when it is ready.
|
||||
connect(_currentAssignment.data(), &ThreadedAssignment::finished, _currentAssignment.data(),
|
||||
&ThreadedAssignment::deleteLater, Qt::QueuedConnection);
|
||||
|
||||
// once it is deleted, we quit the worker thread
|
||||
connect(_currentAssignment.data(), &ThreadedAssignment::destroyed, workerThread, &QThread::quit);
|
||||
|
||||
// have the worker thread remove itself once it is done
|
||||
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
|
||||
|
||||
// once the worker thread says it is done, we consider the assignment completed
|
||||
connect(workerThread, &QThread::destroyed, this, &AssignmentClient::assignmentCompleted);
|
||||
|
||||
_currentAssignment->moveToThread(workerThread);
|
||||
|
||||
// move the NodeList to the thread used for the _current assignment
|
||||
|
@ -222,8 +267,9 @@ void AssignmentClient::readPendingDatagrams() {
|
|||
} else if (packetTypeForPacket(receivedPacket) == PacketTypeStopNode) {
|
||||
if (senderSockAddr.getAddress() == QHostAddress::LocalHost ||
|
||||
senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) {
|
||||
qDebug() << "Network told me to exit.";
|
||||
emit stopAssignmentClient();
|
||||
qDebug() << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketTypeStopNode.";
|
||||
|
||||
QCoreApplication::quit();
|
||||
} else {
|
||||
qDebug() << "Got a stop packet from other than localhost.";
|
||||
}
|
||||
|
@ -260,21 +306,23 @@ void AssignmentClient::handleAuthenticationRequest() {
|
|||
}
|
||||
|
||||
void AssignmentClient::assignmentCompleted() {
|
||||
|
||||
// we expect that to be here the previous assignment has completely cleaned up
|
||||
assert(_currentAssignment.isNull());
|
||||
|
||||
// reset our current assignment pointer to NULL now that it has been deleted
|
||||
_currentAssignment = NULL;
|
||||
|
||||
// reset the logging target to the the CHILD_TARGET_NAME
|
||||
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
|
||||
|
||||
qDebug("Assignment finished or never started - waiting for new assignment.");
|
||||
qDebug() << "Assignment finished or never started - waiting for new assignment.";
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// have us handle incoming NodeList datagrams again
|
||||
disconnect(&nodeList->getNodeSocket(), 0, _currentAssignment.data(), 0);
|
||||
// have us handle incoming NodeList datagrams again, and make sure our ThreadedAssignment isn't handling them
|
||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams);
|
||||
|
||||
// clear our current assignment shared pointer now that we're done with it
|
||||
// if the assignment thread is still around it has its own shared pointer to the assignment
|
||||
_currentAssignment.clear();
|
||||
|
||||
// reset our NodeList by switching back to unassigned and clearing the list
|
||||
nodeList->setOwnerType(NodeType::Unassigned);
|
||||
nodeList->reset();
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_AssignmentClient_h
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QPointer>
|
||||
|
||||
#include "ThreadedAssignment.h"
|
||||
|
||||
|
@ -22,10 +23,9 @@ class AssignmentClient : public QObject {
|
|||
Q_OBJECT
|
||||
public:
|
||||
|
||||
AssignmentClient(int ppid, Assignment::Type requestAssignmentType, QString assignmentPool,
|
||||
QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort);
|
||||
static const SharedAssignmentPointer& getCurrentAssignment() { return _currentAssignment; }
|
||||
|
||||
AssignmentClient(Assignment::Type requestAssignmentType, QString assignmentPool,
|
||||
QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort,
|
||||
quint16 assignmentMonitorPort);
|
||||
private slots:
|
||||
void sendAssignmentRequest();
|
||||
void readPendingDatagrams();
|
||||
|
@ -34,14 +34,16 @@ private slots:
|
|||
void sendStatsPacketToACM();
|
||||
void stopAssignmentClient();
|
||||
|
||||
public slots:
|
||||
void aboutToQuit();
|
||||
|
||||
private:
|
||||
void setUpStatsToMonitor(int ppid);
|
||||
void setUpStatsToMonitor();
|
||||
|
||||
Assignment _requestAssignment;
|
||||
static SharedAssignmentPointer _currentAssignment;
|
||||
QPointer<ThreadedAssignment> _currentAssignment;
|
||||
QString _assignmentServerHostname;
|
||||
HifiSockAddr _assignmentServerSocket;
|
||||
QSharedMemory* _localASPortSharedMem; // memory shared with domain server
|
||||
QSharedMemory* _localACMPortSharedMem; // memory shared with assignment client monitor
|
||||
QTimer _requestTimer; // timer for requesting and assignment
|
||||
QTimer _statsTimerACM; // timer for sending stats to assignment client monitor
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QThread>
|
||||
|
||||
#include <LogHandler.h>
|
||||
#include <SharedUtil.h>
|
||||
|
@ -78,9 +79,8 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
const QCommandLineOption maxChildsOption(ASSIGNMENT_MAX_FORKS_OPTION, "maximum number of children", "child-count");
|
||||
parser.addOption(maxChildsOption);
|
||||
|
||||
const QCommandLineOption ppidOption(PARENT_PID_OPTION, "parent's process id", "pid");
|
||||
parser.addOption(ppidOption);
|
||||
|
||||
const QCommandLineOption monitorPortOption(ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION, "assignment-client monitor port", "port");
|
||||
parser.addOption(monitorPortOption);
|
||||
|
||||
if (!parser.parse(QCoreApplication::arguments())) {
|
||||
qCritical() << parser.errorText() << endl;
|
||||
|
@ -112,9 +112,9 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
maxForks = parser.value(maxChildsOption).toInt();
|
||||
}
|
||||
|
||||
int ppid = 0;
|
||||
if (parser.isSet(ppidOption)) {
|
||||
ppid = parser.value(ppidOption).toInt();
|
||||
unsigned short monitorPort = 0;
|
||||
if (parser.isSet(monitorPortOption)) {
|
||||
monitorPort = parser.value(monitorPortOption).toUShort();
|
||||
}
|
||||
|
||||
if (!numForks && minForks) {
|
||||
|
@ -161,12 +161,11 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
if (argumentVariantMap.contains(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION)) {
|
||||
assignmentServerPort = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toString().toUInt();
|
||||
}
|
||||
|
||||
if (parser.isSet(assignmentServerPortOption)) {
|
||||
assignmentServerPort = parser.value(assignmentServerPortOption).toInt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (parser.isSet(numChildsOption)) {
|
||||
if (minForks && minForks > numForks) {
|
||||
qCritical() << "--min can't be more than -n";
|
||||
|
@ -180,14 +179,22 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
}
|
||||
}
|
||||
|
||||
QThread::currentThread()->setObjectName("main thread");
|
||||
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
|
||||
if (numForks || minForks || maxForks) {
|
||||
AssignmentClientMonitor monitor(numForks, minForks, maxForks, requestAssignmentType, assignmentPool,
|
||||
walletUUID, assignmentServerHostname, assignmentServerPort);
|
||||
exec();
|
||||
AssignmentClientMonitor* monitor = new AssignmentClientMonitor(numForks, minForks, maxForks,
|
||||
requestAssignmentType, assignmentPool,
|
||||
walletUUID, assignmentServerHostname,
|
||||
assignmentServerPort);
|
||||
monitor->setParent(this);
|
||||
connect(this, &QCoreApplication::aboutToQuit, monitor, &AssignmentClientMonitor::aboutToQuit);
|
||||
} else {
|
||||
AssignmentClient client(ppid, requestAssignmentType, assignmentPool,
|
||||
walletUUID, assignmentServerHostname, assignmentServerPort);
|
||||
exec();
|
||||
AssignmentClient* client = new AssignmentClient(requestAssignmentType, assignmentPool,
|
||||
walletUUID, assignmentServerHostname,
|
||||
assignmentServerPort, monitorPort);
|
||||
client->setParent(this);
|
||||
connect(this, &QCoreApplication::aboutToQuit, client, &AssignmentClient::aboutToQuit);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ const QString CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION = "p";
|
|||
const QString ASSIGNMENT_NUM_FORKS_OPTION = "n";
|
||||
const QString ASSIGNMENT_MIN_FORKS_OPTION = "min";
|
||||
const QString ASSIGNMENT_MAX_FORKS_OPTION = "max";
|
||||
const QString PARENT_PID_OPTION = "ppid";
|
||||
const QString ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION = "monitor-port";
|
||||
|
||||
|
||||
class AssignmentClientApp : public QCoreApplication {
|
||||
|
|
|
@ -11,8 +11,9 @@
|
|||
|
||||
#include <signal.h>
|
||||
|
||||
#include <LogHandler.h>
|
||||
#include <AddressManager.h>
|
||||
#include <JSONBreakableMarshal.h>
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "AssignmentClientMonitor.h"
|
||||
#include "AssignmentClientApp.h"
|
||||
|
@ -22,6 +23,7 @@
|
|||
|
||||
|
||||
const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor";
|
||||
const int WAIT_FOR_CHILD_MSECS = 1000;
|
||||
|
||||
AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmentClientForks,
|
||||
const unsigned int minAssignmentClientForks,
|
||||
|
@ -39,22 +41,20 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen
|
|||
_assignmentServerPort(assignmentServerPort)
|
||||
{
|
||||
qDebug() << "_requestAssignmentType =" << _requestAssignmentType;
|
||||
|
||||
|
||||
// start the Logging class with the parent's target name
|
||||
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME);
|
||||
|
||||
// make sure we output process IDs for a monitor otherwise it's insane to parse
|
||||
LogHandler::getInstance().setShouldOutputPID(true);
|
||||
|
||||
// create a NodeList so we can receive stats from children
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
auto nodeList = DependencyManager::set<LimitedNodeList>(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_PORT);
|
||||
auto nodeList = DependencyManager::set<LimitedNodeList>();
|
||||
|
||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClientMonitor::readPendingDatagrams);
|
||||
|
||||
qint64 pid = QCoreApplication::applicationPid ();
|
||||
|
||||
nodeList->putLocalPortIntoSharedMemory(QString(ASSIGNMENT_CLIENT_MONITOR_LOCAL_PORT_SMEM_KEY) + "-" + QString::number(pid),
|
||||
this, nodeList->getNodeSocket().localPort());
|
||||
|
||||
// use QProcess to fork off a process for each of the child assignment clients
|
||||
for (unsigned int i = 0; i < _numAssignmentClientForks; i++) {
|
||||
spawnChildClient();
|
||||
|
@ -69,20 +69,60 @@ AssignmentClientMonitor::~AssignmentClientMonitor() {
|
|||
stopChildProcesses();
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::simultaneousWaitOnChildren(int waitMsecs) {
|
||||
QElapsedTimer waitTimer;
|
||||
waitTimer.start();
|
||||
|
||||
// loop as long as we still have processes around and we're inside the wait window
|
||||
while(_childProcesses.size() > 0 && !waitTimer.hasExpired(waitMsecs)) {
|
||||
// continue processing events so we can handle a process finishing up
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::childProcessFinished() {
|
||||
QProcess* childProcess = qobject_cast<QProcess*>(sender());
|
||||
qint64 processID = _childProcesses.key(childProcess);
|
||||
|
||||
if (processID > 0) {
|
||||
qDebug() << "Child process" << processID << "has finished. Removing from internal map.";
|
||||
_childProcesses.remove(processID);
|
||||
}
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::stopChildProcesses() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
qDebug() << "asking child" << node->getUUID() << "to exit.";
|
||||
node->activateLocalSocket();
|
||||
QByteArray diePacket = byteArrayWithPopulatedHeader(PacketTypeStopNode);
|
||||
nodeList->writeUnverifiedDatagram(diePacket, *node->getActiveSocket());
|
||||
});
|
||||
// ask child processes to terminate
|
||||
foreach(QProcess* childProcess, _childProcesses) {
|
||||
qDebug() << "Attempting to terminate child process" << childProcess->processId();
|
||||
childProcess->terminate();
|
||||
}
|
||||
|
||||
simultaneousWaitOnChildren(WAIT_FOR_CHILD_MSECS);
|
||||
|
||||
if (_childProcesses.size() > 0) {
|
||||
// ask even more firmly
|
||||
foreach(QProcess* childProcess, _childProcesses) {
|
||||
qDebug() << "Attempting to kill child process" << childProcess->processId();
|
||||
childProcess->kill();
|
||||
}
|
||||
|
||||
simultaneousWaitOnChildren(WAIT_FOR_CHILD_MSECS);
|
||||
}
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::aboutToQuit() {
|
||||
stopChildProcesses();
|
||||
|
||||
// clear the log handler so that Qt doesn't call the destructor on LogHandler
|
||||
qInstallMessageHandler(0);
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::spawnChildClient() {
|
||||
QProcess *assignmentClient = new QProcess(this);
|
||||
QProcess* assignmentClient = new QProcess(this);
|
||||
|
||||
|
||||
// unparse the parts of the command-line that the child cares about
|
||||
QStringList _childArguments;
|
||||
if (_assignmentPool != "") {
|
||||
|
@ -106,21 +146,23 @@ void AssignmentClientMonitor::spawnChildClient() {
|
|||
_childArguments.append(QString::number(_requestAssignmentType));
|
||||
}
|
||||
|
||||
// tell children which shared memory key to use
|
||||
qint64 pid = QCoreApplication::applicationPid ();
|
||||
_childArguments.append("--" + PARENT_PID_OPTION);
|
||||
_childArguments.append(QString::number(pid));
|
||||
// tell children which assignment monitor port to use
|
||||
// for now they simply talk to us on localhost
|
||||
_childArguments.append("--" + ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION);
|
||||
_childArguments.append(QString::number(DependencyManager::get<NodeList>()->getLocalSockAddr().getPort()));
|
||||
|
||||
// make sure that the output from the child process appears in our output
|
||||
assignmentClient->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
|
||||
assignmentClient->start(QCoreApplication::applicationFilePath(), _childArguments);
|
||||
|
||||
// make sure we hear that this process has finished when it does
|
||||
connect(assignmentClient, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(childProcessFinished()));
|
||||
|
||||
qDebug() << "Spawned a child client with PID" << assignmentClient->pid();
|
||||
_childProcesses.insert(assignmentClient->processId(), assignmentClient);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AssignmentClientMonitor::checkSpares() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
QUuid aSpareId = "";
|
||||
|
@ -152,7 +194,8 @@ void AssignmentClientMonitor::checkSpares() {
|
|||
qDebug() << "asking child" << aSpareId << "to exit.";
|
||||
SharedNodePointer childNode = nodeList->nodeWithUUID(aSpareId);
|
||||
childNode->activateLocalSocket();
|
||||
QByteArray diePacket = byteArrayWithPopulatedHeader(PacketTypeStopNode);
|
||||
|
||||
QByteArray diePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeStopNode);
|
||||
nodeList->writeUnverifiedDatagram(diePacket, childNode);
|
||||
}
|
||||
}
|
||||
|
@ -186,7 +229,7 @@ void AssignmentClientMonitor::readPendingDatagrams() {
|
|||
} else {
|
||||
// tell unknown assignment-client child to exit.
|
||||
qDebug() << "asking unknown child to exit.";
|
||||
QByteArray diePacket = byteArrayWithPopulatedHeader(PacketTypeStopNode);
|
||||
QByteArray diePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeStopNode);
|
||||
nodeList->writeUnverifiedDatagram(diePacket, senderSockAddr);
|
||||
}
|
||||
}
|
||||
|
@ -196,13 +239,9 @@ void AssignmentClientMonitor::readPendingDatagrams() {
|
|||
// update our records about how to reach this child
|
||||
matchingNode->setLocalSocket(senderSockAddr);
|
||||
|
||||
// push past the packet header
|
||||
QDataStream packetStream(receivedPacket);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
|
||||
// decode json
|
||||
QVariantMap unpackedVariantMap;
|
||||
packetStream >> unpackedVariantMap;
|
||||
QJsonObject unpackedStatsJSON = QJsonObject::fromVariantMap(unpackedVariantMap);
|
||||
QVariantMap packetVariantMap =
|
||||
JSONBreakableMarshal::fromStringBuffer(receivedPacket.mid(numBytesForPacketHeader(receivedPacket)));
|
||||
QJsonObject unpackedStatsJSON = QJsonObject::fromVariantMap(packetVariantMap);
|
||||
|
||||
// get child's assignment type out of the decoded json
|
||||
QString childType = unpackedStatsJSON["assignment_type"].toString();
|
||||
|
|
|
@ -32,14 +32,20 @@ public:
|
|||
QString assignmentPool, QUuid walletUUID, QString assignmentServerHostname,
|
||||
quint16 assignmentServerPort);
|
||||
~AssignmentClientMonitor();
|
||||
|
||||
|
||||
void stopChildProcesses();
|
||||
private slots:
|
||||
void readPendingDatagrams();
|
||||
void checkSpares();
|
||||
void childProcessFinished();
|
||||
|
||||
public slots:
|
||||
void aboutToQuit();
|
||||
|
||||
private:
|
||||
void spawnChildClient();
|
||||
void simultaneousWaitOnChildren(int waitMsecs);
|
||||
|
||||
QTimer _checkSparesTimer; // every few seconds see if it need fewer or more spare children
|
||||
|
||||
const unsigned int _numAssignmentClientForks;
|
||||
|
@ -52,6 +58,7 @@ private:
|
|||
QString _assignmentServerHostname;
|
||||
quint16 _assignmentServerPort;
|
||||
|
||||
QMap<qint64, QProcess*> _childProcesses;
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentClientMonitor_h
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// AssignmentThread.cpp
|
||||
// assignment-client/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014.
|
||||
// Copyright 2014 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
|
||||
//
|
||||
|
||||
#include "AssignmentThread.h"
|
||||
|
||||
AssignmentThread::AssignmentThread(const SharedAssignmentPointer& assignment, QObject* parent) :
|
||||
QThread(parent),
|
||||
_assignment(assignment)
|
||||
{
|
||||
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
//
|
||||
// AssignmentThread.h
|
||||
// assignment-client/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014.
|
||||
// Copyright 2014 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
|
||||
//
|
||||
|
||||
#ifndef hifi_AssignmentThread_h
|
||||
#define hifi_AssignmentThread_h
|
||||
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
class AssignmentThread : public QThread {
|
||||
public:
|
||||
AssignmentThread(const SharedAssignmentPointer& assignment, QObject* parent);
|
||||
private:
|
||||
SharedAssignmentPointer _assignment;
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentThread_h
|
File diff suppressed because it is too large
Load diff
|
@ -29,49 +29,46 @@ class AudioMixer : public ThreadedAssignment {
|
|||
Q_OBJECT
|
||||
public:
|
||||
AudioMixer(const QByteArray& packet);
|
||||
|
||||
void deleteLater() { qDebug() << "DELETE LATER CALLED?"; QObject::deleteLater(); }
|
||||
public slots:
|
||||
/// threaded run of assignment
|
||||
void run();
|
||||
|
||||
|
||||
void readPendingDatagrams() { }; // this will not be called since our datagram processing thread will handle
|
||||
void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
|
||||
void sendStatsPacket();
|
||||
|
||||
static const InboundAudioStream::Settings& getStreamSettings() { return _streamSettings; }
|
||||
|
||||
|
||||
private:
|
||||
/// adds one stream to the mix for a listening node
|
||||
int addStreamToMixForListeningNodeWithStream(AudioMixerClientData* listenerNodeData,
|
||||
const QUuid& streamUUID,
|
||||
PositionalAudioStream* streamToAdd,
|
||||
AvatarAudioStream* listeningNodeStream);
|
||||
|
||||
|
||||
/// prepares and sends a mix to one Node
|
||||
int prepareMixForListeningNode(Node* node);
|
||||
|
||||
|
||||
/// Send Audio Environment packet for a single node
|
||||
void sendAudioEnvironmentPacket(SharedNodePointer node);
|
||||
|
||||
// used on a per stream basis to run the filter on before mixing, large enough to handle the historical
|
||||
// data from a phase delay as well as an entire network buffer
|
||||
int16_t _preMixSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)];
|
||||
|
||||
|
||||
// client samples capacity is larger than what will be sent to optimize mixing
|
||||
// we are MMX adding 4 samples at a time so we need client samples to have an extra 4
|
||||
int16_t _mixSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)];
|
||||
|
||||
void perSecondActions();
|
||||
|
||||
|
||||
bool shouldMute(float quietestFrame);
|
||||
|
||||
QString getReadPendingDatagramsCallsPerSecondsStatsString() const;
|
||||
QString getReadPendingDatagramsPacketsPerCallStatsString() const;
|
||||
QString getReadPendingDatagramsTimeStatsString() const;
|
||||
QString getReadPendingDatagramsHashMatchTimeStatsString() const;
|
||||
|
||||
void parseSettingsObject(const QJsonObject& settingsObject);
|
||||
|
||||
|
||||
float _trailingSleepRatio;
|
||||
float _minAudibilityThreshold;
|
||||
float _performanceThrottlingRatio;
|
||||
|
@ -80,7 +77,7 @@ private:
|
|||
int _numStatFrames;
|
||||
int _sumListeners;
|
||||
int _sumMixes;
|
||||
|
||||
|
||||
QHash<QString, AABox> _audioZones;
|
||||
struct ZonesSettings {
|
||||
QString source;
|
||||
|
@ -94,12 +91,12 @@ private:
|
|||
float wetLevel;
|
||||
};
|
||||
QVector<ReverbSettings> _zoneReverbSettings;
|
||||
|
||||
|
||||
static InboundAudioStream::Settings _streamSettings;
|
||||
|
||||
static bool _printStreamStats;
|
||||
static bool _enableFilter;
|
||||
|
||||
|
||||
quint64 _lastPerSecondCallbackTime;
|
||||
|
||||
bool _sendAudioStreamStats;
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QJsonArray>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <UUID.h>
|
||||
|
@ -156,7 +157,7 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
|
|||
quint8 appendFlag = 0;
|
||||
|
||||
// pack header
|
||||
int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats);
|
||||
int numBytesPacketHeader = nodeList->populatePacketHeader(packet, PacketTypeAudioStreamStats);
|
||||
char* headerEndAt = packet + numBytesPacketHeader;
|
||||
|
||||
// calculate how many stream stat structs we can fit in each packet
|
||||
|
@ -196,70 +197,86 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
|
|||
}
|
||||
}
|
||||
|
||||
QString AudioMixerClientData::getAudioStreamStatsString() const {
|
||||
QString result;
|
||||
QJsonObject AudioMixerClientData::getAudioStreamStats() const {
|
||||
QJsonObject result;
|
||||
|
||||
QJsonObject downstreamStats;
|
||||
AudioStreamStats streamStats = _downstreamAudioStreamStats;
|
||||
result += "DOWNSTREAM.desired:" + QString::number(streamStats._desiredJitterBufferFrames)
|
||||
+ " available_avg_10s:" + QString::number(streamStats._framesAvailableAverage)
|
||||
+ " available:" + QString::number(streamStats._framesAvailable)
|
||||
+ " starves:" + QString::number(streamStats._starveCount)
|
||||
+ " not_mixed:" + QString::number(streamStats._consecutiveNotMixedCount)
|
||||
+ " overflows:" + QString::number(streamStats._overflowCount)
|
||||
+ " silents_dropped: ?"
|
||||
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " min_gap:" + formatUsecTime(streamStats._timeGapMin)
|
||||
+ " max_gap:" + formatUsecTime(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + formatUsecTime(streamStats._timeGapAverage)
|
||||
+ " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
downstreamStats["desired"] = streamStats._desiredJitterBufferFrames;
|
||||
downstreamStats["available_avg_10s"] = streamStats._framesAvailableAverage;
|
||||
downstreamStats["available"] = (double) streamStats._framesAvailable;
|
||||
downstreamStats["starves"] = (double) streamStats._starveCount;
|
||||
downstreamStats["not_mixed"] = (double) streamStats._consecutiveNotMixedCount;
|
||||
downstreamStats["overflows"] = (double) streamStats._overflowCount;
|
||||
downstreamStats["lost%"] = streamStats._packetStreamStats.getLostRate() * 100.0f;
|
||||
downstreamStats["lost%_30s"] = streamStats._packetStreamWindowStats.getLostRate() * 100.0f;
|
||||
downstreamStats["min_gap"] = formatUsecTime(streamStats._timeGapMin);
|
||||
downstreamStats["max_gap"] = formatUsecTime(streamStats._timeGapMax);
|
||||
downstreamStats["avg_gap"] = formatUsecTime(streamStats._timeGapAverage);
|
||||
downstreamStats["min_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMin);
|
||||
downstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
|
||||
downstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
|
||||
result["downstream"] = downstreamStats;
|
||||
|
||||
AvatarAudioStream* avatarAudioStream = getAvatarAudioStream();
|
||||
|
||||
if (avatarAudioStream) {
|
||||
QJsonObject upstreamStats;
|
||||
|
||||
AudioStreamStats streamStats = avatarAudioStream->getAudioStreamStats();
|
||||
result += " UPSTREAM.mic.desired:" + QString::number(streamStats._desiredJitterBufferFrames)
|
||||
+ " desired_calc:" + QString::number(avatarAudioStream->getCalculatedJitterBufferFrames())
|
||||
+ " available_avg_10s:" + QString::number(streamStats._framesAvailableAverage)
|
||||
+ " available:" + QString::number(streamStats._framesAvailable)
|
||||
+ " starves:" + QString::number(streamStats._starveCount)
|
||||
+ " not_mixed:" + QString::number(streamStats._consecutiveNotMixedCount)
|
||||
+ " overflows:" + QString::number(streamStats._overflowCount)
|
||||
+ " silents_dropped:" + QString::number(streamStats._framesDropped)
|
||||
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " min_gap:" + formatUsecTime(streamStats._timeGapMin)
|
||||
+ " max_gap:" + formatUsecTime(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + formatUsecTime(streamStats._timeGapAverage)
|
||||
+ " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
upstreamStats["mic.desired"] = streamStats._desiredJitterBufferFrames;
|
||||
upstreamStats["desired_calc"] = avatarAudioStream->getCalculatedJitterBufferFrames();
|
||||
upstreamStats["available_avg_10s"] = streamStats._framesAvailableAverage;
|
||||
upstreamStats["available"] = (double) streamStats._framesAvailable;
|
||||
upstreamStats["starves"] = (double) streamStats._starveCount;
|
||||
upstreamStats["not_mixed"] = (double) streamStats._consecutiveNotMixedCount;
|
||||
upstreamStats["overflows"] = (double) streamStats._overflowCount;
|
||||
upstreamStats["silents_dropped"] = (double) streamStats._framesDropped;
|
||||
upstreamStats["lost%"] = streamStats._packetStreamStats.getLostRate() * 100.0f;
|
||||
upstreamStats["lost%_30s"] = streamStats._packetStreamWindowStats.getLostRate() * 100.0f;
|
||||
upstreamStats["min_gap"] = formatUsecTime(streamStats._timeGapMin);
|
||||
upstreamStats["max_gap"] = formatUsecTime(streamStats._timeGapMax);
|
||||
upstreamStats["avg_gap"] = formatUsecTime(streamStats._timeGapAverage);
|
||||
upstreamStats["min_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMin);
|
||||
upstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
|
||||
upstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
|
||||
result["upstream"] = upstreamStats;
|
||||
} else {
|
||||
result = "mic unknown";
|
||||
result["upstream"] = "mic unknown";
|
||||
}
|
||||
|
||||
QHash<QUuid, PositionalAudioStream*>::ConstIterator i;
|
||||
QJsonArray injectorArray;
|
||||
for (i = _audioStreams.constBegin(); i != _audioStreams.constEnd(); i++) {
|
||||
if (i.value()->getType() == PositionalAudioStream::Injector) {
|
||||
QJsonObject upstreamStats;
|
||||
|
||||
AudioStreamStats streamStats = i.value()->getAudioStreamStats();
|
||||
result += " UPSTREAM.inj.desired:" + QString::number(streamStats._desiredJitterBufferFrames)
|
||||
+ " desired_calc:" + QString::number(i.value()->getCalculatedJitterBufferFrames())
|
||||
+ " available_avg_10s:" + QString::number(streamStats._framesAvailableAverage)
|
||||
+ " available:" + QString::number(streamStats._framesAvailable)
|
||||
+ " starves:" + QString::number(streamStats._starveCount)
|
||||
+ " not_mixed:" + QString::number(streamStats._consecutiveNotMixedCount)
|
||||
+ " overflows:" + QString::number(streamStats._overflowCount)
|
||||
+ " silents_dropped:" + QString::number(streamStats._framesDropped)
|
||||
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " min_gap:" + formatUsecTime(streamStats._timeGapMin)
|
||||
+ " max_gap:" + formatUsecTime(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + formatUsecTime(streamStats._timeGapAverage)
|
||||
+ " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
upstreamStats["inj.desired"] = streamStats._desiredJitterBufferFrames;
|
||||
upstreamStats["desired_calc"] = i.value()->getCalculatedJitterBufferFrames();
|
||||
upstreamStats["available_avg_10s"] = streamStats._framesAvailableAverage;
|
||||
upstreamStats["available"] = (double) streamStats._framesAvailable;
|
||||
upstreamStats["starves"] = (double) streamStats._starveCount;
|
||||
upstreamStats["not_mixed"] = (double) streamStats._consecutiveNotMixedCount;
|
||||
upstreamStats["overflows"] = (double) streamStats._overflowCount;
|
||||
upstreamStats["silents_dropped"] = (double) streamStats._framesDropped;
|
||||
upstreamStats["lost%"] = streamStats._packetStreamStats.getLostRate() * 100.0f;
|
||||
upstreamStats["lost%_30s"] = streamStats._packetStreamWindowStats.getLostRate() * 100.0f;
|
||||
upstreamStats["min_gap"] = formatUsecTime(streamStats._timeGapMin);
|
||||
upstreamStats["max_gap"] = formatUsecTime(streamStats._timeGapMax);
|
||||
upstreamStats["avg_gap"] = formatUsecTime(streamStats._timeGapAverage);
|
||||
upstreamStats["min_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMin);
|
||||
upstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
|
||||
upstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
|
||||
injectorArray.push_back(upstreamStats);
|
||||
}
|
||||
}
|
||||
|
||||
result["injectors"] = injectorArray;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#ifndef hifi_AudioMixerClientData_h
|
||||
#define hifi_AudioMixerClientData_h
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
|
||||
#include <AABox.h>
|
||||
#include <AudioFormat.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||
#include <AudioBuffer.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||
|
@ -46,7 +48,7 @@ public:
|
|||
|
||||
void removeDeadInjectedStreams();
|
||||
|
||||
QString getAudioStreamStatsString() const;
|
||||
QJsonObject getAudioStreamStats() const;
|
||||
|
||||
void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode);
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <cfloat>
|
||||
#include <random>
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QJsonObject>
|
||||
|
@ -27,7 +30,8 @@
|
|||
|
||||
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
||||
|
||||
const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / 60.0f) * 1000;
|
||||
const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 60;
|
||||
const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float) AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) * 1000;
|
||||
|
||||
AvatarMixer::AvatarMixer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
|
@ -45,16 +49,14 @@ AvatarMixer::AvatarMixer(const QByteArray& packet) :
|
|||
}
|
||||
|
||||
AvatarMixer::~AvatarMixer() {
|
||||
if (_broadcastTimer) {
|
||||
_broadcastTimer->deleteLater();
|
||||
}
|
||||
|
||||
_broadcastThread.quit();
|
||||
_broadcastThread.wait();
|
||||
}
|
||||
|
||||
void attachAvatarDataToNode(Node* newNode) {
|
||||
if (!newNode->getLinkedData()) {
|
||||
newNode->setLinkedData(new AvatarMixerClientData());
|
||||
}
|
||||
}
|
||||
|
||||
const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 300.0f;
|
||||
|
||||
// NOTE: some additional optimizations to consider.
|
||||
|
@ -76,7 +78,12 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
|
||||
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
|
||||
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
|
||||
|
||||
|
||||
// NOTE: The following code calculates the _performanceThrottlingRatio based on how much the avatar-mixer was
|
||||
// able to sleep. This will eventually be used to ask for an additional avatar-mixer to help out. Currently the value
|
||||
// is unused as it is assumed this should not be hit before the avatar-mixer hits the desired bandwidth limit per client.
|
||||
// It is reported in the domain-server stats for the avatar-mixer.
|
||||
|
||||
_trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio)
|
||||
+ (idleTime * CURRENT_FRAME_RATIO / (float) AVATAR_DATA_SEND_INTERVAL_MSECS);
|
||||
|
||||
|
@ -85,14 +92,14 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
|
||||
if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) {
|
||||
if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) {
|
||||
// we're struggling - change our min required loudness to reduce some load
|
||||
// we're struggling - change our performance throttling ratio
|
||||
_performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio));
|
||||
|
||||
qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
hasRatioChanged = true;
|
||||
} else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) {
|
||||
// we've recovered and can back off the required loudness
|
||||
// we've recovered and can back off the performance throttling
|
||||
_performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF;
|
||||
|
||||
if (_performanceThrottlingRatio < 0) {
|
||||
|
@ -100,7 +107,7 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
}
|
||||
|
||||
qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
hasRatioChanged = true;
|
||||
}
|
||||
|
||||
|
@ -115,9 +122,13 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
|
||||
static QByteArray mixedAvatarByteArray;
|
||||
|
||||
int numPacketHeaderBytes = populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
int numPacketHeaderBytes = nodeList->populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData);
|
||||
|
||||
// setup for distributed random floating point values
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::uniform_real_distribution<float> distribution;
|
||||
|
||||
nodeList->eachMatchingNode(
|
||||
[&](const SharedNodePointer& node)->bool {
|
||||
|
@ -142,16 +153,71 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
|
||||
// reset packet pointers for this node
|
||||
mixedAvatarByteArray.resize(numPacketHeaderBytes);
|
||||
|
||||
|
||||
AvatarData& avatar = nodeData->getAvatar();
|
||||
glm::vec3 myPosition = avatar.getPosition();
|
||||
// TODO use this along with the distance in the calculation of whether to send an update
|
||||
// about a given otherNode to this node
|
||||
// FIXME does this mean we should sort the othernodes by distance before iterating
|
||||
// over them?
|
||||
// float outputBandwidth =
|
||||
node->getOutboundBandwidth();
|
||||
|
||||
// reset the internal state for correct random number distribution
|
||||
distribution.reset();
|
||||
|
||||
// reset the max distance for this frame
|
||||
float maxAvatarDistanceThisFrame = 0.0f;
|
||||
|
||||
// reset the number of sent avatars
|
||||
nodeData->resetNumAvatarsSentLastFrame();
|
||||
|
||||
// keep a counter of the number of considered avatars
|
||||
int numOtherAvatars = 0;
|
||||
|
||||
// keep track of outbound data rate specifically for avatar data
|
||||
int numAvatarDataBytes = 0;
|
||||
|
||||
// keep track of the number of other avatars held back in this frame
|
||||
int numAvatarsHeldBack = 0;
|
||||
|
||||
// keep track of the number of other avatar frames skipped
|
||||
int numAvatarsWithSkippedFrames = 0;
|
||||
|
||||
// use the data rate specifically for avatar data for FRD adjustment checks
|
||||
float avatarDataRateLastSecond = nodeData->getOutboundAvatarDataKbps();
|
||||
|
||||
// Check if it is time to adjust what we send this client based on the observed
|
||||
// bandwidth to this node. We do this once a second, which is also the window for
|
||||
// the bandwidth reported by node->getOutboundBandwidth();
|
||||
if (nodeData->getNumFramesSinceFRDAdjustment() > AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) {
|
||||
|
||||
const float FRD_ADJUSTMENT_ACCEPTABLE_RATIO = 0.8f;
|
||||
const float HYSTERISIS_GAP = (1 - FRD_ADJUSTMENT_ACCEPTABLE_RATIO);
|
||||
const float HYSTERISIS_MIDDLE_PERCENTAGE = (1 - (HYSTERISIS_GAP * 0.5f));
|
||||
|
||||
// get the current full rate distance so we can work with it
|
||||
float currentFullRateDistance = nodeData->getFullRateDistance();
|
||||
|
||||
if (avatarDataRateLastSecond > _maxKbpsPerNode) {
|
||||
|
||||
// is the FRD greater than the farthest avatar?
|
||||
// if so, before we calculate anything, set it to that distance
|
||||
currentFullRateDistance = std::min(currentFullRateDistance, nodeData->getMaxAvatarDistance());
|
||||
|
||||
// we're adjusting the full rate distance to target a bandwidth in the middle
|
||||
// of the hysterisis gap
|
||||
currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond;
|
||||
|
||||
nodeData->setFullRateDistance(currentFullRateDistance);
|
||||
nodeData->resetNumFramesSinceFRDAdjustment();
|
||||
} else if (currentFullRateDistance < nodeData->getMaxAvatarDistance()
|
||||
&& avatarDataRateLastSecond < _maxKbpsPerNode * FRD_ADJUSTMENT_ACCEPTABLE_RATIO) {
|
||||
// we are constrained AND we've recovered to below the acceptable ratio
|
||||
// lets adjust the full rate distance to target a bandwidth in the middle of the hyterisis gap
|
||||
currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond;
|
||||
|
||||
nodeData->setFullRateDistance(currentFullRateDistance);
|
||||
nodeData->resetNumFramesSinceFRDAdjustment();
|
||||
}
|
||||
} else {
|
||||
nodeData->incrementNumFramesSinceFRDAdjustment();
|
||||
}
|
||||
|
||||
// this is an AGENT we have received head data from
|
||||
// send back a packet with other active node data to this node
|
||||
nodeList->eachMatchingNode(
|
||||
|
@ -163,36 +229,73 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check throttling value
|
||||
if (!(_performanceThrottlingRatio == 0 || randFloat() < (1.0f - _performanceThrottlingRatio))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[&](const SharedNodePointer& otherNode) {
|
||||
++numOtherAvatars;
|
||||
|
||||
AvatarMixerClientData* otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
MutexTryLocker lock(otherNodeData->getMutex());
|
||||
if (!lock.isLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AvatarData& otherAvatar = otherNodeData->getAvatar();
|
||||
// Decide whether to send this avatar's data based on it's distance from us
|
||||
|
||||
// The full rate distance is the distance at which EVERY update will be sent for this avatar
|
||||
// at a distance of twice the full rate distance, there will be a 50% chance of sending this avatar's update
|
||||
const float FULL_RATE_DISTANCE = 2.0f;
|
||||
// at twice the full rate distance, there will be a 50% chance of sending this avatar's update
|
||||
glm::vec3 otherPosition = otherAvatar.getPosition();
|
||||
float distanceToAvatar = glm::length(myPosition - otherPosition);
|
||||
|
||||
if (!(distanceToAvatar == 0.0f || randFloat() < FULL_RATE_DISTANCE / distanceToAvatar)) {
|
||||
// potentially update the max full rate distance for this frame
|
||||
maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar);
|
||||
|
||||
if (distanceToAvatar != 0.0f
|
||||
&& distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PacketSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID());
|
||||
PacketSequenceNumber lastSeqFromSender = otherNode->getLastSequenceNumberForPacketType(PacketTypeAvatarData);
|
||||
|
||||
if (lastSeqToReceiver > lastSeqFromSender) {
|
||||
// Did we somehow get out of order packets from the sender?
|
||||
// We don't expect this to happen - in RELEASE we add this to a trackable stat
|
||||
// and in DEBUG we crash on the assert
|
||||
|
||||
otherNodeData->incrementNumOutOfOrderSends();
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// make sure we haven't already sent this data from this sender to this receiver
|
||||
// or that somehow we haven't sent
|
||||
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
|
||||
++numAvatarsHeldBack;
|
||||
return;
|
||||
} else if (lastSeqFromSender - lastSeqToReceiver > 1) {
|
||||
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening
|
||||
++numAvatarsWithSkippedFrames;
|
||||
}
|
||||
|
||||
// we're going to send this avatar
|
||||
|
||||
// increment the number of avatars sent to this reciever
|
||||
nodeData->incrementNumAvatarsSentLastFrame();
|
||||
|
||||
// set the last sent sequence number for this sender on the receiver
|
||||
nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
|
||||
otherNode->getLastSequenceNumberForPacketType(PacketTypeAvatarData));
|
||||
|
||||
QByteArray avatarByteArray;
|
||||
avatarByteArray.append(otherNode->getUUID().toRfc4122());
|
||||
avatarByteArray.append(otherAvatar.toByteArray());
|
||||
|
||||
if (avatarByteArray.size() + mixedAvatarByteArray.size() > MAX_PACKET_SIZE) {
|
||||
nodeList->writeDatagram(mixedAvatarByteArray, node);
|
||||
|
||||
numAvatarDataBytes += mixedAvatarByteArray.size();
|
||||
|
||||
// reset the packet
|
||||
mixedAvatarByteArray.resize(numPacketHeaderBytes);
|
||||
|
@ -212,9 +315,10 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
&& (forceSend
|
||||
|| otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp
|
||||
|| randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
|
||||
QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
|
||||
QByteArray billboardPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
|
||||
billboardPacket.append(otherNode->getUUID().toRfc4122());
|
||||
billboardPacket.append(otherNodeData->getAvatar().getBillboard());
|
||||
|
||||
nodeList->writeDatagram(billboardPacket, node);
|
||||
|
||||
++_sumBillboardPackets;
|
||||
|
@ -225,19 +329,36 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|
||||
|| randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
|
||||
|
||||
QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
|
||||
QByteArray identityPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
|
||||
|
||||
QByteArray individualData = otherNodeData->getAvatar().identityByteArray();
|
||||
individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122());
|
||||
identityPacket.append(individualData);
|
||||
|
||||
|
||||
nodeList->writeDatagram(identityPacket, node);
|
||||
|
||||
++_sumIdentityPackets;
|
||||
}
|
||||
});
|
||||
|
||||
// send the last packet
|
||||
nodeList->writeDatagram(mixedAvatarByteArray, node);
|
||||
});
|
||||
|
||||
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
||||
nodeData->recordSentAvatarData(numAvatarDataBytes + mixedAvatarByteArray.size());
|
||||
|
||||
// record the number of avatars held back this frame
|
||||
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
||||
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
||||
|
||||
if (numOtherAvatars == 0) {
|
||||
// update the full rate distance to FLOAT_MAX since we didn't have any other avatars to send
|
||||
nodeData->setMaxAvatarDistance(FLT_MAX);
|
||||
} else {
|
||||
nodeData->setMaxAvatarDistance(maxAvatarDistanceThisFrame);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
_lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
@ -245,13 +366,36 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
||||
if (killedNode->getType() == NodeType::Agent
|
||||
&& killedNode->getLinkedData()) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// this was an avatar we were sending to other people
|
||||
// send a kill packet for it to our other nodes
|
||||
QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
|
||||
QByteArray killPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
|
||||
killPacket += killedNode->getUUID().toRfc4122();
|
||||
|
||||
DependencyManager::get<NodeList>()->broadcastToNodes(killPacket,
|
||||
NodeSet() << NodeType::Agent);
|
||||
nodeList->broadcastToNodes(killPacket, NodeSet() << NodeType::Agent);
|
||||
|
||||
// we also want to remove sequence number data for this avatar on our other avatars
|
||||
// so invoke the appropriate method on the AvatarMixerClientData for other avatars
|
||||
nodeList->eachMatchingNode(
|
||||
[&](const SharedNodePointer& node)->bool {
|
||||
if (!node->getLinkedData()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node->getUUID() == killedNode->getUUID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[&](const SharedNodePointer& node) {
|
||||
QMetaObject::invokeMethod(node->getLinkedData(),
|
||||
"removeLastBroadcastSequenceNumber",
|
||||
Qt::AutoConnection,
|
||||
Q_ARG(const QUuid&, QUuid(killedNode->getUUID())));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,9 +469,38 @@ void AvatarMixer::sendStatsPacket() {
|
|||
|
||||
statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100;
|
||||
statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio;
|
||||
|
||||
QJsonObject avatarsObject;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
// add stats for each listerner
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
QJsonObject avatarStats;
|
||||
|
||||
const QString NODE_OUTBOUND_KBPS_STAT_KEY = "outbound_kbps";
|
||||
|
||||
// add the key to ask the domain-server for a username replacement, if it has it
|
||||
avatarStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID());
|
||||
avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundBandwidth();
|
||||
|
||||
AvatarMixerClientData* clientData = static_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
if (clientData) {
|
||||
MutexTryLocker lock(clientData->getMutex());
|
||||
if (lock.isLocked()) {
|
||||
clientData->loadJSONStats(avatarStats);
|
||||
|
||||
// add the diff between the full outbound bandwidth and the measured bandwidth for AvatarData send only
|
||||
avatarStats["delta_full_vs_avatar_data_kbps"] =
|
||||
avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY].toDouble() - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
avatarsObject[uuidStringWithoutCurlyBraces(node->getUUID())] = avatarStats;
|
||||
});
|
||||
|
||||
statsObject["avatars"] = avatarsObject;
|
||||
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
||||
|
||||
|
||||
_sumListeners = 0;
|
||||
_sumBillboardPackets = 0;
|
||||
_sumIdentityPackets = 0;
|
||||
|
@ -340,17 +513,54 @@ void AvatarMixer::run() {
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
nodeList->linkedDataCreateCallback = attachAvatarDataToNode;
|
||||
nodeList->linkedDataCreateCallback = [] (Node* node) {
|
||||
node->setLinkedData(new AvatarMixerClientData());
|
||||
};
|
||||
|
||||
// setup the timer that will be fired on the broadcast thread
|
||||
QTimer* broadcastTimer = new QTimer();
|
||||
broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS);
|
||||
broadcastTimer->moveToThread(&_broadcastThread);
|
||||
_broadcastTimer = new QTimer;
|
||||
_broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS);
|
||||
_broadcastTimer->moveToThread(&_broadcastThread);
|
||||
|
||||
// connect appropriate signals and slots
|
||||
connect(broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection);
|
||||
connect(&_broadcastThread, SIGNAL(started()), broadcastTimer, SLOT(start()));
|
||||
connect(_broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection);
|
||||
connect(&_broadcastThread, SIGNAL(started()), _broadcastTimer, SLOT(start()));
|
||||
|
||||
// wait until we have the domain-server settings, otherwise we bail
|
||||
DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
|
||||
qDebug() << "Waiting for domain settings from domain-server.";
|
||||
|
||||
// block until we get the settingsRequestComplete signal
|
||||
QEventLoop loop;
|
||||
connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit);
|
||||
connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit);
|
||||
domainHandler.requestDomainSettings();
|
||||
loop.exec();
|
||||
|
||||
if (domainHandler.getSettingsObject().isEmpty()) {
|
||||
qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment.";
|
||||
setFinished(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// parse the settings to pull out the values we need
|
||||
parseDomainServerSettings(domainHandler.getSettingsObject());
|
||||
|
||||
// start the broadcastThread
|
||||
_broadcastThread.start();
|
||||
}
|
||||
|
||||
void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
||||
const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer";
|
||||
const QString NODE_SEND_BANDWIDTH_KEY = "max_node_send_bandwidth";
|
||||
|
||||
const float DEFAULT_NODE_SEND_BANDWIDTH = 1.0f;
|
||||
QJsonValue nodeBandwidthValue = domainSettings[AVATAR_MIXER_SETTINGS_KEY].toObject()[NODE_SEND_BANDWIDTH_KEY];
|
||||
if (!nodeBandwidthValue.isDouble()) {
|
||||
qDebug() << NODE_SEND_BANDWIDTH_KEY << "is not a double - will continue with default value";
|
||||
}
|
||||
|
||||
_maxKbpsPerNode = nodeBandwidthValue.toDouble(DEFAULT_NODE_SEND_BANDWIDTH) * KILO_PER_MEGA;
|
||||
qDebug() << "The maximum send bandwidth per node is" << _maxKbpsPerNode << "kbps.";
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ public slots:
|
|||
|
||||
private:
|
||||
void broadcastAvatarData();
|
||||
void parseDomainServerSettings(const QJsonObject& domainSettings);
|
||||
|
||||
QThread _broadcastThread;
|
||||
|
||||
|
@ -47,6 +48,10 @@ private:
|
|||
int _numStatFrames;
|
||||
int _sumBillboardPackets;
|
||||
int _sumIdentityPackets;
|
||||
|
||||
float _maxKbpsPerNode = 0.0f;
|
||||
|
||||
QTimer* _broadcastTimer = nullptr;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarMixer_h
|
||||
|
|
|
@ -13,15 +13,6 @@
|
|||
|
||||
#include "AvatarMixerClientData.h"
|
||||
|
||||
AvatarMixerClientData::AvatarMixerClientData() :
|
||||
NodeData(),
|
||||
_hasReceivedFirstPackets(false),
|
||||
_billboardChangeTimestamp(0),
|
||||
_identityChangeTimestamp(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int AvatarMixerClientData::parseData(const QByteArray& packet) {
|
||||
// compute the offset to the data payload
|
||||
int offset = numBytesForPacketHeader(packet);
|
||||
|
@ -33,3 +24,25 @@ bool AvatarMixerClientData::checkAndSetHasReceivedFirstPackets() {
|
|||
_hasReceivedFirstPackets = true;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
PacketSequenceNumber AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const {
|
||||
// return the matching PacketSequenceNumber, or the default if we don't have it
|
||||
auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID);
|
||||
if (nodeMatch != _lastBroadcastSequenceNumbers.end()) {
|
||||
return nodeMatch->second;
|
||||
} else {
|
||||
return DEFAULT_SEQUENCE_NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
|
||||
jsonObject["display_name"] = _avatar.getDisplayName();
|
||||
jsonObject["full_rate_distance"] = _fullRateDistance;
|
||||
jsonObject["max_avatar_distance"] = _maxAvatarDistance;
|
||||
jsonObject["num_avatars_sent_last_frame"] = _numAvatarsSentLastFrame;
|
||||
jsonObject["avg_other_avatar_starves_per_second"] = getAvgNumOtherAvatarStarvesPerSecond();
|
||||
jsonObject["avg_other_avatar_skips_per_second"] = getAvgNumOtherAvatarSkipsPerSecond();
|
||||
jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends;
|
||||
|
||||
jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps();
|
||||
}
|
||||
|
|
|
@ -12,32 +12,89 @@
|
|||
#ifndef hifi_AvatarMixerClientData_h
|
||||
#define hifi_AvatarMixerClientData_h
|
||||
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <AvatarData.h>
|
||||
#include <NodeData.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SimpleMovingAverage.h>
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps";
|
||||
|
||||
class AvatarMixerClientData : public NodeData {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AvatarMixerClientData();
|
||||
|
||||
int parseData(const QByteArray& packet);
|
||||
AvatarData& getAvatar() { return _avatar; }
|
||||
|
||||
bool checkAndSetHasReceivedFirstPackets();
|
||||
|
||||
|
||||
PacketSequenceNumber getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const;
|
||||
void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, PacketSequenceNumber sequenceNumber)
|
||||
{ _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; }
|
||||
Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); }
|
||||
|
||||
quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; }
|
||||
void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; }
|
||||
|
||||
quint64 getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
|
||||
void setIdentityChangeTimestamp(quint64 identityChangeTimestamp) { _identityChangeTimestamp = identityChangeTimestamp; }
|
||||
|
||||
void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; }
|
||||
float getFullRateDistance() const { return _fullRateDistance; }
|
||||
|
||||
void setMaxAvatarDistance(float maxAvatarDistance) { _maxAvatarDistance = maxAvatarDistance; }
|
||||
float getMaxAvatarDistance() const { return _maxAvatarDistance; }
|
||||
|
||||
void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; }
|
||||
void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; }
|
||||
int getNumAvatarsSentLastFrame() const { return _numAvatarsSentLastFrame; }
|
||||
|
||||
void recordNumOtherAvatarStarves(int numAvatarsHeldBack) { _otherAvatarStarves.updateAverage((float) numAvatarsHeldBack); }
|
||||
float getAvgNumOtherAvatarStarvesPerSecond() const { return _otherAvatarStarves.getAverageSampleValuePerSecond(); }
|
||||
|
||||
void recordNumOtherAvatarSkips(int numOtherAvatarSkips) { _otherAvatarSkips.updateAverage((float) numOtherAvatarSkips); }
|
||||
float getAvgNumOtherAvatarSkipsPerSecond() const { return _otherAvatarSkips.getAverageSampleValuePerSecond(); }
|
||||
|
||||
void incrementNumOutOfOrderSends() { ++_numOutOfOrderSends; }
|
||||
|
||||
int getNumFramesSinceFRDAdjustment() const { return _numFramesSinceAdjustment; }
|
||||
void incrementNumFramesSinceFRDAdjustment() { ++_numFramesSinceAdjustment; }
|
||||
void resetNumFramesSinceFRDAdjustment() { _numFramesSinceAdjustment = 0; }
|
||||
|
||||
void recordSentAvatarData(int numBytes) { _avgOtherAvatarDataRate.updateAverage((float) numBytes); }
|
||||
|
||||
float getOutboundAvatarDataKbps() const
|
||||
{ return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; }
|
||||
|
||||
void loadJSONStats(QJsonObject& jsonObject) const;
|
||||
private:
|
||||
AvatarData _avatar;
|
||||
bool _hasReceivedFirstPackets;
|
||||
quint64 _billboardChangeTimestamp;
|
||||
quint64 _identityChangeTimestamp;
|
||||
|
||||
std::unordered_map<QUuid, PacketSequenceNumber, UUIDHasher> _lastBroadcastSequenceNumbers;
|
||||
|
||||
bool _hasReceivedFirstPackets = false;
|
||||
quint64 _billboardChangeTimestamp = 0;
|
||||
quint64 _identityChangeTimestamp = 0;
|
||||
|
||||
float _fullRateDistance = FLT_MAX;
|
||||
float _maxAvatarDistance = FLT_MAX;
|
||||
|
||||
int _numAvatarsSentLastFrame = 0;
|
||||
int _numFramesSinceAdjustment = 0;
|
||||
|
||||
SimpleMovingAverage _otherAvatarStarves;
|
||||
SimpleMovingAverage _otherAvatarSkips;
|
||||
int _numOutOfOrderSends = 0;
|
||||
|
||||
SimpleMovingAverage _avgOtherAvatarDataRate;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarMixerClientData_h
|
||||
|
|
|
@ -27,6 +27,11 @@ EntityServer::EntityServer(const QByteArray& packet)
|
|||
}
|
||||
|
||||
EntityServer::~EntityServer() {
|
||||
if (_pruneDeletedEntitiesTimer) {
|
||||
_pruneDeletedEntitiesTimer->stop();
|
||||
_pruneDeletedEntitiesTimer->deleteLater();
|
||||
}
|
||||
|
||||
EntityTree* tree = (EntityTree*)_tree;
|
||||
tree->removeNewlyCreatedHook(this);
|
||||
}
|
||||
|
@ -48,35 +53,13 @@ Octree* EntityServer::createTree() {
|
|||
}
|
||||
|
||||
void EntityServer::beforeRun() {
|
||||
QTimer* pruneDeletedEntitiesTimer = new QTimer(this);
|
||||
connect(pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities()));
|
||||
_pruneDeletedEntitiesTimer = new QTimer();
|
||||
connect(_pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities()));
|
||||
const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second
|
||||
pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS);
|
||||
_pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS);
|
||||
}
|
||||
|
||||
void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) {
|
||||
|
||||
unsigned char outputBuffer[MAX_PACKET_SIZE];
|
||||
unsigned char* copyAt = outputBuffer;
|
||||
|
||||
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(outputBuffer), PacketTypeEntityAddResponse);
|
||||
int packetLength = numBytesPacketHeader;
|
||||
copyAt += numBytesPacketHeader;
|
||||
|
||||
// encode the creatorTokenID
|
||||
uint32_t creatorTokenID = newEntity.getCreatorTokenID();
|
||||
memcpy(copyAt, &creatorTokenID, sizeof(creatorTokenID));
|
||||
copyAt += sizeof(creatorTokenID);
|
||||
packetLength += sizeof(creatorTokenID);
|
||||
|
||||
// encode the entity ID
|
||||
QUuid entityID = newEntity.getID();
|
||||
QByteArray encodedID = entityID.toRfc4122();
|
||||
memcpy(copyAt, encodedID.constData(), encodedID.size());
|
||||
copyAt += sizeof(entityID);
|
||||
packetLength += sizeof(entityID);
|
||||
|
||||
DependencyManager::get<NodeList>()->writeDatagram((char*) outputBuffer, packetLength, senderNode);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ protected:
|
|||
|
||||
private:
|
||||
EntitySimulation* _entitySimulation;
|
||||
QTimer* _pruneDeletedEntitiesTimer = nullptr;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityServer_h
|
||||
|
|
|
@ -9,10 +9,15 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "AssignmentClientApp.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
AssignmentClientApp app(argc, argv);
|
||||
return 0;
|
||||
|
||||
int acReturn = app.exec();
|
||||
qDebug() << "assignment-client process" << app.applicationPid() << "exiting with status code" << acReturn;
|
||||
|
||||
return acReturn;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
//
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
|
@ -275,9 +277,11 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
|
|||
|
||||
char* dataAt = packet;
|
||||
int bytesRemaining = MAX_PACKET_SIZE;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// pack header
|
||||
int numBytesPacketHeader = populatePacketHeader(packet, _myServer->getMyEditNackType());
|
||||
int numBytesPacketHeader = nodeList->populatePacketHeader(packet, _myServer->getMyEditNackType());
|
||||
dataAt += numBytesPacketHeader;
|
||||
bytesRemaining -= numBytesPacketHeader;
|
||||
|
||||
|
@ -299,7 +303,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
|
|||
numSequenceNumbersAvailable -= numSequenceNumbers;
|
||||
|
||||
// send it
|
||||
DependencyManager::get<NodeList>()->writeUnverifiedDatagram(packet, dataAt - packet, destinationNode);
|
||||
nodeList->writeUnverifiedDatagram(packet, dataAt - packet, destinationNode);
|
||||
packetsSent++;
|
||||
|
||||
qDebug() << "NACK Sent back to editor/client... destinationNode=" << nodeUUID;
|
||||
|
|
|
@ -74,7 +74,7 @@ public:
|
|||
|
||||
NodeToSenderStatsMap& getSingleSenderStats() { return _singleSenderStats; }
|
||||
|
||||
void shuttingDown() { _shuttingDown = true;}
|
||||
virtual void terminating() { _shuttingDown = true; ReceivedPacketProcessor::terminating(); }
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -9,11 +9,15 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "PacketHeaders.h"
|
||||
#include "SharedUtil.h"
|
||||
#include "OctreeQueryNode.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include "OctreeSendThread.h"
|
||||
|
||||
OctreeQueryNode::OctreeQueryNode() :
|
||||
|
@ -91,8 +95,8 @@ void OctreeQueryNode::sendThreadFinished() {
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeQueryNode::initializeOctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node) {
|
||||
_octreeSendThread = new OctreeSendThread(myAssignment, node);
|
||||
void OctreeQueryNode::initializeOctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) {
|
||||
_octreeSendThread = new OctreeSendThread(myServer, node);
|
||||
|
||||
// we want to be notified when the thread finishes
|
||||
connect(_octreeSendThread, &GenericThread::finished, this, &OctreeQueryNode::sendThreadFinished);
|
||||
|
@ -182,14 +186,16 @@ void OctreeQueryNode::resetOctreePacket() {
|
|||
_currentPacketIsCompressed = getWantCompression();
|
||||
OCTREE_PACKET_FLAGS flags = 0;
|
||||
if (_currentPacketIsColor) {
|
||||
setAtBit(flags,PACKET_IS_COLOR_BIT);
|
||||
setAtBit(flags, PACKET_IS_COLOR_BIT);
|
||||
}
|
||||
if (_currentPacketIsCompressed) {
|
||||
setAtBit(flags,PACKET_IS_COMPRESSED_BIT);
|
||||
setAtBit(flags, PACKET_IS_COMPRESSED_BIT);
|
||||
}
|
||||
|
||||
_octreePacketAvailableBytes = MAX_PACKET_SIZE;
|
||||
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(_octreePacket), _myPacketType);
|
||||
int numBytesPacketHeader = DependencyManager::get<NodeList>()->populatePacketHeader(reinterpret_cast<char*>(_octreePacket),
|
||||
_myPacketType);
|
||||
|
||||
_octreePacketAt = _octreePacket + numBytesPacketHeader;
|
||||
_octreePacketAvailableBytes -= numBytesPacketHeader;
|
||||
|
||||
|
@ -198,7 +204,7 @@ void OctreeQueryNode::resetOctreePacket() {
|
|||
*flagsAt = flags;
|
||||
_octreePacketAt += sizeof(OCTREE_PACKET_FLAGS);
|
||||
_octreePacketAvailableBytes -= sizeof(OCTREE_PACKET_FLAGS);
|
||||
|
||||
|
||||
// pack in sequence number
|
||||
OCTREE_PACKET_SEQUENCE* sequenceAt = (OCTREE_PACKET_SEQUENCE*)_octreePacketAt;
|
||||
*sequenceAt = _sequenceNumber;
|
||||
|
@ -252,11 +258,16 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
|||
float originalFOV = getCameraFov();
|
||||
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
|
||||
|
||||
newestViewFrustum.setFieldOfView(wideFOV); // hack
|
||||
newestViewFrustum.setAspectRatio(getCameraAspectRatio());
|
||||
newestViewFrustum.setNearClip(getCameraNearClip());
|
||||
newestViewFrustum.setFarClip(getCameraFarClip());
|
||||
newestViewFrustum.setEyeOffsetPosition(getCameraEyeOffsetPosition());
|
||||
if (0.0f != getCameraAspectRatio() &&
|
||||
0.0f != getCameraNearClip() &&
|
||||
0.0f != getCameraFarClip()) {
|
||||
newestViewFrustum.setProjection(glm::perspective(
|
||||
glm::radians(wideFOV), // hack
|
||||
getCameraAspectRatio(),
|
||||
getCameraNearClip(),
|
||||
getCameraFarClip()));
|
||||
}
|
||||
|
||||
|
||||
// if there has been a change, then recalculate
|
||||
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
|
||||
|
@ -314,8 +325,7 @@ void OctreeQueryNode::updateLastKnownViewFrustum() {
|
|||
}
|
||||
|
||||
// save that we know the view has been sent.
|
||||
quint64 now = usecTimestampNow();
|
||||
setLastTimeBagEmpty(now); // is this what we want? poor names
|
||||
setLastTimeBagEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -22,11 +22,11 @@
|
|||
#include <OctreePacketData.h>
|
||||
#include <OctreeQuery.h>
|
||||
#include <OctreeSceneStats.h>
|
||||
#include <ThreadedAssignment.h> // for SharedAssignmentPointer
|
||||
#include "SentPacketHistory.h"
|
||||
#include <qqueue.h>
|
||||
|
||||
class OctreeSendThread;
|
||||
class OctreeServer;
|
||||
|
||||
class OctreeQueryNode : public OctreeQuery {
|
||||
Q_OBJECT
|
||||
|
@ -77,7 +77,7 @@ public:
|
|||
bool moveShouldDump() const;
|
||||
|
||||
quint64 getLastTimeBagEmpty() const { return _lastTimeBagEmpty; }
|
||||
void setLastTimeBagEmpty(quint64 lastTimeBagEmpty) { _lastTimeBagEmpty = lastTimeBagEmpty; }
|
||||
void setLastTimeBagEmpty() { _lastTimeBagEmpty = _sceneSendStartTime; }
|
||||
|
||||
bool getCurrentPacketIsColor() const { return _currentPacketIsColor; }
|
||||
bool getCurrentPacketIsCompressed() const { return _currentPacketIsCompressed; }
|
||||
|
@ -89,7 +89,7 @@ public:
|
|||
|
||||
OctreeSceneStats stats;
|
||||
|
||||
void initializeOctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node);
|
||||
void initializeOctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node);
|
||||
bool isOctreeSendThreadInitalized() { return _octreeSendThread; }
|
||||
|
||||
void dumpOutOfView();
|
||||
|
@ -98,6 +98,8 @@ public:
|
|||
void setLastRootTimestamp(quint64 timestamp) { _lastRootTimestamp = timestamp; }
|
||||
unsigned int getlastOctreePacketLength() const { return _lastOctreePacketLength; }
|
||||
int getDuplicatePacketCount() const { return _duplicatePacketCount; }
|
||||
|
||||
void sceneStart(quint64 sceneSendStartTime) { _sceneSendStartTime = sceneSendStartTime; }
|
||||
|
||||
void nodeKilled();
|
||||
void forceNodeShutdown();
|
||||
|
@ -158,6 +160,8 @@ private:
|
|||
|
||||
SentPacketHistory _sentPacketHistory;
|
||||
QQueue<OCTREE_PACKET_SEQUENCE> _nackedSequenceNumbers;
|
||||
|
||||
quint64 _sceneSendStartTime = 0;
|
||||
};
|
||||
|
||||
#endif // hifi_OctreeQueryNode_h
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
//
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "OctreeSendThread.h"
|
||||
#include "OctreeServer.h"
|
||||
|
@ -21,9 +21,8 @@
|
|||
quint64 startSceneSleepTime = 0;
|
||||
quint64 endSceneSleepTime = 0;
|
||||
|
||||
OctreeSendThread::OctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node) :
|
||||
_myAssignment(myAssignment),
|
||||
_myServer(static_cast<OctreeServer*>(myAssignment.data())),
|
||||
OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
|
||||
_myServer(myServer),
|
||||
_node(node),
|
||||
_nodeUUID(node->getUUID()),
|
||||
_packetData(),
|
||||
|
@ -31,9 +30,14 @@ OctreeSendThread::OctreeSendThread(const SharedAssignmentPointer& myAssignment,
|
|||
_isShuttingDown(false)
|
||||
{
|
||||
QString safeServerName("Octree");
|
||||
|
||||
// set our QThread object name so we can identify this thread while debugging
|
||||
setObjectName(QString("Octree Send Thread (%1)").arg(uuidStringWithoutCurlyBraces(node->getUUID())));
|
||||
|
||||
if (_myServer) {
|
||||
safeServerName = _myServer->getMyServerName();
|
||||
}
|
||||
|
||||
qDebug() << qPrintable(safeServerName) << "server [" << _myServer << "]: client connected "
|
||||
"- starting sending thread [" << this << "]";
|
||||
|
||||
|
@ -53,7 +57,6 @@ OctreeSendThread::~OctreeSendThread() {
|
|||
OctreeServer::stopTrackingThread(this);
|
||||
|
||||
_node.clear();
|
||||
_myAssignment.clear();
|
||||
}
|
||||
|
||||
void OctreeSendThread::setIsShuttingDown() {
|
||||
|
@ -66,15 +69,13 @@ bool OctreeSendThread::process() {
|
|||
return false; // exit early if we're shutting down
|
||||
}
|
||||
|
||||
// check that our server and assignment is still valid
|
||||
if (!_myServer || !_myAssignment) {
|
||||
return false; // exit early if it's not, it means the server is shutting down
|
||||
}
|
||||
|
||||
OctreeServer::didProcess(this);
|
||||
|
||||
quint64 start = usecTimestampNow();
|
||||
|
||||
// we'd better have a server at this point, or we're in trouble
|
||||
assert(_myServer);
|
||||
|
||||
// don't do any send processing until the initial load of the octree is complete...
|
||||
if (_myServer->isInitialLoadComplete()) {
|
||||
if (_node) {
|
||||
|
@ -290,7 +291,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
}
|
||||
|
||||
// calculate max number of packets that can be sent during this interval
|
||||
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxOctreePacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
||||
|
||||
int truePacketsSent = 0;
|
||||
|
@ -342,8 +343,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
|
||||
if (!viewFrustumChanged && !nodeData->getWantDelta()) {
|
||||
// only set our last sent time if we weren't resetting due to frustum change
|
||||
quint64 now = usecTimestampNow();
|
||||
nodeData->setLastTimeBagEmpty(now);
|
||||
nodeData->setLastTimeBagEmpty();
|
||||
}
|
||||
|
||||
// track completed scenes and send out the stats packet accordingly
|
||||
|
@ -367,9 +367,11 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
|
||||
// TODO: add these to stats page
|
||||
//::startSceneSleepTime = _usleepTime;
|
||||
|
||||
|
||||
nodeData->sceneStart(usecTimestampNow() - CHANGE_FUDGE);
|
||||
// start tracking our stats
|
||||
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot(), _myServer->getJurisdiction());
|
||||
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged,
|
||||
_myServer->getOctree()->getRoot(), _myServer->getJurisdiction());
|
||||
|
||||
// This is the start of "resending" the scene.
|
||||
bool dontRestartSceneOnMove = false; // this is experimental
|
||||
|
@ -560,6 +562,13 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
}
|
||||
|
||||
|
||||
if (somethingToSend) {
|
||||
qDebug() << "Hit PPS Limit, packetsSentThisInterval =" << packetsSentThisInterval
|
||||
<< " maxPacketsPerInterval = " << maxPacketsPerInterval
|
||||
<< " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval;
|
||||
}
|
||||
|
||||
|
||||
// Here's where we can/should allow the server to send other data...
|
||||
// send the environment packet
|
||||
// TODO: should we turn this into a while loop to better handle sending multiple special packets
|
||||
|
|
|
@ -26,7 +26,7 @@ class OctreeServer;
|
|||
class OctreeSendThread : public GenericThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node);
|
||||
OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node);
|
||||
virtual ~OctreeSendThread();
|
||||
|
||||
void setIsShuttingDown();
|
||||
|
@ -43,7 +43,6 @@ protected:
|
|||
virtual bool process();
|
||||
|
||||
private:
|
||||
SharedAssignmentPointer _myAssignment;
|
||||
OctreeServer* _myServer;
|
||||
SharedNodePointer _node;
|
||||
QUuid _nodeUUID;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <HTTPConnection.h>
|
||||
#include <LogHandler.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include "../AssignmentClient.h"
|
||||
|
@ -212,14 +213,6 @@ void OctreeServer::trackProcessWaitTime(float time) {
|
|||
_averageProcessWaitTime.updateAverage(time);
|
||||
}
|
||||
|
||||
void OctreeServer::attachQueryNodeToNode(Node* newNode) {
|
||||
if (!newNode->getLinkedData() && _instance) {
|
||||
OctreeQueryNode* newQueryNodeData = _instance->createOctreeQueryNode();
|
||||
newQueryNodeData->init();
|
||||
newNode->setLinkedData(newQueryNodeData);
|
||||
}
|
||||
}
|
||||
|
||||
OctreeServer::OctreeServer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
_argc(0),
|
||||
|
@ -266,16 +259,19 @@ OctreeServer::~OctreeServer() {
|
|||
}
|
||||
|
||||
if (_jurisdictionSender) {
|
||||
_jurisdictionSender->terminating();
|
||||
_jurisdictionSender->terminate();
|
||||
_jurisdictionSender->deleteLater();
|
||||
}
|
||||
|
||||
if (_octreeInboundPacketProcessor) {
|
||||
_octreeInboundPacketProcessor->terminating();
|
||||
_octreeInboundPacketProcessor->terminate();
|
||||
_octreeInboundPacketProcessor->deleteLater();
|
||||
}
|
||||
|
||||
if (_persistThread) {
|
||||
_persistThread->terminating();
|
||||
_persistThread->terminate();
|
||||
_persistThread->deleteLater();
|
||||
}
|
||||
|
@ -832,42 +828,42 @@ void OctreeServer::parsePayload() {
|
|||
|
||||
void OctreeServer::readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// If we know we're shutting down we just drop these packets on the floor.
|
||||
// This stops us from initializing send threads we just shut down.
|
||||
|
||||
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
||||
PacketType packetType = packetTypeForPacket(receivedPacket);
|
||||
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
if (packetType == getMyQueryMessageType()) {
|
||||
// If we got a query packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
if (matchingNode) {
|
||||
nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket);
|
||||
OctreeQueryNode* nodeData = (OctreeQueryNode*)matchingNode->getLinkedData();
|
||||
if (nodeData && !nodeData->isOctreeSendThreadInitalized()) {
|
||||
if (!_isShuttingDown) {
|
||||
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
||||
PacketType packetType = packetTypeForPacket(receivedPacket);
|
||||
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
if (packetType == getMyQueryMessageType()) {
|
||||
// If we got a query packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
if (matchingNode) {
|
||||
nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket);
|
||||
|
||||
// NOTE: this is an important aspect of the proper ref counting. The send threads/node data need to
|
||||
// know that the OctreeServer/Assignment will not get deleted on it while it's still active. The
|
||||
// solution is to get the shared pointer for the current assignment. We need to make sure this is the
|
||||
// same SharedAssignmentPointer that was ref counted by the assignment client.
|
||||
SharedAssignmentPointer sharedAssignment = AssignmentClient::getCurrentAssignment();
|
||||
nodeData->initializeOctreeSendThread(sharedAssignment, matchingNode);
|
||||
OctreeQueryNode* nodeData = (OctreeQueryNode*) matchingNode->getLinkedData();
|
||||
if (nodeData && !nodeData->isOctreeSendThreadInitalized()) {
|
||||
nodeData->initializeOctreeSendThread(this, matchingNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (packetType == PacketTypeOctreeDataNack) {
|
||||
// If we got a nack packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
if (matchingNode) {
|
||||
OctreeQueryNode* nodeData = (OctreeQueryNode*)matchingNode->getLinkedData();
|
||||
if (nodeData) {
|
||||
nodeData->parseNackPacket(receivedPacket);
|
||||
} else if (packetType == PacketTypeOctreeDataNack) {
|
||||
// If we got a nack packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
if (matchingNode) {
|
||||
OctreeQueryNode* nodeData = (OctreeQueryNode*)matchingNode->getLinkedData();
|
||||
if (nodeData) {
|
||||
nodeData->parseNackPacket(receivedPacket);
|
||||
}
|
||||
}
|
||||
} else if (packetType == PacketTypeJurisdictionRequest) {
|
||||
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
|
||||
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else {
|
||||
// let processNodeData handle it.
|
||||
DependencyManager::get<NodeList>()->processNodeData(senderSockAddr, receivedPacket);
|
||||
}
|
||||
} else if (packetType == PacketTypeJurisdictionRequest) {
|
||||
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
|
||||
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else {
|
||||
// let processNodeData handle it.
|
||||
DependencyManager::get<NodeList>()->processNodeData(senderSockAddr, receivedPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1095,8 +1091,6 @@ void OctreeServer::readConfiguration() {
|
|||
}
|
||||
|
||||
void OctreeServer::run() {
|
||||
qInstallMessageHandler(LogHandler::verboseMessageHandler);
|
||||
|
||||
_safeServerName = getMyServerName();
|
||||
|
||||
// Before we do anything else, create our tree...
|
||||
|
@ -1130,7 +1124,11 @@ void OctreeServer::run() {
|
|||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
#endif
|
||||
|
||||
nodeList->linkedDataCreateCallback = &OctreeServer::attachQueryNodeToNode;
|
||||
nodeList->linkedDataCreateCallback = [] (Node* node) {
|
||||
OctreeQueryNode* newQueryNodeData = _instance->createOctreeQueryNode();
|
||||
newQueryNodeData->init();
|
||||
node->setLinkedData(newQueryNodeData);
|
||||
};
|
||||
|
||||
srand((unsigned)time(0));
|
||||
|
||||
|
@ -1218,16 +1216,34 @@ void OctreeServer::forceNodeShutdown(SharedNodePointer node) {
|
|||
|
||||
void OctreeServer::aboutToFinish() {
|
||||
qDebug() << qPrintable(_safeServerName) << "server STARTING about to finish...";
|
||||
|
||||
_isShuttingDown = true;
|
||||
|
||||
qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down...";
|
||||
_octreeInboundPacketProcessor->shuttingDown();
|
||||
|
||||
// we're going down - set the NodeList linkedDataCallback to NULL so we do not create any more OctreeQueryNode objects.
|
||||
// This ensures that when we forceNodeShutdown below for each node we don't get any more newly connecting nodes
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->linkedDataCreateCallback = NULL;
|
||||
|
||||
DependencyManager::get<NodeList>()->eachNode([this](const SharedNodePointer& node) {
|
||||
if (_octreeInboundPacketProcessor) {
|
||||
_octreeInboundPacketProcessor->terminating();
|
||||
}
|
||||
|
||||
if (_jurisdictionSender) {
|
||||
_jurisdictionSender->terminating();
|
||||
}
|
||||
|
||||
// force a shutdown of all of our OctreeSendThreads - at this point it has to be impossible for a
|
||||
// linkedDataCreateCallback to be called for a new node
|
||||
nodeList->eachNode([this](const SharedNodePointer& node) {
|
||||
qDebug() << qPrintable(_safeServerName) << "server about to finish while node still connected node:" << *node;
|
||||
forceNodeShutdown(node);
|
||||
});
|
||||
|
||||
if (_persistThread) {
|
||||
_persistThread->aboutToFinish();
|
||||
_persistThread->terminating();
|
||||
}
|
||||
|
||||
qDebug() << qPrintable(_safeServerName) << "server ENDING about to finish...";
|
||||
|
|
|
@ -75,8 +75,6 @@ public:
|
|||
virtual bool hasSpecialPacketToSend(const SharedNodePointer& node) { return false; }
|
||||
virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { return 0; }
|
||||
|
||||
static void attachQueryNodeToNode(Node* newNode);
|
||||
|
||||
static float SKIP_TIME; // use this for trackXXXTime() calls for non-times
|
||||
|
||||
static void trackLoopTime(float time) { _averageLoopTime.updateAverage(time); }
|
||||
|
@ -146,12 +144,14 @@ protected:
|
|||
QString getStatusLink();
|
||||
|
||||
void setupDatagramProcessingThread();
|
||||
|
||||
|
||||
int _argc;
|
||||
const char** _argv;
|
||||
char** _parsedArgV;
|
||||
QJsonObject _settings;
|
||||
|
||||
bool _isShuttingDown = false;
|
||||
|
||||
HTTPManager* _httpManager;
|
||||
int _statusPort;
|
||||
QString _statusHost;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#
|
||||
# OSXTBBInstallNameChange.cmake
|
||||
# cmake/externals/tbb
|
||||
# OSXInstallNameChange.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
# Created by Stephen Birarda on February 20, 2014
|
||||
|
@ -10,36 +10,33 @@
|
|||
#
|
||||
|
||||
# first find the so files in the source dir
|
||||
set(_TBB_LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib)
|
||||
file(GLOB_RECURSE _TBB_LIBRARIES "${_TBB_LIBRARY_DIR}/*.dylib")
|
||||
message("INSTALL_NAME_LIBRARY_DIR ${INSTALL_NAME_LIBRARY_DIR}")
|
||||
|
||||
# raise an error if we found none
|
||||
if (NOT _TBB_LIBRARIES)
|
||||
message(FATAL_ERROR "Did not find any TBB libraries")
|
||||
file(GLOB_RECURSE _LIBRARIES "${INSTALL_NAME_LIBRARY_DIR}/*.dylib")
|
||||
|
||||
if (NOT _LIBRARIES)
|
||||
message(FATAL_ERROR "OSXInstallNameChange -- no libraries found: ${INSTALL_NAME_LIBRARY_DIR}")
|
||||
endif ()
|
||||
|
||||
# find the install_name_tool command
|
||||
find_program(INSTALL_NAME_TOOL_COMMAND NAMES install_name_tool DOC "Path to the install_name_tool command")
|
||||
|
||||
# find the lipo command
|
||||
find_program(LIPO_COMMAND NAMES lipo DOC "Path to the lipo command")
|
||||
|
||||
# enumerate the libraries
|
||||
foreach(_TBB_LIBRARY ${_TBB_LIBRARIES})
|
||||
get_filename_component(_TBB_LIBRARY_FILENAME ${_TBB_LIBRARY} NAME)
|
||||
foreach(_LIBRARY ${_LIBRARIES})
|
||||
get_filename_component(_LIBRARY_FILENAME ${_LIBRARY} NAME)
|
||||
|
||||
set(_INSTALL_NAME_ARGS ${INSTALL_NAME_TOOL_COMMAND} -id ${_TBB_LIBRARY} ${_TBB_LIBRARY_FILENAME})
|
||||
set(_INSTALL_NAME_ARGS ${INSTALL_NAME_TOOL_COMMAND} -id ${_LIBRARY} ${_LIBRARY_FILENAME})
|
||||
|
||||
message(STATUS "${INSTALL_NAME_COMMAND} ${_INSTALL_NAME_ARGS}")
|
||||
|
||||
execute_process(
|
||||
COMMAND ${INSTALL_NAME_COMMAND} ${_INSTALL_NAME_ARGS}
|
||||
WORKING_DIRECTORY ${_TBB_LIBRARY_DIR}
|
||||
WORKING_DIRECTORY ${INSTALL_NAME_LIBRARY_DIR}
|
||||
ERROR_VARIABLE _INSTALL_NAME_ERROR
|
||||
)
|
||||
|
||||
if (_INSTALL_NAME_ERROR)
|
||||
message(FATAL_ERROR "There was an error changing install name for ${_TBB_LIBRARY_FILENAME} - ${_INSTALL_NAME_ERROR}")
|
||||
message(FATAL_ERROR "There was an error changing install name for ${_LIBRARY_FILENAME} - ${_INSTALL_NAME_ERROR}")
|
||||
endif ()
|
||||
endforeach()
|
||||
|
18
cmake/externals/boostconfig/CMakeLists.txt
vendored
Normal file
18
cmake/externals/boostconfig/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
set(EXTERNAL_NAME boostconfig)
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://github.com/boostorg/config/archive/boost-1.58.0.zip
|
||||
URL_MD5 42fa673bae2b7645a22736445e80eb8d
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
|
||||
|
18
cmake/externals/oglplus/CMakeLists.txt
vendored
Normal file
18
cmake/externals/oglplus/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
set(EXTERNAL_NAME oglplus)
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://softlayer-dal.dl.sourceforge.net/project/oglplus/oglplus-0.61.x/oglplus-0.61.0.zip
|
||||
URL_MD5 bb55038c36c660d2b6c7be380414fa60
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include ${SOURCE_DIR}/implement CACHE TYPE INTERNAL)
|
||||
|
38
cmake/externals/openvr/CMakeLists.txt
vendored
Normal file
38
cmake/externals/openvr/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
include(ExternalProject)
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
set(EXTERNAL_NAME OpenVR)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://github.com/ValveSoftware/openvr/archive/0.9.1.zip
|
||||
URL_MD5 f986f5a6815e9454c53c5bf58ce02fdc
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/headers CACHE TYPE INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/win32/openvr_api.lib CACHE TYPE INTERNAL)
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/osx32/libopenvr_api.dylib CACHE TYPE INTERNAL)
|
||||
|
||||
elseif(NOT ANDROID)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/linux32/libopenvr_api.so CACHE TYPE INTERNAL)
|
||||
|
||||
endif()
|
||||
|
57
cmake/externals/polyvox/CMakeLists.txt
vendored
Normal file
57
cmake/externals/polyvox/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
set(EXTERNAL_NAME polyvox)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/polyvox.zip
|
||||
URL_MD5 904b840328278c9b36fa7a14be730c34
|
||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
||||
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
if (APPLE)
|
||||
set(INSTALL_NAME_LIBRARY_DIR ${INSTALL_DIR}/lib)
|
||||
message(STATUS "in polyvox INSTALL_NAME_LIBRARY_DIR ${INSTALL_NAME_LIBRARY_DIR}")
|
||||
ExternalProject_Add_Step(
|
||||
${EXTERNAL_NAME}
|
||||
change-install-name
|
||||
COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking"
|
||||
COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${INSTALL_NAME_LIBRARY_DIR} -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake
|
||||
DEPENDEES install
|
||||
WORKING_DIRECTORY <SOURCE_DIR>
|
||||
LOG 1
|
||||
)
|
||||
endif ()
|
||||
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_CORE_INCLUDE_DIRS ${INSTALL_DIR}/PolyVoxCore/include CACHE FILEPATH
|
||||
"Path to polyvox core include directory")
|
||||
set(${EXTERNAL_NAME_UPPER}_UTIL_INCLUDE_DIRS ${INSTALL_DIR}/PolyVoxUtil/include CACHE FILEPATH
|
||||
"Path to polyvox util include directory")
|
||||
else ()
|
||||
set(${EXTERNAL_NAME_UPPER}_CORE_INCLUDE_DIRS ${INSTALL_DIR}/include/PolyVoxCore CACHE FILEPATH
|
||||
"Path to polyvox core include directory")
|
||||
set(${EXTERNAL_NAME_UPPER}_UTIL_INCLUDE_DIRS ${INSTALL_DIR}/include/PolyVoxUtil CACHE FILEPATH
|
||||
"Path to polyvox util include directory")
|
||||
endif ()
|
||||
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY ${INSTALL_DIR}/PolyVoxCore/lib/PolyVoxCore.lib CACHE FILEPATH "polyvox core library")
|
||||
# set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY ${INSTALL_DIR}/PolyVoxUtil/lib/PolyVoxUtil.lib CACHE FILEPATH "polyvox util library")
|
||||
elseif (APPLE)
|
||||
set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY ${INSTALL_DIR}/lib/libPolyVoxCore.dylib CACHE FILEPATH "polyvox core library")
|
||||
# set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY ${INSTALL_DIR}/lib/libPolyVoxUtil.dylib CACHE FILEPATH "polyvox util library")
|
||||
else ()
|
||||
set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY ${INSTALL_DIR}/lib/libPolyVoxCore.so CACHE FILEPATH "polyvox core library")
|
||||
# set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY ${INSTALL_DIR}/lib/libPolyVoxUtil.so CACHE FILEPATH "polyvox util library")
|
||||
endif ()
|
4
cmake/externals/tbb/CMakeLists.txt
vendored
4
cmake/externals/tbb/CMakeLists.txt
vendored
|
@ -66,7 +66,7 @@ if (APPLE)
|
|||
${EXTERNAL_NAME}
|
||||
change-install-name
|
||||
COMMENT "Calling install_name_tool on TBB libraries to fix install name for dylib linking"
|
||||
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/OSXTBBInstallNameChange.cmake
|
||||
COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_TBB_LIB_DIR} -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake
|
||||
DEPENDEES install
|
||||
WORKING_DIRECTORY <SOURCE_DIR>
|
||||
LOG 1
|
||||
|
@ -115,4 +115,4 @@ endif ()
|
|||
|
||||
if (DEFINED ${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE "List of tbb include directories")
|
||||
endif ()
|
||||
endif ()
|
||||
|
|
|
@ -34,4 +34,4 @@ macro(SETUP_HIFI_PROJECT)
|
|||
foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES})
|
||||
target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
|
||||
endforeach()
|
||||
endmacro()
|
||||
endmacro()
|
||||
|
|
56
cmake/macros/SymlinkOrCopyDirectoryBesideTarget.cmake
Normal file
56
cmake/macros/SymlinkOrCopyDirectoryBesideTarget.cmake
Normal file
|
@ -0,0 +1,56 @@
|
|||
#
|
||||
# CopyDirectoryBesideTarget.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Stephen Birarda on 04/30/15.
|
||||
# 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
|
||||
#
|
||||
|
||||
macro(SYMLINK_OR_COPY_DIRECTORY_BESIDE_TARGET _SHOULD_SYMLINK _DIRECTORY _DESTINATION)
|
||||
|
||||
# remove the current directory
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E remove_directory $<TARGET_FILE_DIR:${TARGET_NAME}>/${_DESTINATION}
|
||||
)
|
||||
|
||||
if (${_SHOULD_SYMLINK})
|
||||
|
||||
# first create the destination
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/${_DESTINATION}
|
||||
)
|
||||
|
||||
# the caller wants a symlink, so just add a command to symlink all contents of _DIRECTORY
|
||||
# in the destination - we can't simply create a symlink for _DESTINATION
|
||||
# because the remove_directory call above would delete the original files
|
||||
|
||||
file(GLOB _DIR_ITEMS ${_DIRECTORY}/*)
|
||||
|
||||
foreach(_ITEM ${_DIR_ITEMS})
|
||||
# get the filename for this item
|
||||
get_filename_component(_ITEM_FILENAME ${_ITEM} NAME)
|
||||
|
||||
# add the command to symlink this item
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E create_symlink
|
||||
${_ITEM}
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/${_DESTINATION}/${_ITEM_FILENAME}
|
||||
)
|
||||
endforeach()
|
||||
else ()
|
||||
# copy the directory
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${_DIRECTORY}
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/${_DESTINATION}
|
||||
)
|
||||
endif ()
|
||||
# glob everything in this directory - add a custom command to copy any files
|
||||
endmacro(SYMLINK_OR_COPY_DIRECTORY_BESIDE_TARGET _SHOULD_SYMLINK _DIRECTORY)
|
24
cmake/modules/FindBoostConfig.cmake
Normal file
24
cmake/modules/FindBoostConfig.cmake
Normal file
|
@ -0,0 +1,24 @@
|
|||
#
|
||||
# Try to find BOOSTCONFIG include path.
|
||||
# Once done this will define
|
||||
#
|
||||
# BOOSTCONFIG_INCLUDE_DIRS
|
||||
#
|
||||
# Created by Bradley Austin Davis on 2015/05/22
|
||||
# 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
|
||||
#
|
||||
|
||||
# setup hints for BOOSTCONFIG search
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("BOOSTCONFIG")
|
||||
|
||||
# locate header
|
||||
find_path(BOOSTCONFIG_INCLUDE_DIRS "boost/config.hpp" HINTS ${BOOSTCONFIG_SEARCH_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(BOOSTCONFIG DEFAULT_MSG BOOSTCONFIG_INCLUDE_DIRS)
|
||||
|
||||
mark_as_advanced(BOOSTCONFIG_INCLUDE_DIRS BOOSTCONFIG_SEARCH_DIRS)
|
24
cmake/modules/FindOGLPLUS.cmake
Normal file
24
cmake/modules/FindOGLPLUS.cmake
Normal file
|
@ -0,0 +1,24 @@
|
|||
#
|
||||
# Try to find OGLPLUS include path.
|
||||
# Once done this will define
|
||||
#
|
||||
# OGLPLUS_INCLUDE_DIRS
|
||||
#
|
||||
# Created by Bradley Austin Davis on 2015/05/22
|
||||
# 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
|
||||
#
|
||||
|
||||
# setup hints for OGLPLUS search
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("oglplus")
|
||||
|
||||
# locate header
|
||||
find_path(OGLPLUS_INCLUDE_DIRS "oglplus/fwd.hpp" HINTS ${OGLPLUS_SEARCH_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(OGLPLUS DEFAULT_MSG OGLPLUS_INCLUDE_DIRS)
|
||||
|
||||
mark_as_advanced(OGLPLUS_INCLUDE_DIRS OGLPLUS_SEARCH_DIRS)
|
21
cmake/modules/FindOpenVR.cmake
Normal file
21
cmake/modules/FindOpenVR.cmake
Normal file
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# FindLibOVR.cmake
|
||||
#
|
||||
# Try to find the LibOVR library to use the Oculus
|
||||
|
||||
# Once done this will define
|
||||
#
|
||||
# OPENVR_FOUND - system found LibOVR
|
||||
# OPENVR_INCLUDE_DIRS - the LibOVR include directory
|
||||
# OPENVR_LIBRARIES - Link this to use LibOVR
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
if (NOT ANDROID)
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(OPENVR DEFAULT_MSG OPENVR_INCLUDE_DIRS OPENVR_LIBRARIES)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(OPENVR_INCLUDE_DIRS OPENVR_LIBRARIES OPENVR_SEARCH_DIRS)
|
54
cmake/modules/FindPolyVox.cmake
Normal file
54
cmake/modules/FindPolyVox.cmake
Normal file
|
@ -0,0 +1,54 @@
|
|||
#
|
||||
# FindPolyvox.cmake
|
||||
#
|
||||
# Try to find the libpolyvox resampling library
|
||||
#
|
||||
# You can provide a LIBPOLYVOX_ROOT_DIR which contains lib and include directories
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# POLYVOX_FOUND - system found libpolyvox
|
||||
# POLYVOX_INCLUDE_DIRS - the libpolyvox include directory
|
||||
# POLYVOX_LIBRARIES - link to this to use libpolyvox
|
||||
#
|
||||
# Created on 1/22/2015 by Stephen Birarda
|
||||
# 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
|
||||
#
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("polyvox")
|
||||
|
||||
find_path(POLYVOX_CORE_INCLUDE_DIRS PolyVoxCore/SimpleVolume.h PATH_SUFFIXES include include/PolyVoxCore HINTS ${POLYVOX_SEARCH_DIRS})
|
||||
# find_path(POLYVOX_UTIL_INCLUDE_DIRS PolyVoxUtil/Serialization.h PATH_SUFFIXES include include/PolyVoxUtil HINTS ${POLYVOX_SEARCH_DIRS})
|
||||
|
||||
find_library(POLYVOX_CORE_LIBRARY NAMES PolyVoxCore PATH_SUFFIXES lib HINTS ${POLYVOX_SEARCH_DIRS})
|
||||
# find_library(POLYVOX_UTIL_LIBRARY NAMES PolyVoxUtil PATH_SUFFIXES lib HINTS ${POLYVOX_SEARCH_DIRS})
|
||||
|
||||
|
||||
# if (WIN32)
|
||||
# find_path(POLYVOX_DLL_PATH polyvox.dll PATH_SUFFIXES bin HINTS ${POLYVOX_SEARCH_DIRS})
|
||||
# endif()
|
||||
|
||||
# set(POLYVOX_REQUIREMENTS POLYVOX_CORE_INCLUDE_DIRS POLYVOX_UTIL_INCLUDE_DIRS POLYVOX_CORE_LIBRARY POLYVOX_UTIL_LIBRARY)
|
||||
set(POLYVOX_REQUIREMENTS POLYVOX_CORE_INCLUDE_DIRS POLYVOX_CORE_LIBRARY)
|
||||
# if (WIN32)
|
||||
# list(APPEND POLYVOX_REQUIREMENTS POLYVOX_DLL_PATH)
|
||||
# endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Polyvox DEFAULT_MSG ${POLYVOX_REQUIREMENTS})
|
||||
|
||||
# if (WIN32)
|
||||
# add_paths_to_fixup_libs(${POLYVOX_DLL_PATH})
|
||||
# endif ()
|
||||
|
||||
# set(POLYVOX_INCLUDE_DIRS ${POLYVOX_CORE_INCLUDE_DIRS} ${POLYVOX_UTIL_INCLUDE_DIRS})
|
||||
# set(POLYVOX_LIBRARIES ${POLYVOX_CORE_LIBRARY} ${POLYVOX_UTIL_LIBRARY})
|
||||
|
||||
set(POLYVOX_INCLUDE_DIRS ${POLYVOX_CORE_INCLUDE_DIRS})
|
||||
set(POLYVOX_LIBRARIES ${POLYVOX_CORE_LIBRARY})
|
||||
|
||||
mark_as_advanced(POLYVOX_INCLUDE_DIRS POLYVOX_LIBRARIES POLYVOX_SEARCH_DIRS)
|
|
@ -1,39 +1,18 @@
|
|||
set(TARGET_NAME domain-server)
|
||||
|
||||
if (UPPER_CMAKE_BUILD_TYPE MATCHES DEBUG AND NOT WIN32)
|
||||
set(_SHOULD_SYMLINK_RESOURCES TRUE)
|
||||
else ()
|
||||
set(_SHOULD_SYMLINK_RESOURCES FALSE)
|
||||
endif ()
|
||||
|
||||
# setup the project and link required Qt modules
|
||||
setup_hifi_project(Network)
|
||||
|
||||
# remove current resources dir
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E remove_directory
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources
|
||||
)
|
||||
# copy all files in resources, including web
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${PROJECT_SOURCE_DIR}/resources"
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources
|
||||
)
|
||||
|
||||
if (NOT WIN32)
|
||||
# remove the web directory so we can make it a symlink
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E remove_directory
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web
|
||||
)
|
||||
|
||||
# make the web directory a symlink
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E create_symlink
|
||||
"${PROJECT_SOURCE_DIR}/resources/web"
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web
|
||||
)
|
||||
|
||||
endif ()
|
||||
# TODO: find a solution that will handle web file changes in resources on windows without a re-build.
|
||||
# Currently the resources are only copied on post-build. If one is changed but the domain-server is not, they will
|
||||
# not be re-copied. This is worked-around on OS X/UNIX by using a symlink.
|
||||
symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources" "resources")
|
||||
|
||||
# link the shared hifi libraries
|
||||
link_hifi_libraries(embedded-webserver networking shared)
|
||||
|
@ -52,4 +31,4 @@ include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
|||
# append OpenSSL to our list of libraries to link
|
||||
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
||||
copy_dlls_beside_windows_executable()
|
||||
|
|
File diff suppressed because it is too large
Load diff
22
domain-server/resources/web/css/bootstrap-switch.min.css
vendored
Executable file
22
domain-server/resources/web/css/bootstrap-switch.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,21 +1,10 @@
|
|||
body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table-lead {
|
||||
color: #66CCCC;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.table-lead .lead-line {
|
||||
background-color: #66CCCC;
|
||||
}
|
||||
|
||||
#queued-lead {
|
||||
color: #F77777;
|
||||
}
|
||||
|
||||
#queued-lead .lead-line {
|
||||
background-color: #F77777;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.table-lead h3 {
|
||||
|
@ -44,14 +33,6 @@ span.port {
|
|||
color: #666666;
|
||||
}
|
||||
|
||||
.stats-key {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.stale {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.locked {
|
||||
color: #428bca;
|
||||
}
|
||||
|
@ -81,7 +62,11 @@ span.port {
|
|||
}
|
||||
|
||||
td.buttons {
|
||||
width: 14px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
td.buttons.reorder-buttons {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
td .glyphicon {
|
||||
|
@ -97,7 +82,81 @@ td.reorder-buttons .glyphicon {
|
|||
display: inherit;
|
||||
}
|
||||
|
||||
td a.glyphicon {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
tr.new-row {
|
||||
color: #3c763d;
|
||||
background-color: #dff0d8;
|
||||
}
|
||||
|
||||
.highchart-modal .modal-dialog {
|
||||
width: 650px;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#xs-advanced-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#advanced-toggle-button-xs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* styling for bootstrap-switch toggles */
|
||||
.checkbox-help {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* CSS only spinner for AJAX requests */
|
||||
|
||||
.spinner {
|
||||
margin: 30px auto 0;
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner > div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #333;
|
||||
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
-webkit-animation: bouncedelay 1.4s infinite ease-in-out;
|
||||
animation: bouncedelay 1.4s infinite ease-in-out;
|
||||
/* Prevent first frame from flickering when animation starts */
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.spinner .bounce1 {
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.spinner .bounce2 {
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes bouncedelay {
|
||||
0%, 80%, 100% { -webkit-transform: scale(0.0) }
|
||||
40% { -webkit-transform: scale(1.0) }
|
||||
}
|
||||
|
||||
@keyframes bouncedelay {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0.0);
|
||||
-webkit-transform: scale(0.0);
|
||||
} 40% {
|
||||
transform: scale(1.0);
|
||||
-webkit-transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
body.stop-scrolling {
|
||||
height: 100%;
|
||||
overflow: hidden; }
|
||||
|
||||
.sweet-overlay {
|
||||
background-color: black;
|
||||
/* IE8 */
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";
|
||||
/* IE8 */
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
position: fixed;
|
||||
left: 0;
|
||||
|
@ -6,11 +14,11 @@
|
|||
top: 0;
|
||||
bottom: 0;
|
||||
display: none;
|
||||
z-index: 1000; }
|
||||
z-index: 10000; }
|
||||
|
||||
.sweet-alert {
|
||||
background-color: white;
|
||||
font-family: sans-serif;
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
width: 478px;
|
||||
padding: 17px;
|
||||
border-radius: 5px;
|
||||
|
@ -22,7 +30,7 @@
|
|||
margin-top: -200px;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
z-index: 2000; }
|
||||
z-index: 99999; }
|
||||
@media all and (max-width: 540px) {
|
||||
.sweet-alert {
|
||||
width: auto;
|
||||
|
@ -36,15 +44,120 @@
|
|||
text-align: center;
|
||||
font-weight: 600;
|
||||
text-transform: none;
|
||||
position: relative; }
|
||||
position: relative;
|
||||
margin: 25px 0;
|
||||
padding: 0;
|
||||
line-height: 40px;
|
||||
display: block; }
|
||||
.sweet-alert p {
|
||||
color: #797979;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
position: relative;
|
||||
text-align: inherit;
|
||||
float: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: normal; }
|
||||
.sweet-alert fieldset {
|
||||
border: none;
|
||||
position: relative; }
|
||||
.sweet-alert .sa-error-container {
|
||||
background-color: #f1f1f1;
|
||||
margin-left: -17px;
|
||||
margin-right: -17px;
|
||||
overflow: hidden;
|
||||
padding: 0 10px;
|
||||
max-height: 0;
|
||||
webkit-transition: padding 0.15s, max-height 0.15s;
|
||||
transition: padding 0.15s, max-height 0.15s; }
|
||||
.sweet-alert .sa-error-container.show {
|
||||
padding: 10px 0;
|
||||
max-height: 100px;
|
||||
webkit-transition: padding 0.2s, max-height 0.2s;
|
||||
transition: padding 0.25s, max-height 0.25s; }
|
||||
.sweet-alert .sa-error-container .icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #ea7d7d;
|
||||
color: white;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
margin-right: 3px; }
|
||||
.sweet-alert .sa-error-container p {
|
||||
display: inline-block; }
|
||||
.sweet-alert .sa-input-error {
|
||||
position: absolute;
|
||||
top: 29px;
|
||||
right: 26px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(0.5);
|
||||
transform: scale(0.5);
|
||||
-webkit-transform-origin: 50% 50%;
|
||||
transform-origin: 50% 50%;
|
||||
-webkit-transition: all 0.1s;
|
||||
transition: all 0.1s; }
|
||||
.sweet-alert .sa-input-error::before, .sweet-alert .sa-input-error::after {
|
||||
content: "";
|
||||
width: 20px;
|
||||
height: 6px;
|
||||
background-color: #f06e57;
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -4px;
|
||||
left: 50%;
|
||||
margin-left: -9px; }
|
||||
.sweet-alert .sa-input-error::before {
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg); }
|
||||
.sweet-alert .sa-input-error::after {
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg); }
|
||||
.sweet-alert .sa-input-error.show {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
.sweet-alert input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #d7d7d7;
|
||||
height: 43px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 17px;
|
||||
font-size: 18px;
|
||||
box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.06);
|
||||
padding: 0 12px;
|
||||
display: none;
|
||||
-webkit-transition: all 0.3s;
|
||||
transition: all 0.3s; }
|
||||
.sweet-alert input:focus {
|
||||
outline: none;
|
||||
box-shadow: 0px 0px 3px #c4e6f5;
|
||||
border: 1px solid #b4dbed; }
|
||||
.sweet-alert input:focus::-moz-placeholder {
|
||||
transition: opacity 0.3s 0.03s ease;
|
||||
opacity: 0.5; }
|
||||
.sweet-alert input:focus:-ms-input-placeholder {
|
||||
transition: opacity 0.3s 0.03s ease;
|
||||
opacity: 0.5; }
|
||||
.sweet-alert input:focus::-webkit-input-placeholder {
|
||||
transition: opacity 0.3s 0.03s ease;
|
||||
opacity: 0.5; }
|
||||
.sweet-alert input::-moz-placeholder {
|
||||
color: #bdbdbd; }
|
||||
.sweet-alert input:-ms-input-placeholder {
|
||||
color: #bdbdbd; }
|
||||
.sweet-alert input::-webkit-input-placeholder {
|
||||
color: #bdbdbd; }
|
||||
.sweet-alert.show-input input {
|
||||
display: block; }
|
||||
.sweet-alert button {
|
||||
background-color: #AEDEF4;
|
||||
color: white;
|
||||
|
@ -52,6 +165,7 @@
|
|||
box-shadow: none;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 5px;
|
||||
padding: 10px 32px;
|
||||
margin: 26px 5px 0 5px;
|
||||
|
@ -71,22 +185,29 @@
|
|||
background-color: #b6b6b6; }
|
||||
.sweet-alert button.cancel:focus {
|
||||
box-shadow: rgba(197, 205, 211, 0.8) 0px 0px 2px, rgba(0, 0, 0, 0.0470588) 0px 0px 0px 1px inset !important; }
|
||||
.sweet-alert button::-moz-focus-inner {
|
||||
border: 0; }
|
||||
.sweet-alert[data-has-cancel-button=false] button {
|
||||
box-shadow: none !important; }
|
||||
.sweet-alert .icon {
|
||||
.sweet-alert[data-has-confirm-button=false][data-has-cancel-button=false] {
|
||||
padding-bottom: 40px; }
|
||||
.sweet-alert .sa-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 4px solid gray;
|
||||
-webkit-border-radius: 40px;
|
||||
border-radius: 40px;
|
||||
border-radius: 50%;
|
||||
margin: 20px auto;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
box-sizing: content-box; }
|
||||
.sweet-alert .icon.error {
|
||||
.sweet-alert .sa-icon.sa-error {
|
||||
border-color: #F27474; }
|
||||
.sweet-alert .icon.error .x-mark {
|
||||
.sweet-alert .sa-icon.sa-error .sa-x-mark {
|
||||
position: relative;
|
||||
display: block; }
|
||||
.sweet-alert .icon.error .line {
|
||||
.sweet-alert .sa-icon.sa-error .sa-line {
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
width: 47px;
|
||||
|
@ -94,37 +215,39 @@
|
|||
display: block;
|
||||
top: 37px;
|
||||
border-radius: 2px; }
|
||||
.sweet-alert .icon.error .line.left {
|
||||
.sweet-alert .sa-icon.sa-error .sa-line.sa-left {
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
left: 17px; }
|
||||
.sweet-alert .icon.error .line.right {
|
||||
.sweet-alert .sa-icon.sa-error .sa-line.sa-right {
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
right: 16px; }
|
||||
.sweet-alert .icon.warning {
|
||||
.sweet-alert .sa-icon.sa-warning {
|
||||
border-color: #F8BB86; }
|
||||
.sweet-alert .icon.warning .body {
|
||||
.sweet-alert .sa-icon.sa-warning .sa-body {
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
height: 47px;
|
||||
left: 50%;
|
||||
top: 10px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
margin-left: -2px;
|
||||
background-color: #F8BB86; }
|
||||
.sweet-alert .icon.warning .dot {
|
||||
.sweet-alert .sa-icon.sa-warning .sa-dot {
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
-webkit-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
margin-left: -3px;
|
||||
left: 50%;
|
||||
bottom: 10px;
|
||||
background-color: #F8BB86; }
|
||||
.sweet-alert .icon.info {
|
||||
.sweet-alert .sa-icon.sa-info {
|
||||
border-color: #C9DAE1; }
|
||||
.sweet-alert .icon.info::before {
|
||||
.sweet-alert .sa-icon.sa-info::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
|
@ -134,7 +257,7 @@
|
|||
border-radius: 2px;
|
||||
margin-left: -2px;
|
||||
background-color: #C9DAE1; }
|
||||
.sweet-alert .icon.info::after {
|
||||
.sweet-alert .sa-icon.sa-info::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
|
@ -143,17 +266,21 @@
|
|||
margin-left: -3px;
|
||||
top: 19px;
|
||||
background-color: #C9DAE1; }
|
||||
.sweet-alert .icon.success {
|
||||
.sweet-alert .sa-icon.sa-success {
|
||||
border-color: #A5DC86; }
|
||||
.sweet-alert .icon.success::before, .sweet-alert .icon.success::after {
|
||||
.sweet-alert .sa-icon.sa-success::before, .sweet-alert .sa-icon.sa-success::after {
|
||||
content: '';
|
||||
-webkit-border-radius: 40px;
|
||||
border-radius: 40px;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 120px;
|
||||
background: white;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg); }
|
||||
.sweet-alert .icon.success::before {
|
||||
.sweet-alert .sa-icon.sa-success::before {
|
||||
-webkit-border-radius: 120px 0 0 120px;
|
||||
border-radius: 120px 0 0 120px;
|
||||
top: -7px;
|
||||
left: -33px;
|
||||
|
@ -161,7 +288,8 @@
|
|||
transform: rotate(-45deg);
|
||||
-webkit-transform-origin: 60px 60px;
|
||||
transform-origin: 60px 60px; }
|
||||
.sweet-alert .icon.success::after {
|
||||
.sweet-alert .sa-icon.sa-success::after {
|
||||
-webkit-border-radius: 0 120px 120px 0;
|
||||
border-radius: 0 120px 120px 0;
|
||||
top: -11px;
|
||||
left: 30px;
|
||||
|
@ -169,17 +297,19 @@
|
|||
transform: rotate(-45deg);
|
||||
-webkit-transform-origin: 0px 60px;
|
||||
transform-origin: 0px 60px; }
|
||||
.sweet-alert .icon.success .placeholder {
|
||||
.sweet-alert .sa-icon.sa-success .sa-placeholder {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 4px solid rgba(165, 220, 134, 0.2);
|
||||
-webkit-border-radius: 40px;
|
||||
border-radius: 40px;
|
||||
border-radius: 50%;
|
||||
box-sizing: content-box;
|
||||
position: absolute;
|
||||
left: -4px;
|
||||
top: -4px;
|
||||
z-index: 2; }
|
||||
.sweet-alert .icon.success .fix {
|
||||
.sweet-alert .sa-icon.sa-success .sa-fix {
|
||||
width: 5px;
|
||||
height: 90px;
|
||||
background-color: white;
|
||||
|
@ -189,26 +319,26 @@
|
|||
z-index: 1;
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg); }
|
||||
.sweet-alert .icon.success .line {
|
||||
.sweet-alert .sa-icon.sa-success .sa-line {
|
||||
height: 5px;
|
||||
background-color: #A5DC86;
|
||||
display: block;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
z-index: 2; }
|
||||
.sweet-alert .icon.success .line.tip {
|
||||
.sweet-alert .sa-icon.sa-success .sa-line.sa-tip {
|
||||
width: 25px;
|
||||
left: 14px;
|
||||
top: 46px;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg); }
|
||||
.sweet-alert .icon.success .line.long {
|
||||
.sweet-alert .sa-icon.sa-success .sa-line.sa-long {
|
||||
width: 47px;
|
||||
right: 8px;
|
||||
top: 38px;
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg); }
|
||||
.sweet-alert .icon.custom {
|
||||
.sweet-alert .sa-icon.sa-custom {
|
||||
background-size: contain;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
|
@ -222,238 +352,274 @@
|
|||
0% {
|
||||
transform: scale(0.7);
|
||||
-webkit-transform: scale(0.7); }
|
||||
|
||||
45% {
|
||||
transform: scale(1.05);
|
||||
-webkit-transform: scale(1.05); }
|
||||
|
||||
80% {
|
||||
transform: scale(0.95);
|
||||
-webkit-tranform: scale(0.95); }
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); } }
|
||||
@-moz-keyframes showSweetAlert {
|
||||
0% {
|
||||
transform: scale(0.7);
|
||||
-webkit-transform: scale(0.7); }
|
||||
45% {
|
||||
transform: scale(1.05);
|
||||
-webkit-transform: scale(1.05); }
|
||||
80% {
|
||||
transform: scale(0.95);
|
||||
-webkit-tranform: scale(0.95); }
|
||||
-webkit-transform: scale(0.95); }
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); } }
|
||||
|
||||
@keyframes showSweetAlert {
|
||||
0% {
|
||||
transform: scale(0.7);
|
||||
-webkit-transform: scale(0.7); }
|
||||
|
||||
45% {
|
||||
transform: scale(1.05);
|
||||
-webkit-transform: scale(1.05); }
|
||||
|
||||
80% {
|
||||
transform: scale(0.95);
|
||||
-webkit-tranform: scale(0.95); }
|
||||
-webkit-transform: scale(0.95); }
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); } }
|
||||
|
||||
@-webkit-keyframes hideSweetAlert {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); }
|
||||
|
||||
100% {
|
||||
transform: scale(0.5);
|
||||
-webkit-transform: scale(0.5); } }
|
||||
@-moz-keyframes hideSweetAlert {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); }
|
||||
100% {
|
||||
transform: scale(0.5);
|
||||
-webkit-transform: scale(0.5); } }
|
||||
|
||||
@keyframes hideSweetAlert {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1); }
|
||||
|
||||
100% {
|
||||
transform: scale(0.5);
|
||||
-webkit-transform: scale(0.5); } }
|
||||
.showSweetAlert {
|
||||
-webkit-animation: showSweetAlert 0.3s;
|
||||
-moz-animation: showSweetAlert 0.3s;
|
||||
animation: showSweetAlert 0.3s; }
|
||||
|
||||
.hideSweetAlert {
|
||||
@-webkit-keyframes slideFromTop {
|
||||
0% {
|
||||
top: 0%; }
|
||||
|
||||
100% {
|
||||
top: 50%; } }
|
||||
|
||||
@keyframes slideFromTop {
|
||||
0% {
|
||||
top: 0%; }
|
||||
|
||||
100% {
|
||||
top: 50%; } }
|
||||
|
||||
@-webkit-keyframes slideToTop {
|
||||
0% {
|
||||
top: 50%; }
|
||||
|
||||
100% {
|
||||
top: 0%; } }
|
||||
|
||||
@keyframes slideToTop {
|
||||
0% {
|
||||
top: 50%; }
|
||||
|
||||
100% {
|
||||
top: 0%; } }
|
||||
|
||||
@-webkit-keyframes slideFromBottom {
|
||||
0% {
|
||||
top: 70%; }
|
||||
|
||||
100% {
|
||||
top: 50%; } }
|
||||
|
||||
@keyframes slideFromBottom {
|
||||
0% {
|
||||
top: 70%; }
|
||||
|
||||
100% {
|
||||
top: 50%; } }
|
||||
|
||||
@-webkit-keyframes slideToBottom {
|
||||
0% {
|
||||
top: 50%; }
|
||||
|
||||
100% {
|
||||
top: 70%; } }
|
||||
|
||||
@keyframes slideToBottom {
|
||||
0% {
|
||||
top: 50%; }
|
||||
|
||||
100% {
|
||||
top: 70%; } }
|
||||
|
||||
.showSweetAlert[data-animation=pop] {
|
||||
-webkit-animation: showSweetAlert 0.3s;
|
||||
animation: showSweetAlert 0.3s; }
|
||||
.showSweetAlert[data-animation=none] {
|
||||
-webkit-animation: none;
|
||||
animation: none; }
|
||||
.showSweetAlert[data-animation=slide-from-top] {
|
||||
-webkit-animation: slideFromTop 0.3s;
|
||||
animation: slideFromTop 0.3s; }
|
||||
.showSweetAlert[data-animation=slide-from-bottom] {
|
||||
-webkit-animation: slideFromBottom 0.3s;
|
||||
animation: slideFromBottom 0.3s; }
|
||||
|
||||
.hideSweetAlert[data-animation=pop] {
|
||||
-webkit-animation: hideSweetAlert 0.2s;
|
||||
-moz-animation: hideSweetAlert 0.2s;
|
||||
animation: hideSweetAlert 0.2s; }
|
||||
.hideSweetAlert[data-animation=none] {
|
||||
-webkit-animation: none;
|
||||
animation: none; }
|
||||
.hideSweetAlert[data-animation=slide-from-top] {
|
||||
-webkit-animation: slideToTop 0.4s;
|
||||
animation: slideToTop 0.4s; }
|
||||
.hideSweetAlert[data-animation=slide-from-bottom] {
|
||||
-webkit-animation: slideToBottom 0.3s;
|
||||
animation: slideToBottom 0.3s; }
|
||||
|
||||
@-webkit-keyframes animateSuccessTip {
|
||||
0% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
|
||||
54% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
|
||||
70% {
|
||||
width: 50px;
|
||||
left: -8px;
|
||||
top: 37px; }
|
||||
|
||||
84% {
|
||||
width: 17px;
|
||||
left: 21px;
|
||||
top: 48px; }
|
||||
|
||||
100% {
|
||||
width: 25px;
|
||||
left: 14px;
|
||||
top: 45px; } }
|
||||
@-moz-keyframes animateSuccessTip {
|
||||
0% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
54% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
70% {
|
||||
width: 50px;
|
||||
left: -8px;
|
||||
top: 37px; }
|
||||
84% {
|
||||
width: 17px;
|
||||
left: 21px;
|
||||
top: 48px; }
|
||||
100% {
|
||||
width: 25px;
|
||||
left: 14px;
|
||||
top: 45px; } }
|
||||
|
||||
@keyframes animateSuccessTip {
|
||||
0% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
|
||||
54% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px; }
|
||||
|
||||
70% {
|
||||
width: 50px;
|
||||
left: -8px;
|
||||
top: 37px; }
|
||||
|
||||
84% {
|
||||
width: 17px;
|
||||
left: 21px;
|
||||
top: 48px; }
|
||||
|
||||
100% {
|
||||
width: 25px;
|
||||
left: 14px;
|
||||
top: 45px; } }
|
||||
|
||||
@-webkit-keyframes animateSuccessLong {
|
||||
0% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
|
||||
65% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
|
||||
84% {
|
||||
width: 55px;
|
||||
right: 0px;
|
||||
top: 35px; }
|
||||
|
||||
100% {
|
||||
width: 47px;
|
||||
right: 8px;
|
||||
top: 38px; } }
|
||||
@-moz-keyframes animateSuccessLong {
|
||||
0% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
65% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
84% {
|
||||
width: 55px;
|
||||
right: 0px;
|
||||
top: 35px; }
|
||||
100% {
|
||||
width: 47px;
|
||||
right: 8px;
|
||||
top: 38px; } }
|
||||
|
||||
@keyframes animateSuccessLong {
|
||||
0% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
|
||||
65% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px; }
|
||||
|
||||
84% {
|
||||
width: 55px;
|
||||
right: 0px;
|
||||
top: 35px; }
|
||||
|
||||
100% {
|
||||
width: 47px;
|
||||
right: 8px;
|
||||
top: 38px; } }
|
||||
|
||||
@-webkit-keyframes rotatePlaceholder {
|
||||
0% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
|
||||
5% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
|
||||
12% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); }
|
||||
|
||||
100% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); } }
|
||||
@-moz-keyframes rotatePlaceholder {
|
||||
0% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
5% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
12% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); }
|
||||
100% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); } }
|
||||
|
||||
@keyframes rotatePlaceholder {
|
||||
0% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
|
||||
5% {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
|
||||
12% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); }
|
||||
|
||||
100% {
|
||||
transform: rotate(-405deg);
|
||||
-webkit-transform: rotate(-405deg); } }
|
||||
|
||||
.animateSuccessTip {
|
||||
-webkit-animation: animateSuccessTip 0.75s;
|
||||
-moz-animation: animateSuccessTip 0.75s;
|
||||
animation: animateSuccessTip 0.75s; }
|
||||
|
||||
.animateSuccessLong {
|
||||
-webkit-animation: animateSuccessLong 0.75s;
|
||||
-moz-animation: animateSuccessLong 0.75s;
|
||||
animation: animateSuccessLong 0.75s; }
|
||||
|
||||
.icon.success.animate::after {
|
||||
.sa-icon.sa-success.animate::after {
|
||||
-webkit-animation: rotatePlaceholder 4.25s ease-in;
|
||||
-moz-animation: rotatePlaceholder 4.25s ease-in;
|
||||
animation: rotatePlaceholder 4.25s ease-in; }
|
||||
|
||||
@-webkit-keyframes animateErrorIcon {
|
||||
|
@ -461,31 +627,25 @@
|
|||
transform: rotateX(100deg);
|
||||
-webkit-transform: rotateX(100deg);
|
||||
opacity: 0; }
|
||||
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
-webkit-transform: rotateX(0deg);
|
||||
opacity: 1; } }
|
||||
@-moz-keyframes animateErrorIcon {
|
||||
0% {
|
||||
transform: rotateX(100deg);
|
||||
-webkit-transform: rotateX(100deg);
|
||||
opacity: 0; }
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
-webkit-transform: rotateX(0deg);
|
||||
opacity: 1; } }
|
||||
|
||||
@keyframes animateErrorIcon {
|
||||
0% {
|
||||
transform: rotateX(100deg);
|
||||
-webkit-transform: rotateX(100deg);
|
||||
opacity: 0; }
|
||||
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
-webkit-transform: rotateX(0deg);
|
||||
opacity: 1; } }
|
||||
|
||||
.animateErrorIcon {
|
||||
-webkit-animation: animateErrorIcon 0.5s;
|
||||
-moz-animation: animateErrorIcon 0.5s;
|
||||
animation: animateErrorIcon 0.5s; }
|
||||
|
||||
@-webkit-keyframes animateXMark {
|
||||
|
@ -494,108 +654,104 @@
|
|||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
|
||||
50% {
|
||||
transform: scale(0.4);
|
||||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
|
||||
80% {
|
||||
transform: scale(1.15);
|
||||
-webkit-transform: scale(1.15);
|
||||
margin-top: -6px; }
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
margin-top: 0;
|
||||
opacity: 1; } }
|
||||
@-moz-keyframes animateXMark {
|
||||
0% {
|
||||
transform: scale(0.4);
|
||||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
50% {
|
||||
transform: scale(0.4);
|
||||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
80% {
|
||||
transform: scale(1.15);
|
||||
-webkit-transform: scale(1.15);
|
||||
margin-top: -6px; }
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
margin-top: 0;
|
||||
opacity: 1; } }
|
||||
|
||||
@keyframes animateXMark {
|
||||
0% {
|
||||
transform: scale(0.4);
|
||||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
|
||||
50% {
|
||||
transform: scale(0.4);
|
||||
-webkit-transform: scale(0.4);
|
||||
margin-top: 26px;
|
||||
opacity: 0; }
|
||||
|
||||
80% {
|
||||
transform: scale(1.15);
|
||||
-webkit-transform: scale(1.15);
|
||||
margin-top: -6px; }
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
margin-top: 0;
|
||||
opacity: 1; } }
|
||||
|
||||
.animateXMark {
|
||||
-webkit-animation: animateXMark 0.5s;
|
||||
-moz-animation: animateXMark 0.5s;
|
||||
animation: animateXMark 0.5s; }
|
||||
|
||||
/*@include keyframes(simpleRotate) {
|
||||
0% { transform: rotateY(0deg); }
|
||||
100% { transform: rotateY(-360deg); }
|
||||
}
|
||||
.simpleRotate {
|
||||
@include animation('simpleRotate 0.75s');
|
||||
}*/
|
||||
@-webkit-keyframes pulseWarning {
|
||||
0% {
|
||||
border-color: #F8D486; }
|
||||
|
||||
100% {
|
||||
border-color: #F8BB86; } }
|
||||
@-moz-keyframes pulseWarning {
|
||||
0% {
|
||||
border-color: #F8D486; }
|
||||
100% {
|
||||
border-color: #F8BB86; } }
|
||||
|
||||
@keyframes pulseWarning {
|
||||
0% {
|
||||
border-color: #F8D486; }
|
||||
|
||||
100% {
|
||||
border-color: #F8BB86; } }
|
||||
|
||||
.pulseWarning {
|
||||
-webkit-animation: pulseWarning 0.75s infinite alternate;
|
||||
-moz-animation: pulseWarning 0.75s infinite alternate;
|
||||
animation: pulseWarning 0.75s infinite alternate; }
|
||||
|
||||
@-webkit-keyframes pulseWarningIns {
|
||||
0% {
|
||||
background-color: #F8D486; }
|
||||
|
||||
100% {
|
||||
background-color: #F8BB86; } }
|
||||
@-moz-keyframes pulseWarningIns {
|
||||
0% {
|
||||
background-color: #F8D486; }
|
||||
100% {
|
||||
background-color: #F8BB86; } }
|
||||
|
||||
@keyframes pulseWarningIns {
|
||||
0% {
|
||||
background-color: #F8D486; }
|
||||
|
||||
100% {
|
||||
background-color: #F8BB86; } }
|
||||
|
||||
.pulseWarningIns {
|
||||
-webkit-animation: pulseWarningIns 0.75s infinite alternate;
|
||||
-moz-animation: pulseWarningIns 0.75s infinite alternate;
|
||||
animation: pulseWarningIns 0.75s infinite alternate; }
|
||||
|
||||
/* Internet Explorer 9 has some special quirks that are fixed here */
|
||||
/* The icons are not animated. */
|
||||
/* This file is automatically merged into sweet-alert.min.js through Gulp */
|
||||
/* Error icon */
|
||||
.sweet-alert .sa-icon.sa-error .sa-line.sa-left {
|
||||
-ms-transform: rotate(45deg) \9; }
|
||||
|
||||
.sweet-alert .sa-icon.sa-error .sa-line.sa-right {
|
||||
-ms-transform: rotate(-45deg) \9; }
|
||||
|
||||
/* Success icon */
|
||||
.sweet-alert .sa-icon.sa-success {
|
||||
border-color: transparent\9; }
|
||||
|
||||
.sweet-alert .sa-icon.sa-success .sa-line.sa-tip {
|
||||
-ms-transform: rotate(45deg) \9; }
|
||||
|
||||
.sweet-alert .sa-icon.sa-success .sa-line.sa-long {
|
||||
-ms-transform: rotate(-45deg) \9; }
|
|
@ -1,4 +1,4 @@
|
|||
</div>
|
||||
<script src='/js/jquery.min.js'></script>
|
||||
<script src='/js/bootstrap.min.js'></script>
|
||||
<script src='/js/domain-server.js'></script>
|
||||
<script src='/js/domain-server.js'></script>
|
||||
|
|
|
@ -3,10 +3,13 @@
|
|||
<head>
|
||||
<title>domain-server</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- Bootstrap -->
|
||||
|
||||
<link href="/css/bootstrap.min.css" rel="stylesheet" media="screen">
|
||||
<link href="/css/style.css" rel="stylesheet" media="screen">
|
||||
<link href="/css/sweet-alert.css" rel="stylesheet" media="screen">
|
||||
<link href="/css/sweetalert.css" rel="stylesheet" media="screen">
|
||||
<link href="/css/bootstrap-switch.min.css" rel="stylesheet" media="screen">
|
||||
<link href="/stats/css/json.human.css" rel="stylesheet" media="screen">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-default" role="navigation">
|
||||
|
@ -21,7 +24,7 @@
|
|||
</button>
|
||||
<a class="navbar-brand" href="/">domain-server</a>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
|
@ -37,4 +40,4 @@
|
|||
</div>
|
||||
</div><!-- /.container-fluid -->
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid">
|
||||
|
|
File diff suppressed because one or more lines are too long
5
domain-server/resources/web/js/query-string.js
Normal file
5
domain-server/resources/web/js/query-string.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
function qs(key) {
|
||||
key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars
|
||||
var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)"));
|
||||
return match && decodeURIComponent(match[1].replace(/\+/g, " "));
|
||||
}
|
|
@ -1,711 +0,0 @@
|
|||
var Settings = {
|
||||
showAdvanced: false,
|
||||
ADVANCED_CLASS: 'advanced-setting',
|
||||
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
||||
DATA_ROW_CLASS: 'value-row',
|
||||
DATA_COL_CLASS: 'value-col',
|
||||
ADD_ROW_BUTTON_CLASS: 'add-row',
|
||||
ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row',
|
||||
DEL_ROW_BUTTON_CLASS: 'del-row',
|
||||
DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row',
|
||||
MOVE_UP_BUTTON_CLASS: 'move-up',
|
||||
MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up',
|
||||
MOVE_DOWN_BUTTON_CLASS: 'move-down',
|
||||
MOVE_DOWN_SPAN_CLASSES: 'glyphicon glyphicon-chevron-down move-down',
|
||||
TABLE_BUTTONS_CLASS: 'buttons',
|
||||
ADD_DEL_BUTTONS_CLASS: 'add-del-buttons',
|
||||
ADD_DEL_BUTTONS_CLASSES: 'buttons add-del-buttons',
|
||||
REORDER_BUTTONS_CLASS: 'reorder-buttons',
|
||||
REORDER_BUTTONS_CLASSES: 'buttons reorder-buttons',
|
||||
NEW_ROW_CLASS: 'new-row'
|
||||
};
|
||||
|
||||
var viewHelpers = {
|
||||
getFormGroup: function(groupName, setting, values, isAdvanced, isLocked) {
|
||||
setting_name = groupName + "." + setting.name
|
||||
|
||||
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "'>"
|
||||
|
||||
if (_.has(values, groupName) && _.has(values[groupName], setting.name)) {
|
||||
setting_value = values[groupName][setting.name]
|
||||
} else if (_.has(setting, 'default')) {
|
||||
setting_value = setting.default
|
||||
} else {
|
||||
setting_value = ""
|
||||
}
|
||||
|
||||
label_class = 'control-label'
|
||||
if (isLocked) {
|
||||
label_class += ' locked'
|
||||
}
|
||||
|
||||
common_attrs = " class='" + (setting.type !== 'checkbox' ? 'form-control' : '')
|
||||
+ " " + Settings.TRIGGER_CHANGE_CLASS + "' data-short-name='" + setting.name + "' name='" + setting_name + "' "
|
||||
+ "id='" + setting_name + "'"
|
||||
|
||||
if (setting.type === 'checkbox') {
|
||||
if (setting.label) {
|
||||
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
|
||||
}
|
||||
form_group += "<div class='checkbox" + (isLocked ? " disabled" : "") + "'>"
|
||||
form_group += "<label for='" + setting_name + "'>"
|
||||
form_group += "<input type='checkbox'" + common_attrs + (setting_value ? "checked" : "") + (isLocked ? " disabled" : "") + "/>"
|
||||
form_group += " " + setting.help + "</label>";
|
||||
form_group += "</div>"
|
||||
} else {
|
||||
input_type = _.has(setting, 'type') ? setting.type : "text"
|
||||
|
||||
if (setting.label) {
|
||||
form_group += "<label for='" + setting_name + "' class='" + label_class + "'>" + setting.label + "</label>";
|
||||
}
|
||||
|
||||
if (input_type === 'table') {
|
||||
form_group += makeTable(setting, setting_name, setting_value, isLocked)
|
||||
} else {
|
||||
if (input_type === 'select') {
|
||||
form_group += "<select class='form-control' data-hidden-input='" + setting_name + "'>'"
|
||||
|
||||
_.each(setting.options, function(option) {
|
||||
form_group += "<option value='" + option.value + "'" +
|
||||
(option.value == setting_value ? 'selected' : '') + ">" + option.label + "</option>"
|
||||
})
|
||||
|
||||
form_group += "</select>"
|
||||
|
||||
form_group += "<input type='hidden'" + common_attrs + "value='" + setting_value + "'>"
|
||||
} else {
|
||||
|
||||
if (input_type == 'integer') {
|
||||
input_type = "text"
|
||||
}
|
||||
|
||||
form_group += "<input type='" + input_type + "'" + common_attrs +
|
||||
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
"' value='" + setting_value + "'" + (isLocked ? " disabled" : "") + "/>"
|
||||
}
|
||||
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
}
|
||||
}
|
||||
|
||||
form_group += "</div>"
|
||||
return form_group
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
/*
|
||||
* Clamped-width.
|
||||
* Usage:
|
||||
* <div data-clampedwidth=".myParent">This long content will force clamped width</div>
|
||||
*
|
||||
* Author: LV
|
||||
*/
|
||||
|
||||
$('[data-clampedwidth]').each(function () {
|
||||
var elem = $(this);
|
||||
var parentPanel = elem.data('clampedwidth');
|
||||
var resizeFn = function () {
|
||||
var sideBarNavWidth = $(parentPanel).width() - parseInt(elem.css('paddingLeft')) - parseInt(elem.css('paddingRight')) - parseInt(elem.css('marginLeft')) - parseInt(elem.css('marginRight')) - parseInt(elem.css('borderLeftWidth')) - parseInt(elem.css('borderRightWidth'));
|
||||
elem.css('width', sideBarNavWidth);
|
||||
};
|
||||
|
||||
resizeFn();
|
||||
$(window).resize(resizeFn);
|
||||
})
|
||||
|
||||
$('#settings-form').on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){
|
||||
addTableRow(this);
|
||||
})
|
||||
|
||||
$('#settings-form').on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){
|
||||
deleteTableRow(this);
|
||||
})
|
||||
|
||||
$('#settings-form').on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){
|
||||
moveTableRow(this, true);
|
||||
})
|
||||
|
||||
$('#settings-form').on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){
|
||||
moveTableRow(this, false);
|
||||
})
|
||||
|
||||
$('#settings-form').on('keypress', 'table input', function(e){
|
||||
if (e.keyCode == 13) {
|
||||
// capture enter in table input
|
||||
// if we have a sibling next to us that has an input, jump to it, otherwise check if we have a glyphicon for add to click
|
||||
sibling = $(this).parent('td').next();
|
||||
|
||||
if (sibling.hasClass(Settings.DATA_COL_CLASS)) {
|
||||
// set focus to next input
|
||||
sibling.find('input').focus()
|
||||
} else if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
|
||||
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click()
|
||||
|
||||
// set focus to the first input in the new row
|
||||
$(this).closest('table').find('tr.inputs input:first').focus()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#settings-form').on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){
|
||||
// this input was changed, add the changed data attribute to it
|
||||
$(this).attr('data-changed', true)
|
||||
|
||||
badgeSidebarForDifferences($(this))
|
||||
})
|
||||
|
||||
$('#advanced-toggle-button').click(function(){
|
||||
Settings.showAdvanced = !Settings.showAdvanced
|
||||
var advancedSelector = $('.' + Settings.ADVANCED_CLASS)
|
||||
|
||||
if (Settings.showAdvanced) {
|
||||
advancedSelector.show()
|
||||
$(this).html("Hide advanced")
|
||||
} else {
|
||||
advancedSelector.hide()
|
||||
$(this).html("Show advanced")
|
||||
}
|
||||
|
||||
$(this).blur()
|
||||
})
|
||||
|
||||
$('#settings-form').on('click', '#choose-domain-btn', function(){
|
||||
chooseFromHighFidelityDomains($(this))
|
||||
})
|
||||
|
||||
$('#settings-form').on('change', 'select', function(){
|
||||
$("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change()
|
||||
})
|
||||
|
||||
var panelsSource = $('#panels-template').html()
|
||||
Settings.panelsTemplate = _.template(panelsSource)
|
||||
|
||||
var sidebarTemplate = $('#list-group-template').html()
|
||||
Settings.sidebarTemplate = _.template(sidebarTemplate)
|
||||
|
||||
// $('body').scrollspy({ target: '#setup-sidebar'})
|
||||
|
||||
reloadSettings()
|
||||
})
|
||||
|
||||
function reloadSettings() {
|
||||
$.getJSON('/settings.json', function(data){
|
||||
_.extend(data, viewHelpers)
|
||||
|
||||
$('.nav-stacked').html(Settings.sidebarTemplate(data))
|
||||
$('#panels').html(Settings.panelsTemplate(data))
|
||||
|
||||
Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
|
||||
|
||||
// add tooltip to locked settings
|
||||
$('label.locked').tooltip({
|
||||
placement: 'right',
|
||||
title: 'This setting is in the master config file and cannot be changed'
|
||||
})
|
||||
|
||||
if (!_.has(data["locked"], "metaverse") && !_.has(data["locked"]["metaverse"], "id")) {
|
||||
// append the domain selection modal, as long as it's not locked
|
||||
appendDomainSelectionModal()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function appendDomainSelectionModal() {
|
||||
var metaverseInput = $("[name='metaverse.id']");
|
||||
var chooseButton = $("<button type='button' id='choose-domain-btn' class='btn btn-primary' style='margin-top:10px'>Choose ID from my domains</button>");
|
||||
metaverseInput.after(chooseButton);
|
||||
}
|
||||
|
||||
var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!";
|
||||
|
||||
$('body').on('click', '.save-button', function(e){
|
||||
// disable any inputs not changed
|
||||
$("input:not([data-changed])").each(function(){
|
||||
$(this).prop('disabled', true);
|
||||
});
|
||||
|
||||
// grab a JSON representation of the form via form2js
|
||||
var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true);
|
||||
|
||||
console.log(formJSON);
|
||||
|
||||
// re-enable all inputs
|
||||
$("input").each(function(){
|
||||
$(this).prop('disabled', false);
|
||||
});
|
||||
|
||||
// remove focus from the button
|
||||
$(this).blur()
|
||||
|
||||
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
|
||||
$.ajax('/settings.json', {
|
||||
data: JSON.stringify(formJSON),
|
||||
contentType: 'application/json',
|
||||
type: 'POST'
|
||||
}).done(function(data){
|
||||
if (data.status == "success") {
|
||||
showRestartModal();
|
||||
} else {
|
||||
showErrorMessage("Error", SETTINGS_ERROR_MESSAGE)
|
||||
reloadSettings();
|
||||
}
|
||||
}).fail(function(){
|
||||
showErrorMessage("Error", SETTINGS_ERROR_MESSAGE)
|
||||
reloadSettings();
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
function makeTable(setting, setting_name, setting_value, isLocked) {
|
||||
var isArray = !_.has(setting, 'key')
|
||||
|
||||
if (!isArray && setting.can_order) {
|
||||
setting.can_order = false;
|
||||
}
|
||||
|
||||
var html = "<span class='help-block'>" + setting.help + "</span>"
|
||||
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name + "' name='" + setting_name
|
||||
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>"
|
||||
|
||||
// Column names
|
||||
html += "<tr class='headers'>"
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='number'><strong>#</strong></td>" // Row number
|
||||
}
|
||||
|
||||
if (setting.key) {
|
||||
html += "<td class='key'><strong>" + setting.key.label + "</strong></td>" // Key
|
||||
}
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='data'><strong>" + col.label + "</strong></td>" // Data
|
||||
})
|
||||
|
||||
if (!isLocked) {
|
||||
if (setting.can_order) {
|
||||
html += "<td class=" + Settings.REORDER_BUTTONS_CLASSES +
|
||||
"><span class='glyphicon glyphicon-sort'></span></td>";
|
||||
}
|
||||
html += "<td class=" + Settings.ADD_DEL_BUTTONS_CLASSES + "></td></tr>"
|
||||
}
|
||||
|
||||
// populate rows in the table from existing values
|
||||
var row_num = 1
|
||||
|
||||
_.each(setting_value, function(row, indexOrName) {
|
||||
html += "<tr class='" + Settings.DATA_ROW_CLASS + "'" + (isArray ? "" : "name='" + setting_name + "." + indexOrName + "'") + ">"
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'>" + row_num + "</td>"
|
||||
}
|
||||
|
||||
if (setting.key) {
|
||||
html += "<td class='key'>" + indexOrName + "</td>"
|
||||
}
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'>"
|
||||
|
||||
if (isArray) {
|
||||
rowIsObject = setting.columns.length > 1
|
||||
colValue = rowIsObject ? row[col.name] : row
|
||||
html += colValue
|
||||
|
||||
// for arrays we add a hidden input to this td so that values can be posted appropriately
|
||||
html += "<input type='hidden' name='" + setting_name + "[" + indexOrName + "]"
|
||||
+ (rowIsObject ? "." + col.name : "") + "' value='" + colValue + "'/>"
|
||||
} else if (row.hasOwnProperty(col.name)) {
|
||||
html += row[col.name]
|
||||
}
|
||||
|
||||
html += "</td>"
|
||||
})
|
||||
|
||||
if (!isLocked) {
|
||||
if (setting.can_order) {
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES+
|
||||
"'><span class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></span><span class='" +
|
||||
Settings.MOVE_DOWN_SPAN_CLASSES + "'></span></td>"
|
||||
}
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
||||
"'><span class='" + Settings.DEL_ROW_SPAN_CLASSES + "'></span></td>"
|
||||
}
|
||||
|
||||
html += "</tr>"
|
||||
|
||||
row_num++
|
||||
})
|
||||
|
||||
// populate inputs in the table for new values
|
||||
if (!isLocked) {
|
||||
html += makeTableInputs(setting)
|
||||
}
|
||||
html += "</table>"
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeTableInputs(setting) {
|
||||
var html = "<tr class='inputs'>"
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'></td>"
|
||||
}
|
||||
|
||||
if (setting.key) {
|
||||
html += "<td class='key' name='" + setting.key.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (_.has(setting.key, 'placeholder') ? setting.key.placeholder : "") + "' value=''>\
|
||||
</td>"
|
||||
}
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
||||
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
||||
</td>"
|
||||
})
|
||||
|
||||
if (setting.can_order) {
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
|
||||
}
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
||||
"'><span class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></span></td>"
|
||||
html += "</tr>"
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
function badgeSidebarForDifferences(changedElement) {
|
||||
// figure out which group this input is in
|
||||
var panelParentID = changedElement.closest('.panel').attr('id')
|
||||
|
||||
// get a JSON representation of that section
|
||||
var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID]
|
||||
var initialPanelJSON = Settings.initialValues[panelParentID]
|
||||
|
||||
var badgeValue = 0
|
||||
|
||||
// badge for any settings we have that are not the same or are not present in initialValues
|
||||
for (var setting in panelJSON) {
|
||||
if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") ||
|
||||
(!_.isEqual(panelJSON[setting], initialPanelJSON[setting])
|
||||
&& (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) {
|
||||
badgeValue += 1
|
||||
}
|
||||
}
|
||||
|
||||
// update the list-group-item badge to have the new value
|
||||
if (badgeValue == 0) {
|
||||
badgeValue = ""
|
||||
}
|
||||
|
||||
$("a[href='#" + panelParentID + "'] .badge").html(badgeValue);
|
||||
}
|
||||
|
||||
function addTableRow(add_glyphicon) {
|
||||
var row = $(add_glyphicon).closest('tr')
|
||||
|
||||
var table = row.parents('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS)
|
||||
|
||||
if (!isArray) {
|
||||
// Check key spaces
|
||||
var key = row.children(".key").children("input").val()
|
||||
if (key.indexOf(' ') !== -1) {
|
||||
showErrorMessage("Error", "Key contains spaces")
|
||||
return
|
||||
}
|
||||
// Check keys with the same name
|
||||
var equals = false;
|
||||
_.each(columns.children(".key"), function(element) {
|
||||
if ($(element).text() === key) {
|
||||
equals = true
|
||||
return
|
||||
}
|
||||
})
|
||||
if (equals) {
|
||||
showErrorMessage("Error", "Two keys cannot be identical")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check empty fields
|
||||
var empty = false;
|
||||
_.each(row.children('.' + Settings.DATA_COL_CLASS + ' input'), function(element) {
|
||||
if ($(element).val().length === 0) {
|
||||
empty = true
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
if (empty) {
|
||||
showErrorMessage("Error", "Empty field(s)")
|
||||
return
|
||||
}
|
||||
|
||||
var input_clone = row.clone()
|
||||
|
||||
// Change input row to data row
|
||||
var table = row.parents("table")
|
||||
var setting_name = table.attr("name")
|
||||
var full_name = setting_name + "." + key
|
||||
row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS)
|
||||
row.removeClass("inputs")
|
||||
|
||||
_.each(row.children(), function(element) {
|
||||
if ($(element).hasClass("numbered")) {
|
||||
// Index row
|
||||
var numbers = columns.children(".numbered")
|
||||
if (numbers.length > 0) {
|
||||
$(element).html(parseInt(numbers.last().text()) + 1)
|
||||
} else {
|
||||
$(element).html(1)
|
||||
}
|
||||
} else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) {
|
||||
$(element).html("<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'><span class='" + Settings.MOVE_UP_SPAN_CLASSES +
|
||||
"'></span><span class='" + Settings.MOVE_DOWN_SPAN_CLASSES + "'></span></td>")
|
||||
} else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
|
||||
// Change buttons
|
||||
var span = $(element).children("span")
|
||||
span.removeClass(Settings.ADD_ROW_SPAN_CLASSES)
|
||||
span.addClass(Settings.DEL_ROW_SPAN_CLASSES)
|
||||
} else if ($(element).hasClass("key")) {
|
||||
var input = $(element).children("input")
|
||||
$(element).html(input.val())
|
||||
input.remove()
|
||||
} else if ($(element).hasClass(Settings.DATA_COL_CLASS)) {
|
||||
// Hide inputs
|
||||
var input = $(element).children("input")
|
||||
input.attr("type", "hidden")
|
||||
|
||||
if (isArray) {
|
||||
var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length
|
||||
var key = $(element).attr('name')
|
||||
|
||||
// are there multiple columns or just one?
|
||||
// with multiple we have an array of Objects, with one we have an array of whatever the value type is
|
||||
var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length
|
||||
input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
|
||||
} else {
|
||||
input.attr("name", full_name + "." + $(element).attr("name"))
|
||||
}
|
||||
|
||||
input.attr("data-changed", "true")
|
||||
|
||||
$(element).append(input.val())
|
||||
} else {
|
||||
console.log("Unknown table element")
|
||||
}
|
||||
})
|
||||
|
||||
input_clone.find('input').each(function(){
|
||||
$(this).val($(this).attr('data-default'));
|
||||
});
|
||||
|
||||
if (isArray) {
|
||||
updateDataChangedForSiblingRows(row, true)
|
||||
|
||||
// the addition of any table row should remove the empty-array-row
|
||||
row.siblings('.empty-array-row').remove()
|
||||
}
|
||||
|
||||
badgeSidebarForDifferences($(table))
|
||||
|
||||
row.parent().append(input_clone)
|
||||
}
|
||||
|
||||
function deleteTableRow(delete_glyphicon) {
|
||||
var row = $(delete_glyphicon).closest('tr')
|
||||
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
row.empty();
|
||||
|
||||
if (!isArray) {
|
||||
row.html("<input type='hidden' class='form-control' name='"
|
||||
+ row.attr('name') + "' data-changed='true' value=''>");
|
||||
} else {
|
||||
if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
|
||||
updateDataChangedForSiblingRows(row)
|
||||
|
||||
// this isn't the last row - we can just remove it
|
||||
row.remove()
|
||||
} else {
|
||||
// this is the last row, we can't remove it completely since we need to post an empty array
|
||||
|
||||
row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS)
|
||||
row.addClass('empty-array-row')
|
||||
|
||||
row.html("<input type='hidden' class='form-control' name='" + table.attr("name").replace('[]', '')
|
||||
+ "' data-changed='true' value=''>");
|
||||
}
|
||||
}
|
||||
|
||||
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
|
||||
badgeSidebarForDifferences($(table))
|
||||
}
|
||||
|
||||
function moveTableRow(move_glyphicon, move_up) {
|
||||
var row = $(move_glyphicon).closest('tr')
|
||||
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
if (!isArray) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (move_up) {
|
||||
var prev_row = row.prev()
|
||||
if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) {
|
||||
prev_row.before(row)
|
||||
}
|
||||
} else {
|
||||
var next_row = row.next()
|
||||
if (next_row.hasClass(Settings.DATA_ROW_CLASS)) {
|
||||
next_row.after(row)
|
||||
}
|
||||
}
|
||||
|
||||
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
|
||||
badgeSidebarForDifferences($(table))
|
||||
}
|
||||
|
||||
function updateDataChangedForSiblingRows(row, forceTrue) {
|
||||
// anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true
|
||||
// unless it matches the inital set of values
|
||||
|
||||
if (!forceTrue) {
|
||||
// figure out which group this row is in
|
||||
var panelParentID = row.closest('.panel').attr('id')
|
||||
// get the short name for the setting from the table
|
||||
var tableShortName = row.closest('table').data('short-name')
|
||||
|
||||
// get a JSON representation of that section
|
||||
var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName]
|
||||
var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName]
|
||||
|
||||
// if they are equal, we don't need data-changed
|
||||
isTrue = !_.isEqual(panelSettingJSON, initialPanelSettingJSON)
|
||||
} else {
|
||||
isTrue = true
|
||||
}
|
||||
|
||||
row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){
|
||||
var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input')
|
||||
if (isTrue) {
|
||||
hiddenInput.attr('data-changed', isTrue)
|
||||
} else {
|
||||
hiddenInput.removeAttr('data-changed')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function showRestartModal() {
|
||||
$('#restart-modal').modal({
|
||||
backdrop: 'static',
|
||||
keyboard: false
|
||||
});
|
||||
|
||||
var secondsElapsed = 0;
|
||||
var numberOfSecondsToWait = 3;
|
||||
|
||||
var refreshSpan = $('span#refresh-time')
|
||||
refreshSpan.html(numberOfSecondsToWait + " seconds");
|
||||
|
||||
// call ourselves every 1 second to countdown
|
||||
var refreshCountdown = setInterval(function(){
|
||||
secondsElapsed++;
|
||||
secondsLeft = numberOfSecondsToWait - secondsElapsed
|
||||
refreshSpan.html(secondsLeft + (secondsLeft == 1 ? " second" : " seconds"))
|
||||
|
||||
if (secondsElapsed == numberOfSecondsToWait) {
|
||||
location.reload(true);
|
||||
clearInterval(refreshCountdown);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function cleanupFormValues(node) {
|
||||
if (node.type && node.type === 'checkbox') {
|
||||
return { name: node.name, value: node.checked ? true : false };
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showErrorMessage(title, message) {
|
||||
swal(title, message)
|
||||
}
|
||||
|
||||
function chooseFromHighFidelityDomains(clickedButton) {
|
||||
// setup the modal to help user pick their domain
|
||||
if (Settings.initialValues.metaverse.access_token) {
|
||||
|
||||
// add a spinner to the choose button
|
||||
clickedButton.html("Loading domains...")
|
||||
clickedButton.attr('disabled', 'disabled')
|
||||
|
||||
// get a list of user domains from data-web
|
||||
data_web_domains_url = "https://metaverse.highfidelity.com/api/v1/domains?access_token="
|
||||
$.getJSON(data_web_domains_url + Settings.initialValues.metaverse.access_token, function(data){
|
||||
|
||||
modal_buttons = {
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-default'
|
||||
}
|
||||
}
|
||||
|
||||
if (data.data.domains.length) {
|
||||
// setup a select box for the returned domains
|
||||
modal_body = "<p>Choose the High Fidelity domain you want this domain-server to represent.<br/>This will set your domain ID on the settings page.</p>"
|
||||
domain_select = $("<select id='domain-name-select' class='form-control'></select>")
|
||||
_.each(data.data.domains, function(domain){
|
||||
domain_select.append("<option value='" + domain.id + "'>(" + domain.id + ")" + (domain.names.length > 0 ? " [" + domain.names + "]" : "") + "</option>");
|
||||
})
|
||||
modal_body += "<label for='domain-name-select'>Domains</label>" + domain_select[0].outerHTML
|
||||
modal_buttons["success"] = {
|
||||
label: 'Choose domain',
|
||||
callback: function() {
|
||||
domainID = $('#domain-name-select').val()
|
||||
// set the domain ID on the form
|
||||
$("[name='metaverse.id']").val(domainID).change();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
modal_buttons["success"] = {
|
||||
label: 'Create new domain',
|
||||
callback: function() {
|
||||
window.open("https://metaverse.highfidelity.com/user/domains", '_blank');
|
||||
}
|
||||
}
|
||||
modal_body = "<p>You do not have any domains in your High Fidelity account." +
|
||||
"<br/><br/>Go to your domains page to create a new one. Once your domain is created re-open this dialog to select it.</p>"
|
||||
}
|
||||
|
||||
|
||||
bootbox.dialog({
|
||||
title: "Choose matching domain",
|
||||
message: modal_body,
|
||||
buttons: modal_buttons
|
||||
})
|
||||
|
||||
// remove the spinner from the choose button
|
||||
clickedButton.html("Choose from my domains")
|
||||
clickedButton.removeAttr('disabled')
|
||||
})
|
||||
|
||||
} else {
|
||||
bootbox.alert({
|
||||
message: "You must have an access token to query your High Fidelity domains.<br><br>" +
|
||||
"Please follow the instructions on the settings page to add an access token.",
|
||||
title: "Access token required"
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
$(document).ready(function(){
|
||||
/*
|
||||
* Clamped-width.
|
||||
* Usage:
|
||||
* <div data-clampedwidth=".myParent">This long content will force clamped width</div>
|
||||
*
|
||||
* Author: LV
|
||||
*/
|
||||
|
||||
$('[data-clampedwidth]').each(function () {
|
||||
var elem = $(this);
|
||||
var parentPanel = elem.data('clampedwidth');
|
||||
var resizeFn = function () {
|
||||
var sideBarNavWidth = $(parentPanel).width() - parseInt(elem.css('paddingLeft')) - parseInt(elem.css('paddingRight')) - parseInt(elem.css('marginLeft')) - parseInt(elem.css('marginRight')) - parseInt(elem.css('borderLeftWidth')) - parseInt(elem.css('borderRightWidth'));
|
||||
elem.css('width', sideBarNavWidth);
|
||||
};
|
||||
|
||||
resizeFn();
|
||||
$(window).resize(resizeFn);
|
||||
});
|
||||
|
||||
|
||||
var listSource = $('#list-group-template').html();
|
||||
var listTemplate = _.template(listSource);
|
||||
|
||||
reloadSettings();
|
||||
|
||||
function reloadSettings() {
|
||||
$.getJSON('describe-setup.json', function(data){
|
||||
$('.list-group').html(listTemplate(data));
|
||||
});
|
||||
}
|
||||
});
|
File diff suppressed because one or more lines are too long
1
domain-server/resources/web/js/underscore-keypath.min.js
vendored
Normal file
1
domain-server/resources/web/js/underscore-keypath.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
!function(){function t(t,r,e,n){return(0!==n?".":"")+r.replace(/\./g,"«dot»")}function r(r){"use strict";if("object"==typeof r&&"Array"===r.constructor.name)return Array.prototype.slice.call(r);if("string"==typeof r){r=r.replace(/\.\./g,"«dot»"),r=r.replace(/\['(([^']|\\')*)'\]/g,t),r=r.replace(/\["(([^"]|\\")*)"\]/g,t),r=r.replace(/([$A-Za-z_][0-9A-Za-z_$]*)\(\)/,"$1");var e=r.split(".");return e=s(e).map(function(t){return t.replace(/«dot»/g,".")})}throw new Error("keypath must be an array or a string")}function e(t){"use strict";return t=null===t?"":String(t),t.charAt(0).toUpperCase()+t.slice(1)}function n(t,r){"use strict";var e=y[r];return e?e(t):void 0}function u(t,r,e){"use strict";if(!(t instanceof Array))return void 0;var n=l[r];return n?n(t,e):void 0}function i(t,r){"use strict";if(""===r)return t;if(0===r.indexOf("@"))return n(t,r);var u=t["get"+e(r)];return void 0===u&&(u=t["is"+e(r)]),void 0===u&&(u=t[r]),"function"==typeof u&&(u=u.call(t)),u}function o(t,r){"use strict";return""===r?!0:"function"==typeof t["get"+e(r)]||"function"==typeof t["is"+e(r)]||s.has(t,r)}function a(t,r,n){"use strict";var i=t["set"+e(r)];return i?i.call(t,n):0===r.indexOf("@")?u(t,r,n):(t[r]=n,n)}function f(t,r){"use strict";var e,n,u=!0;for(e in r)if(r.hasOwnProperty(e)&&(n=r[e],s(t).valueForKeyPath(e)!==n)){u=!1;break}return u}var s;"undefined"!=typeof _&&(s=_);var c,y,l,p,d,h=this;if(c="undefined"!=typeof module&&"undefined"!=typeof require,"undefined"==typeof s&&(s=c?require("underscore"):h.underscore),"undefined"==typeof s)throw new Error("underscore.js is not found!");y={"@first":function(t){return s.first(t)},"@last":function(t){return s.last(t)},"@max":function(t){return s.max(t)},"@min":function(t){return s.min(t)},"@size":function(t){return s.size(t)}},l={"@first":function(t,r){return t[0]=r,r},"@last":function(t,r){return t[Math.max(t.length-1,0)]=r,r}},p={valueForKeyPath:function(t,e,n){"use strict";var u,o,a,f=t;if(null===t||void 0===t)return n;for(u=r(e),o=u.shift(),a=void 0;null!==f&&void 0!==f&&"string"==typeof o;)a=f,f=i(f,o),"function"==typeof f&&(f=f.call(a)),o=u.shift();return null===f||void 0===f?n:f},setValueForKeyPath:function(t,e,n){"use strict";var u,i,o=r(e);return u=o.pop(),i=s(t).valueForKeyPath(o.join(".")),null!==i&&void 0!==i?a(i,u,n):void 0},pluckByKeyPath:function(t,r){"use strict";var e,n,u=[];for(e=0;e<t.length;e+=1)n=t[e],u[e]=s(n).valueForKeyPath(r);return u},whereByKeyPath:function(t,r){"use strict";var e,n,u=[];for(e=0;e<t.length;e+=1)n=t[e],f(n,r)&&u.push(n);return u},findWhereByKeyPath:function(t,r){"use strict";var e,n;for(e=0;e<t.length;e+=1)if(n=t[e],f(n,r))return n;return null},sortByKeyPath:function(t,r){"use strict";return"string"!=typeof r?s.sortBy(t,r):s.sortBy(t,function(t){return s.valueForKeyPath(t,r)})},groupByKeyPath:function(t,r){"use strict";return"string"!=typeof r?s.groupBy(t,r):s.groupBy(t,function(t){return s.valueForKeyPath(t,r)})},indexByKeyPath:function(t,r){"use strict";return"string"!=typeof r?s.indexBy(t,r):s.indexBy(t,function(t){return s.valueForKeyPath(t,r)})},countByKeyPath:function(t,r){"use strict";return"string"!=typeof r?s.countBy(t,r):s.countBy(t,function(t){return s.valueForKeyPath(t,r)})},hasKeyPath:function(t,e){"use strict";var n,u,i;return null===t||void 0===t?!1:(n=r(e),u=n.pop(),i=this.valueForKeyPath(t,n),null!==i&&void 0!==i?o(i,u):!1)}},d={getValueForKeyPath:p.valueForKeyPath},s.mixin(p),s.mixin(d),c&&(module.exports=s,module.exports.__debug__={toSegments:r})}();
|
File diff suppressed because one or more lines are too long
|
@ -1,68 +1,82 @@
|
|||
<!--#include virtual="header.html"-->
|
||||
|
||||
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<div class="col-md-12">
|
||||
<div class="alert" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
|
||||
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col" class="hidden-xs" data-spy="affix" data-offset-top="55">
|
||||
<script id="list-group-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<li>
|
||||
<a href="#<%-group.name %>" class="list-group-item">
|
||||
<span class="badge"></span>
|
||||
<%- group.label %>
|
||||
</a>
|
||||
</li>
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
|
||||
<button id="advanced-toggle-button" hidden=true class="btn btn-info">Show advanced</button>
|
||||
<button class="btn btn-success save-button">Save and restart</button>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9 col-sm-9 col-xs-12">
|
||||
<form id="settings-form" role="form">
|
||||
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
|
||||
<% isAdvanced = _.isEmpty(split_settings[0]) %>
|
||||
<% if (isAdvanced) { %>
|
||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
||||
<% } %>
|
||||
<div class="panel panel-default <%- (isAdvanced) ? 'advanced-setting' : '' %>" id="<%- group.name %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<%= getFormGroup(group.name, setting, values, false,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<% }); %>
|
||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
||||
<% $("#advanced-toggle-button").show() %>
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<%= getFormGroup(group.name, setting, values, true,
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
|
||||
<div id="setup-sidebar" class="hidden-xs" data-spy="affix" data-offset-top="55" data-clampedwidth="#setup-sidebar-col">
|
||||
<script id="list-group-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% panelID = group.name ? group.name : group.label %>
|
||||
<li>
|
||||
<a href="#<%- panelID %>" class="list-group-item">
|
||||
<span class="badge"></span>
|
||||
<%- group.label %>
|
||||
</a>
|
||||
</li>
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
|
||||
<button id="advanced-toggle-button" hidden=true class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
<button class="btn btn-success save-button">Save and restart</button>
|
||||
</div>
|
||||
</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">
|
||||
<form id="settings-form" role="form">
|
||||
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
|
||||
<% isAdvanced = _.isEmpty(split_settings[0]) %>
|
||||
<% if (isAdvanced) { %>
|
||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
||||
<% } %>
|
||||
|
||||
<% isGrouped = !!group.name %>
|
||||
<% panelID = isGrouped ? group.name : group.html_id %>
|
||||
|
||||
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
|
||||
id="<%- panelID %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
||||
<% $("#advanced-toggle-button").show() %>
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, true,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
<div id="panels"></div>
|
||||
|
||||
</form>
|
||||
<% }); %>
|
||||
</script>
|
||||
<div id="panels"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-xs-12 hidden-sm hidden-md hidden-lg">
|
||||
<button class="btn btn-success save-button" id="small-save-button">Save and restart</button>
|
||||
</div>
|
||||
|
@ -83,8 +97,10 @@
|
|||
|
||||
<!--#include virtual="footer.html"-->
|
||||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
<script src='/js/sweet-alert.min.js'></script>
|
||||
<script src='/js/settings.js'></script>
|
||||
<script src='/js/form2js.min.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
||||
<script src='js/bootstrap-switch.min.js'></script>
|
||||
<script src='js/sweetalert.min.js'></script>
|
||||
<script src='js/settings.js'></script>
|
||||
<script src='js/form2js.min.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|
22
domain-server/resources/web/settings/js/bootstrap-switch.min.js
vendored
Executable file
22
domain-server/resources/web/settings/js/bootstrap-switch.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
1203
domain-server/resources/web/settings/js/settings.js
Normal file
1203
domain-server/resources/web/settings/js/settings.js
Normal file
File diff suppressed because it is too large
Load diff
1
domain-server/resources/web/settings/js/sweetalert.min.js
vendored
Executable file
1
domain-server/resources/web/settings/js/sweetalert.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
115
domain-server/resources/web/stats/css/json.human.css
Executable file
115
domain-server/resources/web/stats/css/json.human.css
Executable file
|
@ -0,0 +1,115 @@
|
|||
.jh-root, .jh-type-object, .jh-type-array, .jh-key, .jh-value, .jh-root tr {
|
||||
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.jh-key, .jh-value {
|
||||
margin: 0;
|
||||
padding: 0.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.jh-value {
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.jh-type-number {
|
||||
text-align: center;
|
||||
color: #5286BC;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jh-type-bool-true {
|
||||
text-align: center;
|
||||
color: #5A811C;
|
||||
}
|
||||
|
||||
.jh-type-bool-false {
|
||||
text-align: center;
|
||||
color: #D45317;
|
||||
}
|
||||
|
||||
.jh-type-bool-image {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 5px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.jh-type-string {
|
||||
font-style: italic;
|
||||
color: #6E6E6E;
|
||||
}
|
||||
|
||||
.jh-array-key {
|
||||
font-style: italic;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jh-object-key, .jh-array-key {
|
||||
color: #444;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.jh-type-object tr:nth-child(odd), .jh-type-array tr:nth-child(odd) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.jh-type-object tr:nth-child(even), .jh-type-array tr:nth-child(even) {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.jh-type-object, .jh-type-array {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.jh-root {
|
||||
border: 1px solid #ccc;
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
th.jh-key {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th.jh-key.stale {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.jh-type-object tr, .jh-type-array tr {
|
||||
border: 1px solid #ddd;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.jh-type-object tr:last-child, .jh-type-array tr:last-child {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.jh-type-object tr:hover, .jh-type-array tr:hover {
|
||||
border: 1px solid #F99927;
|
||||
}
|
||||
|
||||
.jh-empty {
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.jh-a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.jh-a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.jh-a span.jh-type-string {
|
||||
text-decoration: none;
|
||||
color : #268ddd;
|
||||
font-style: normal;
|
||||
}
|
||||
|
|
@ -1,6 +1,15 @@
|
|||
<!--#include virtual="header.html"-->
|
||||
<div id="stats-lead" class="table-lead"><h3>Stats</h3><div class="lead-line"></div></div>
|
||||
<table id="stats-table" class="table"><tbody></tbody></table>
|
||||
<div class="col-xs-12 table-lead" id="stats-lead"><h3>Stats</h3><div class="lead-line"></div></div>
|
||||
<div class="col-xs-12">
|
||||
<p class="help"><em>Click on any of the numerical values in the tables below to view a line graph of the incoming values.</em></p>
|
||||
</div>
|
||||
<div class="col-xs-12" id="stats-container"></div>
|
||||
<!--#include virtual="footer.html"-->
|
||||
<script src='/js/query-string.js'></script>
|
||||
<script src='js/stats.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
||||
<script src='js/json.human.js'></script>
|
||||
<script src='js/highcharts-custom.js'></script>
|
||||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|
247
domain-server/resources/web/stats/js/highcharts-custom.js
Normal file
247
domain-server/resources/web/stats/js/highcharts-custom.js
Normal file
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
Highcharts 4.0.4 JS v/Highstock 2.0.4 (2014-09-02)
|
||||
|
||||
(c) 2009-2014 Torstein Honsi
|
||||
|
||||
License: www.highcharts.com/license
|
||||
*/
|
||||
(function(){function v(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function G(){var a,b=arguments,c,d={},e=function(a,b){var c,d;"object"!==typeof a&&(a={});for(d in b)b.hasOwnProperty(d)&&((c=b[d])&&"object"===typeof c&&"[object Array]"!==Object.prototype.toString.call(c)&&"renderTo"!==d&&"number"!==typeof c.nodeType?a[d]=e(a[d]||{},c):a[d]=b[d]);return a};!0===b[0]&&(d=b[1],b=Array.prototype.slice.call(b,2));c=b.length;for(a=0;a<c;a++)d=e(d,b[a]);return d}function E(a,b){return parseInt(a,
|
||||
b||10)}function pa(a){return"string"===typeof a}function da(a){return a&&"object"===typeof a}function ta(a){return"[object Array]"===Object.prototype.toString.call(a)}function ga(a){return"number"===typeof a}function ab(a){return R.log(a)/R.LN10}function ia(a){return R.pow(10,a)}function qa(a,b){for(var c=a.length;c--;)if(a[c]===b){a.splice(c,1);break}}function w(a){return a!==u&&null!==a}function N(a,b,c){var d,e;if(pa(b))w(c)?a.setAttribute(b,c):a&&a.getAttribute&&(e=a.getAttribute(b));else if(w(b)&&
|
||||
da(b))for(d in b)a.setAttribute(d,b[d]);return e}function ea(a){return ta(a)?a:[a]}function n(){var a=arguments,b,c,d=a.length;for(b=0;b<d;b++)if(c=a[b],c!==u&&null!==c)return c}function J(a,b){ra&&!Z&&b&&b.opacity!==u&&(b.filter="alpha(opacity="+100*b.opacity+")");v(a.style,b)}function ja(a,b,c,d,e){a=y.createElement(a);b&&v(a,b);e&&J(a,{padding:0,border:"none",margin:0});c&&J(a,c);d&&d.appendChild(a);return a}function nb(a,b){var c=function(){return u};c.prototype=new a;v(c.prototype,b);return c}
|
||||
function ka(a,b,c,d){var e=U.numberFormat,f=M.lang,g=+a||0,h=-1===b?(g.toString().split(".")[1]||"").length:isNaN(b=X(b))?2:b,k=void 0===c?f.decimalPoint:c,f=void 0===d?f.thousandsSep:d,l=0>g?"-":"",m=String(E(g=X(g).toFixed(h))),q=3<m.length?m.length%3:0;return e!==ka?e(a,b,c,d):l+(q?m.substr(0,q)+f:"")+m.substr(q).replace(/(\d{3})(?=\d)/g,"$1"+f)+(h?k+X(g-m).toFixed(h).slice(2):"")}function sa(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function bb(a,b,c){var d=a[b];a[b]=function(){var a=
|
||||
Array.prototype.slice.call(arguments);a.unshift(d);return c.apply(this,a)}}function ua(a,b){for(var c="{",d=!1,e,f,g,h,k,l=[];-1!==(c=a.indexOf(c));){e=a.slice(0,c);if(d){f=e.split(":");g=f.shift().split(".");k=g.length;e=b;for(h=0;h<k;h++)e=e[g[h]];f.length&&(f=f.join(":"),g=/\.([0-9])/,h=M.lang,k=void 0,/f$/.test(f)?(k=(k=f.match(g))?k[1]:-1,null!==e&&(e=ka(e,k,h.decimalPoint,-1<f.indexOf(",")?h.thousandsSep:""))):e=Ka(f,e))}l.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}l.push(a);return l.join("")}
|
||||
function ob(a,b,c,d){var e;c=n(c,1);e=a/c;b||(b=[1,2,2.5,5,10],!1===d&&(1===c?b=[1,2,5,10]:.1>=c&&(b=[1/c])));for(d=0;d<b.length&&!(a=b[d],e<=(b[d]+(b[d+1]||b[d]))/2);d++);return a*c}function cb(a,b){var c=a.length,d,e;for(e=0;e<c;e++)a[e].ss_i=e;a.sort(function(a,c){d=b(a,c);return 0===d?a.ss_i-c.ss_i:d});for(e=0;e<c;e++)delete a[e].ss_i}function La(a){for(var b=a.length,c=a[0];b--;)a[b]<c&&(c=a[b]);return c}function va(a){for(var b=a.length,c=a[0];b--;)a[b]>c&&(c=a[b]);return c}function Ma(a,b){for(var c in a)a[c]&&
|
||||
a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Na(a){Oa||(Oa=ja("div"));a&&Oa.appendChild(a);Oa.innerHTML=""}function ha(a){return parseFloat(a.toPrecision(14))}function pb(){var a=M.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";la=M.global.Date||window.Date;wa=6E4*(a&&M.global.timezoneOffset||0);Pa=a?la.UTC:function(a,b,c,g,h,k){return(new la(a,b,n(c,1),n(g,0),n(h,0),n(k,0))).getTime()};db=b+"Minutes";eb=b+"Hours";fb=b+"Day";Da=b+"Date";Qa=b+"Month";Ra=b+"FullYear";qb=c+"Minutes";
|
||||
rb=c+"Hours";gb=c+"Date";sb=c+"Month";tb=c+"FullYear"}function O(){}function xa(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;c||d||this.addLabel()}function ma(){this.init.apply(this,arguments)}function Ea(){this.init.apply(this,arguments)}var u,y=document,I=window,R=Math,A=R.round,K=R.floor,ya=R.ceil,x=R.max,T=R.min,X=R.abs,Fa=R.cos,Sa=R.sin,ub=R.PI,vb=2*ub/360,na=navigator.userAgent,Bb=I.opera,ra=/msie/i.test(na)&&!Bb,Cb=/AppleWebKit/.test(na),Ta=/Firefox/.test(na),wb=/(Mobile|Android|Windows Phone)/.test(na),
|
||||
Z=!!y.createElementNS&&!!y.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect,Db=Ta&&4>parseInt(na.split("Firefox/")[1],10),ca=!Z&&!ra&&!!y.createElement("canvas").getContext,Ga,xb={},hb=0,Oa,M,Ka,S,ib,z,aa,Eb=function(){return u},Y=[],Ha=0,Fb=/^[0-9]+$/,la,Pa,wa,db,eb,fb,Da,Qa,Ra,qb,rb,gb,sb,tb,P={},U;I.Highcharts?aa(16,!0):U=I.Highcharts={};Ka=function(a,b,c){if(!w(b)||isNaN(b))return"Invalid date";a=n(a,"%Y-%m-%d %H:%M:%S");var d=new la(b-wa),e,f=d[eb](),g=d[fb](),h=d[Da](),k=d[Qa](),
|
||||
l=d[Ra](),m=M.lang,q=m.weekdays,d=v({a:q[g].substr(0,3),A:q[g],d:sa(h),e:h,b:m.shortMonths[k],B:m.months[k],m:sa(k+1),y:l.toString().substr(2,2),Y:l,H:sa(f),I:sa(f%12||12),l:f%12||12,M:sa(d[db]()),p:12>f?"AM":"PM",P:12>f?"am":"pm",S:sa(d.getSeconds()),L:sa(A(b%1E3),3)},U.dateFormats);for(e in d)for(;-1!==a.indexOf("%"+e);)a=a.replace("%"+e,"function"===typeof d[e]?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};aa=function(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+
|
||||
a;if(b)throw c;I.console&&console.log(c)};z={millisecond:1,second:1E3,minute:6E4,hour:36E5,day:864E5,week:6048E5,month:26784E5,year:31556952E3};ib={init:function(a,b,c){b=b||"";var d=a.shift,e=-1<b.indexOf("C"),f=e?7:3,g;b=b.split(" ");c=[].concat(c);var h,k,l=function(a){for(g=a.length;g--;)"M"===a[g]&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(l(b),l(c));a.isArea&&(h=b.splice(b.length-6,6),k=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);
|
||||
a.shift=0;if(b.length)for(a=c.length;b.length<a;)d=[].concat(b).splice(b.length-f,f),e&&(d[f-6]=d[f-2],d[f-5]=d[f-1]),b=b.concat(d);h&&(b=b.concat(h),c=c.concat(k));return[b,c]},step:function(a,b,c,d){var e=[],f=a.length;if(1===c)e=d;else if(f===b.length&&1>c)for(;f--;)d=parseFloat(a[f]),e[f]=isNaN(d)?a[f]:c*parseFloat(b[f]-d)+d;else e=b;return e}};(function(a){I.HighchartsAdapter=I.HighchartsAdapter||a&&{init:function(b){var c=a.fx;a.extend(a.easing,{easeOutQuad:function(a,b,c,g,h){return-g*(b/=
|
||||
h)*(b-2)+c}});a.each(["cur","_default","width","height","opacity"],function(b,e){var f=c.step,g;"cur"===e?f=c.prototype:"_default"===e&&a.Tween&&(f=a.Tween.propHooks[e],e="set");(g=f[e])&&(f[e]=function(a){var c;a=b?a:this;if("align"!==a.prop)return c=a.elem,c.attr?c.attr(a.prop,"cur"===e?u:a.now):g.apply(this,arguments)})});bb(a.cssHooks.opacity,"get",function(a,b,c){return b.attr?b.opacity||0:a.call(this,b,c)});this.addAnimSetter("d",function(a){var c=a.elem,f;a.started||(f=b.init(c,c.d,c.toD),
|
||||
a.start=f[0],a.end=f[1],a.started=!0);c.attr("d",b.step(a.start,a.end,a.pos,c.toD))});this.each=Array.prototype.forEach?function(a,b){return Array.prototype.forEach.call(a,b)}:function(a,b){var c,g=a.length;for(c=0;c<g;c++)if(!1===b.call(a[c],a[c],c,a))return c};a.fn.highcharts=function(){var a="Chart",b=arguments,c,g;this[0]&&(pa(b[0])&&(a=b[0],b=Array.prototype.slice.call(b,1)),c=b[0],c!==u&&(c.chart=c.chart||{},c.chart.renderTo=this[0],new U[a](c,b[1]),g=this),c===u&&(g=Y[N(this[0],"data-highcharts-chart")]));
|
||||
return g}},addAnimSetter:function(b,c){a.Tween?a.Tween.propHooks[b]={set:c}:a.fx.step[b]=c},getScript:a.getScript,inArray:a.inArray,adapterRun:function(b,c){return a(b)[c]()},grep:a.grep,map:function(a,c){for(var d=[],e=0,f=a.length;e<f;e++)d[e]=c.call(a[e],a[e],e,a);return d},offset:function(b){return a(b).offset()},addEvent:function(b,c,d){a(b).bind(c,d)},removeEvent:function(b,c,d){var e=y.removeEventListener?"removeEventListener":"detachEvent";y[e]&&b&&!b[e]&&(b[e]=function(){});a(b).unbind(c,
|
||||
d)},fireEvent:function(b,c,d,e){var f=a.Event(c),g="detached"+c,h;!ra&&d&&(delete d.layerX,delete d.layerY,delete d.returnValue);v(f,d);b[c]&&(b[g]=b[c],b[c]=null);a.each(["preventDefault","stopPropagation"],function(a,b){var c=f[b];f[b]=function(){try{c.call(f)}catch(a){"preventDefault"===b&&(h=!0)}}});a(b).trigger(f);b[g]&&(b[c]=b[g],b[g]=null);!e||f.isDefaultPrevented()||h||e(f)},washMouseEvent:function(a){var c=a.originalEvent||a;c.pageX===u&&(c.pageX=a.pageX,c.pageY=a.pageY);return c},animate:function(b,
|
||||
c,d){var e=a(b);b.style||(b.style={});c.d&&(b.toD=c.d,c.d=1);e.stop();c.opacity!==u&&b.attr&&(c.opacity+="px");b.hasAnim=1;e.animate(c,d)},stop:function(b){b.hasAnim&&a(b).stop()}}})(I.jQuery);var Ua=I.HighchartsAdapter,ba=Ua||{};Ua&&Ua.init.call(Ua,ib);var Va=ba.adapterRun,Ia=ba.inArray,t=ba.each,jb=ba.grep,Gb=ba.offset,kb=ba.map,F=ba.addEvent,L=ba.removeEvent,B=ba.fireEvent,Hb=ba.washMouseEvent,Wa=ba.animate,Xa=ba.stop,lb={enabled:!0,x:0,y:15,style:{color:"#606060",cursor:"default",fontSize:"11px"}};
|
||||
M={colors:"#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #8085e8 #8d4653 #91e8e1".split(" "),symbols:["circle","diamond","square","triangle","triangle-down"],lang:{loading:"Loading...",months:"January February March April May June July August September October November December".split(" "),shortMonths:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),weekdays:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),decimalPoint:".",numericSymbols:"kMGTPE".split(""),
|
||||
resetZoom:"Reset zoom",resetZoomTitle:"Reset zoom level 1:1",thousandsSep:","},global:{useUTC:!0,canvasToolsURL:"http://code.highcharts.com@product.cdnpath@//Highstock 2.0.4/modules/canvas-tools.js",VMLRadialGradientURL:"http://code.highcharts.com@product.cdnpath@//Highstock 2.0.4/gfx/vml-radial-gradient.png"},chart:{borderColor:"#4572A7",borderRadius:0,defaultSeriesType:"line",ignoreHiddenSeries:!0,spacing:[10,10,15,10],backgroundColor:"#FFFFFF",plotBorderColor:"#C0C0C0",resetZoomButton:{theme:{zIndex:20},
|
||||
position:{align:"right",x:-10,y:10}}},title:{text:"Chart title",align:"center",margin:15,style:{color:"#333333",fontSize:"18px"}},subtitle:{text:"",align:"center",style:{color:"#555555"}},plotOptions:{line:{allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{},lineWidth:2,marker:{lineWidth:0,radius:4,lineColor:"#FFFFFF",states:{hover:{enabled:!0,lineWidthPlus:1,radiusPlus:2},select:{fillColor:"#FFFFFF",lineColor:"#000000",lineWidth:2}}},point:{events:{}},dataLabels:G(lb,{align:"center",
|
||||
enabled:!1,formatter:function(){return null===this.y?"":ka(this.y,-1)},verticalAlign:"bottom",y:0}),cropThreshold:300,pointRange:0,states:{hover:{lineWidthPlus:1,marker:{},halo:{size:10,opacity:.25}},select:{marker:{}}},stickyTracking:!0,turboThreshold:1E3}},labels:{style:{position:"absolute",color:"#3E576F"}},legend:{enabled:!0,align:"center",layout:"horizontal",labelFormatter:function(){return this.name},borderColor:"#909090",borderRadius:0,navigation:{activeColor:"#274b6d",inactiveColor:"#CCC"},
|
||||
shadow:!1,itemStyle:{color:"#333333",fontSize:"12px",fontWeight:"bold"},itemHoverStyle:{color:"#000"},itemHiddenStyle:{color:"#CCC"},itemCheckboxStyle:{position:"absolute",width:"13px",height:"13px"},symbolPadding:5,verticalAlign:"bottom",x:0,y:0,title:{style:{fontWeight:"bold"}}},loading:{labelStyle:{fontWeight:"bold",position:"relative",top:"45%"},style:{position:"absolute",backgroundColor:"white",opacity:.5,textAlign:"center"}},tooltip:{enabled:!0,animation:Z,backgroundColor:"rgba(249, 249, 249, .85)",
|
||||
borderWidth:1,borderRadius:3,dateTimeLabelFormats:{millisecond:"%A, %b %e, %H:%M:%S.%L",second:"%A, %b %e, %H:%M:%S",minute:"%A, %b %e, %H:%M",hour:"%A, %b %e, %H:%M",day:"%A, %b %e, %Y",week:"Week from %A, %b %e, %Y",month:"%B %Y",year:"%Y"},headerFormat:'<span style="font-size: 10px">{point.key}</span><br/>',pointFormat:'<span style="color:{series.color}">\u25cf</span> {series.name}: <b>{point.y}</b><br/>',shadow:!0,snap:wb?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px",padding:"8px",
|
||||
whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"9px"}}};var Ya=M.plotOptions;pb();var Ib=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,Jb=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,Kb=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,za=function(a){var b=[],c,d;(function(a){a&&
|
||||
a.stops?d=kb(a.stops,function(a){return za(a[1])}):(c=Ib.exec(a))?b=[E(c[1]),E(c[2]),E(c[3]),parseFloat(c[4],10)]:(c=Jb.exec(a))?b=[E(c[1],16),E(c[2],16),E(c[3],16),1]:(c=Kb.exec(a))&&(b=[E(c[1]),E(c[2]),E(c[3]),1])})(a);return{get:function(c){var f;d?(f=G(a),f.stops=[].concat(f.stops),t(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?"rgb"===c?"rgb("+b[0]+","+b[1]+","+b[2]+")":"a"===c?b[3]:"rgba("+b.join(",")+")":a;return f},brighten:function(a){if(d)t(d,function(b){b.brighten(a)});
|
||||
else if(ga(a)&&0!==a){var c;for(c=0;3>c;c++)b[c]+=E(255*a),0>b[c]&&(b[c]=0),255<b[c]&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}};O.prototype={opacity:1,textProps:"fontSize fontWeight fontFamily color lineHeight width textDecoration textShadow HcTextStroke".split(" "),init:function(a,b){this.element="span"===b?ja(b):y.createElementNS("http://www.w3.org/2000/svg",b);this.renderer=a},animate:function(a,b,c){b=n(b,S,!0);Xa(this);b?(b=G(b,{}),c&&(b.complete=c),Wa(this,
|
||||
a,b)):(this.attr(a),c&&c());return this},colorGradient:function(a,b,c){var d=this.renderer,e,f,g,h,k,l,m,q,r,p,n=[];a.linearGradient?f="linearGradient":a.radialGradient&&(f="radialGradient");if(f){g=a[f];h=d.gradients;l=a.stops;r=c.radialReference;ta(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"});"radialGradient"===f&&r&&!w(g.gradientUnits)&&(g=G(g,{cx:r[0]-r[2]/2+g.cx*r[2],cy:r[1]-r[2]/2+g.cy*r[2],r:g.r*r[2],gradientUnits:"userSpaceOnUse"}));for(p in g)"id"!==p&&n.push(p,
|
||||
g[p]);for(p in l)n.push(l[p]);n=n.join(",");h[n]?a=h[n].attr("id"):(g.id=a="highcharts-"+hb++,h[n]=k=d.createElement(f).attr(g).add(d.defs),k.stops=[],t(l,function(a){0===a[1].indexOf("rgba")?(e=za(a[1]),m=e.get("rgb"),q=e.get("a")):(m=a[1],q=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":m,"stop-opacity":q}).add(k);k.stops.push(a)}));c.setAttribute(b,"url("+d.url+"#"+a+")")}},attr:function(a,b){var c,d,e=this.element,f,g=this,h;"string"===typeof a&&b!==u&&(c=a,a={},a[c]=b);if("string"===
|
||||
typeof a)g=(this[a+"Getter"]||this._defaultGetter).call(this,a,e);else{for(c in a)d=a[c],h=!1,this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(f||(this.symbolAttr(a),f=!0),h=!0),!this.rotation||"x"!==c&&"y"!==c||(this.doTransform=!0),h||(this[c+"Setter"]||this._defaultSetter).call(this,d,c,e),this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c)&&this.updateShadows(c,d);this.doTransform&&(this.updateTransform(),this.doTransform=!1)}return g},
|
||||
updateShadows:function(a,b){for(var c=this.shadows,d=c.length;d--;)c[d].setAttribute(a,"height"===a?x(b-(c[d].cutHeight||0),0):"d"===a?this.d:b)},addClass:function(a){var b=this.element,c=N(b,"class")||"";-1===c.indexOf(a)&&N(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;t("x y r start end width height innerR anchorX anchorY".split(" "),function(c){b[c]=n(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",
|
||||
a?"url("+this.renderer.url+"#"+a.id+")":"none")},crisp:function(a){var b,c={},d,e=a.strokeWidth||this.strokeWidth||0;d=A(e)%2/2;a.x=K(a.x||this.x||0)+d;a.y=K(a.y||this.y||0)+d;a.width=K((a.width||this.width||0)-2*d);a.height=K((a.height||this.height||0)-2*d);a.strokeWidth=e;for(b in a)this[b]!==a[b]&&(this[b]=c[b]=a[b]);return c},css:function(a){var b=this.styles,c={},d=this.element,e,f,g="";e=!b;a&&a.color&&(a.fill=a.color);if(b)for(f in a)a[f]!==b[f]&&(c[f]=a[f],e=!0);if(e){e=this.textWidth=a&&
|
||||
a.width&&"text"===d.nodeName.toLowerCase()&&E(a.width);b&&(a=v(b,c));this.styles=a;e&&(ca||!Z&&this.renderer.forExport)&&delete a.width;if(ra&&!Z)J(this.element,a);else{b=function(a,b){return"-"+b.toLowerCase()};for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";N(d,"style",g)}e&&this.added&&this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;Ga&&"click"===a?(d.ontouchstart=function(a){c.touchEventFired=la.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(-1===
|
||||
na.indexOf("Android")||1100<la.now()-(c.touchEventFired||0))&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){this.element.radialReference=a;return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation,g=this.element;e&&(a+=this.attr("width"),b+=this.attr("height"));
|
||||
a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(g.getAttribute("x")||0)+" "+(g.getAttribute("y")||0)+")");(w(c)||w(d))&&a.push("scale("+n(c,1)+" "+n(d,1)+")");a.length&&g.setAttribute("transform",a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||pa(c))this.alignTo=d=c||"renderer",qa(f,
|
||||
this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=n(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if("right"===d||"center"===d)f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=A(f);if("bottom"===e||"middle"===e)g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=A(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,
|
||||
b=this.renderer,c,d,e=this.rotation;c=this.element;var f=this.styles,g=e*vb;d=this.textStr;var h;if(""===d||Fb.test(d))h="num."+d.toString().length+(f?"|"+f.fontSize+"|"+f.fontFamily:"");h&&(a=b.cache[h]);if(!a){if("http://www.w3.org/2000/svg"===c.namespaceURI||b.forExport){try{a=c.getBBox?v({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(k){}if(!a||0>a.width)a={width:0,height:0}}else a=this.htmlGetBBox();b.isSVG&&(c=a.width,d=a.height,ra&&f&&"11px"===f.fontSize&&"16.9"===d.toPrecision(3)&&
|
||||
(a.height=d=14),e&&(a.width=X(d*Sa(g))+X(c*Fa(g)),a.height=X(d*Fa(g))+X(c*Sa(g))));this.bBox=a;h&&(b.cache[h]=a)}return a},show:function(a){a&&"http://www.w3.org/2000/svg"===this.element.namespaceURI?this.element.removeAttribute("visibility"):this.attr({visibility:a?"inherit":"visible"});return this},hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.attr({y:-9999})}})},add:function(a){var b=this.renderer,
|
||||
c=a||b,d=c.element||b.box,e=this.element,f=this.zIndex,g,h;a&&(this.parentGroup=a);this.parentInverted=a&&a.inverted;void 0!==this.textStr&&b.buildText(this);f&&(c.handleZ=!0,f=E(f));if(c.handleZ)for(a=d.childNodes,g=0;g<a.length;g++)if(b=a[g],c=N(b,"zIndex"),b!==e&&(E(c)>f||!w(f)&&w(c))){d.insertBefore(e,b);h=!0;break}h||d.appendChild(e);this.added=!0;if(this.onAdd)this.onAdd();return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||
|
||||
{},c=a.shadows,d=a.renderer.isSVG&&"SPAN"===b.nodeName&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;Xa(a);a.clipPath&&(a.clipPath=a.clipPath.destroy());if(a.stops){for(f=0;f<a.stops.length;f++)a.stops[f]=a.stops[f].destroy();a.stops=null}a.safeRemoveChild(b);for(c&&t(c,function(b){a.safeRemoveChild(b)});d&&d.div&&0===d.div.childNodes.length;)b=d.parentGroup,a.safeRemoveChild(d.div),delete d.div,d=b;a.alignTo&&qa(a.renderer.alignedObjects,a);for(e in a)delete a[e];
|
||||
return null},shadow:function(a,b,c){var d=[],e,f,g=this.element,h,k,l,m;if(a){k=n(a.width,3);l=(a.opacity||.15)/k;m=this.parentInverted?"(-1,-1)":"("+n(a.offsetX,1)+", "+n(a.offsetY,1)+")";for(e=1;e<=k;e++)f=g.cloneNode(0),h=2*k+1-2*e,N(f,{isShadow:"true",stroke:a.color||"black","stroke-opacity":l*e,"stroke-width":h,transform:"translate"+m,fill:"none"}),c&&(N(f,"height",x(N(f,"height")-h,0)),f.cutHeight=h),b?b.element.appendChild(f):g.parentNode.insertBefore(f,g),d.push(f);this.shadows=d}return this},
|
||||
xGetter:function(a){"circle"===this.element.nodeName&&(a={x:"cx",y:"cy"}[a]||a);return this._defaultGetter(a)},_defaultGetter:function(a){a=n(this[a],this.element?this.element.getAttribute(a):null,0);/^[\-0-9\.]+$/.test(a)&&(a=parseFloat(a));return a},dSetter:function(a,b,c){a&&a.join&&(a=a.join(" "));/(NaN| {2}|^$)/.test(a)&&(a="M 0 0");c.setAttribute(b,a);this[b]=a},dashstyleSetter:function(a){var b;if(a=a&&a.toLowerCase()){a=a.replace("shortdashdotdot","3,1,1,1,1,1,").replace("shortdashdot","3,1,1,1").replace("shortdot",
|
||||
"1,1,").replace("shortdash","3,1,").replace("longdash","8,3,").replace(/dot/g,"1,3,").replace("dash","4,3,").replace(/,$/,"").split(",");for(b=a.length;b--;)a[b]=E(a[b])*this["stroke-width"];a=a.join(",").replace("NaN","none");this.element.setAttribute("stroke-dasharray",a)}},alignSetter:function(a){this.element.setAttribute("text-anchor",{left:"start",center:"middle",right:"end"}[a])},opacitySetter:function(a,b,c){this[b]=a;c.setAttribute(b,a)},titleSetter:function(a){var b=this.element.getElementsByTagName("title")[0];
|
||||
b||(b=y.createElementNS("http://www.w3.org/2000/svg","title"),this.element.appendChild(b));b.textContent=n(a,"").replace(/<[^>]*>/g,"")},textSetter:function(a){a!==this.textStr&&(delete this.bBox,this.textStr=a,this.added&&this.renderer.buildText(this))},fillSetter:function(a,b,c){"string"===typeof a?c.setAttribute(b,a):a&&this.colorGradient(a,b,c)},zIndexSetter:function(a,b,c){c.setAttribute(b,a);this[b]=a},_defaultSetter:function(a,b,c){c.setAttribute(b,a)}};O.prototype.yGetter=O.prototype.xGetter;
|
||||
O.prototype.translateXSetter=O.prototype.translateYSetter=O.prototype.rotationSetter=O.prototype.verticalAlignSetter=O.prototype.scaleXSetter=O.prototype.scaleYSetter=function(a,b){this[b]=a;this.doTransform=!0};O.prototype["stroke-widthSetter"]=O.prototype.strokeSetter=function(a,b,c){this[b]=a;this.stroke&&this["stroke-width"]?(this.strokeWidth=this["stroke-width"],O.prototype.fillSetter.call(this,this.stroke,"stroke",c),c.setAttribute("stroke-width",this["stroke-width"]),this.hasStroke=!0):"stroke-width"===
|
||||
b&&0===a&&this.hasStroke&&(c.removeAttribute("stroke"),this.hasStroke=!1)};var Ja=function(){this.init.apply(this,arguments)};Ja.prototype={Element:O,init:function(a,b,c,d,e){var f=location,g;d=this.createElement("svg").attr({version:"1.1"}).css(this.getStyle(d));g=d.element;a.appendChild(g);-1===a.innerHTML.indexOf("xmlns")&&N(g,"xmlns","http://www.w3.org/2000/svg");this.isSVG=!0;this.box=g;this.boxWrapper=d;this.alignedObjects=[];this.url=(Ta||Cb)&&y.getElementsByTagName("base").length?f.href.replace(/#.*?$/,
|
||||
"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(y.createTextNode("Created with Highcharts 4.0.4 /Highstock 2.0.4"));this.defs=this.createElement("defs").add();this.forExport=e;this.gradients={};this.cache={};this.setSize(b,c,!1);var h;Ta&&a.getBoundingClientRect&&(this.subPixelFix=b=function(){J(a,{left:0,top:0});h=a.getBoundingClientRect();J(a,{left:ya(h.left)-h.left+"px",top:ya(h.top)-h.top+"px"})},b(),F(I,"resize",b))},getStyle:function(a){return this.style=
|
||||
v({fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',fontSize:"12px"},a)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Ma(this.gradients||{});this.gradients=null;a&&(this.defs=a.destroy());this.subPixelFix&&L(I,"resize",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);return b},draw:function(){},
|
||||
buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=n(a.textStr,"").toString(),f=-1!==e.indexOf("<"),g=b.childNodes,h,k,l=N(b,"x"),m=a.styles,q=a.textWidth,r=m&&m.lineHeight,p=m&&m.HcTextStroke,Q=g.length,D=function(a){return r?E(r):c.fontMetrics(/(px|em)$/.test(a&&a.style.fontSize)?a.style.fontSize:m&&m.fontSize||c.style.fontSize||12,a).h};Q--;)b.removeChild(g[Q]);f||p||-1!==e.indexOf(" ")?(h=/<.*style="([^"]+)".*>/,k=/<.*href="(http[^"]+)".*>/,q&&!a.added&&this.box.appendChild(b),e=
|
||||
f?e.replace(/<(b|strong)>/g,'<span style="font-weight:bold">').replace(/<(i|em)>/g,'<span style="font-style:italic">').replace(/<a/g,"<span").replace(/<\/(b|strong|i|em|a)>/g,"</span>").split(/<br.*?>/g):[e],""===e[e.length-1]&&e.pop(),t(e,function(e,f){var g,r=0;e=e.replace(/<span/g,"|||<span").replace(/<\/span>/g,"</span>|||");g=e.split("|||");t(g,function(e){if(""!==e||1===g.length){var p={},n=y.createElementNS("http://www.w3.org/2000/svg","tspan"),Q;h.test(e)&&(Q=e.match(h)[1].replace(/(;| |^)color([ :])/,
|
||||
"$1fill$2"),N(n,"style",Q));k.test(e)&&!d&&(N(n,"onclick",'location.href="'+e.match(k)[1]+'"'),J(n,{cursor:"pointer"}));e=(e.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"<").replace(/>/g,">");if(" "!==e){n.appendChild(y.createTextNode(e));r?p.dx=0:f&&null!==l&&(p.x=l);N(n,p);b.appendChild(n);!r&&f&&(!Z&&d&&J(n,{display:"block"}),N(n,"dy",D(n)));if(q){e=e.replace(/([^\^])-/g,"$1- ").split(" ");for(var p=1<g.length||1<e.length&&"nowrap"!==m.whiteSpace,t,C,H=m.HcHeight,u=[],w=D(n),yb=1;p&&(e.length||
|
||||
u.length);)delete a.bBox,t=a.getBBox(),C=t.width,!Z&&c.forExport&&(C=c.measureSpanWidth(n.firstChild.data,a.styles)),(t=C>q)&&1!==e.length?(n.removeChild(n.firstChild),u.unshift(e.pop())):(e=u,u=[],e.length&&(yb++,H&&yb*w>H?(e=["..."],a.attr("title",a.textStr)):(n=y.createElementNS("http://www.w3.org/2000/svg","tspan"),N(n,{dy:w,x:l}),Q&&N(n,"style",Q),b.appendChild(n))),C>q&&(q=C)),e.length&&n.appendChild(y.createTextNode(e.join(" ").replace(/- /g,"-")))}r++}}})})):b.appendChild(y.createTextNode(e))},
|
||||
button:function(a,b,c,d,e,f,g,h,k){var l=this.label(a,b,c,k,null,null,null,null,"button"),m=0,q,r,p,n,D,t;a={x1:0,y1:0,x2:0,y2:1};e=G({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);p=e.style;delete e.style;f=G(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);n=f.style;delete f.style;g=G(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);D=g.style;delete g.style;
|
||||
h=G(e,{style:{color:"#CCC"}},h);t=h.style;delete h.style;F(l.element,ra?"mouseover":"mouseenter",function(){3!==m&&l.attr(f).css(n)});F(l.element,ra?"mouseout":"mouseleave",function(){3!==m&&(q=[e,f,g][m],r=[p,n,D][m],l.attr(q).css(r))});l.setState=function(a){(l.state=m=a)?2===a?l.attr(g).css(D):3===a&&l.attr(h).css(t):l.attr(e).css(p)};return l.on("click",function(){3!==m&&d.call(l)}).attr(e).css(v({cursor:"default"},p))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=A(a[1])-b%2/2);a[2]===a[5]&&
|
||||
(a[2]=a[5]=A(a[2])+b%2/2);return a},path:function(a){var b={fill:"none"};ta(a)?b.d=a:da(a)&&v(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=da(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=function(a){this.element.setAttribute("cx",a)};b.ySetter=function(a){this.element.setAttribute("cy",a)};return b.attr(a)},arc:function(a,b,c,d,e,f){da(a)&&(b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x);a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||
|
||||
0});a.r=c;return a},rect:function(a,b,c,d,e,f){e=da(a)?a.r:e;var g=this.createElement("rect");a=da(a)?a:a===u?{}:{x:a,y:b,width:x(c,0),height:x(d,0)};f!==u&&(a.strokeWidth=f,a=g.crisp(a));e&&(a.r=e);g.rSetter=function(a){N(this.element,{rx:a,ry:a})};return g.attr(a)},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[n(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return w(a)?b.attr({"class":"highcharts-"+
|
||||
a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:"none"};1<arguments.length&&v(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(A(b),A(c),d,e,f),k=/^url\((.*?)\)$/,l,m;h?(g=this.path(h),v(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&v(g,f)):k.test(a)&&(m=function(a,b){a.element&&
|
||||
(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(A((d-b[0])/2),A((e-b[1])/2)))},l=a.match(k)[1],a=xb[l]||f&&f.width&&f.height&&[f.width,f.height],g=this.image(l).attr({x:b,y:c}),g.isImg=!0,a?m(g,a):(g.attr({width:0,height:0}),ja("img",{onload:function(){m(g,xb[l]=[this.width,this.height])},src:l})));return g},symbols:{circle:function(a,b,c,d){var e=.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",
|
||||
a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start;c=e.r||c||d;var g=e.end-.001;d=e.innerR;var h=e.open,k=Fa(f),l=Sa(f),m=Fa(g),g=Sa(g);e=e.end-f<ub?0:1;return["M",a+c*k,b+c*l,"A",c,c,0,e,1,a+c*m,b+c*g,h?"M":"L",a+d*m,b+d*g,"A",d,d,0,e,0,a+d*k,b+d*l,h?"":"Z"]},
|
||||
callout:function(a,b,c,d,e){var f=T(e&&e.r||0,c,d),g=f+6,h=e&&e.anchorX,k=e&&e.anchorY;e=A(e.strokeWidth||0)%2/2;a+=e;b+=e;e=["M",a+f,b,"L",a+c-f,b,"C",a+c,b,a+c,b,a+c,b+f,"L",a+c,b+d-f,"C",a+c,b+d,a+c,b+d,a+c-f,b+d,"L",a+f,b+d,"C",a,b+d,a,b+d,a,b+d-f,"L",a,b+f,"C",a,b,a,b,a+f,b];h&&h>c&&k>b+g&&k<b+d-g?e.splice(13,3,"L",a+c,k-6,a+c+6,k,a+c,k+6,a+c,b+d-f):h&&0>h&&k>b+g&&k<b+d-g?e.splice(33,3,"L",a,k+6,a-6,k,a,k-6,a,b+f):k&&k>d&&h>a+g&&h<a+c-g?e.splice(23,3,"L",h+6,b+d,h,b+d+6,h-6,b+d,a+f,b+d):k&&0>
|
||||
k&&h>a+g&&h<a+c-g&&e.splice(3,3,"L",h-6,b,h,b-6,h+6,b,c-f,b);return e}},clipRect:function(a,b,c,d){var e="highcharts-"+hb++,f=this.createElement("clipPath").attr({id:e}).add(this.defs);a=this.rect(a,b,c,d,0).add(f);a.id=e;a.clipPath=f;return a},text:function(a,b,c,d){var e=ca||!Z&&this.forExport,f={};if(d&&!this.forExport)return this.html(a,b,c);f.x=Math.round(b||0);c&&(f.y=Math.round(c));if(a||0===a)f.text=a;a=this.createElement("text").attr(f);e&&a.css({position:"absolute"});d||(a.xSetter=function(a,
|
||||
b,c){var d=c.getElementsByTagName("tspan"),e,f=c.getAttribute(b),r;for(r=0;r<d.length;r++)e=d[r],e.getAttribute(b)===f&&e.setAttribute(b,a);c.setAttribute(b,a)});return a},fontMetrics:function(a,b){a=a||this.style.fontSize;b&&I.getComputedStyle&&(b=b.element||b,a=I.getComputedStyle(b,"").fontSize);a=/px/.test(a)?E(a):/em/.test(a)?12*parseFloat(a):12;var c=24>a?a+4:A(1.2*a),d=A(.8*c);return{h:c,b:d,f:a}},label:function(a,b,c,d,e,f,g,h,k){function l(){var a,b;a=n.element.style;H=(void 0===Aa||void 0===
|
||||
z||p.styles.textAlign)&&n.textStr&&n.getBBox();p.width=(Aa||H.width||0)+2*W+x;p.height=(z||H.height||0)+2*W;oa=W+r.fontMetrics(a&&a.fontSize,n).b;E&&(D||(a=A(-V*W),b=h?-oa:0,p.box=D=d?r.symbol(d,a,b,p.width,p.height,C):r.rect(a,b,p.width,p.height,0,C["stroke-width"]),D.attr("fill","none").add(p)),D.isImg||D.attr(v({width:A(p.width),height:A(p.height)},C)),C=null)}function m(){var a=p.styles,a=a&&a.textAlign,b=x+W*(1-V),c;c=h?0:oa;w(Aa)&&H&&("center"===a||"right"===a)&&(b+={center:.5,right:1}[a]*(Aa-
|
||||
H.width));if(b!==n.x||c!==n.y)n.attr("x",b),c!==u&&n.attr("y",c);n.x=b;n.y=c}function q(a,b){D?D.attr(a,b):C[a]=b}var r=this,p=r.g(k),n=r.text("",0,0,g).attr({zIndex:1}),D,H,V=0,W=3,x=0,Aa,z,mb,y,B=0,C={},oa,E;p.onAdd=function(){n.add(p);p.attr({text:a||0===a?a:"",x:b,y:c});D&&w(e)&&p.attr({anchorX:e,anchorY:f})};p.widthSetter=function(a){Aa=a};p.heightSetter=function(a){z=a};p.paddingSetter=function(a){w(a)&&a!==W&&(W=a,m())};p.paddingLeftSetter=function(a){w(a)&&a!==x&&(x=a,m())};p.alignSetter=
|
||||
function(a){V={left:0,center:.5,right:1}[a]};p.textSetter=function(a){a!==u&&n.textSetter(a);l();m()};p["stroke-widthSetter"]=function(a,b){a&&(E=!0);B=a%2/2;q(b,a)};p.strokeSetter=p.fillSetter=p.rSetter=function(a,b){"fill"===b&&a&&(E=!0);q(b,a)};p.anchorXSetter=function(a,b){e=a;q(b,a+B-mb)};p.anchorYSetter=function(a,b){f=a;q(b,a-y)};p.xSetter=function(a){p.x=a;V&&(a-=V*((Aa||H.width)+W));mb=A(a);p.attr("translateX",mb)};p.ySetter=function(a){y=p.y=A(a);p.attr("translateY",y)};var F=p.css;return v(p,
|
||||
{css:function(a){if(a){var b={};a=G(a);t(p.textProps,function(c){a[c]!==u&&(b[c]=a[c],delete a[c])});n.css(b)}return F.call(p,a)},getBBox:function(){return{width:H.width+2*W,height:H.height+2*W,x:H.x-W,y:H.y-W}},shadow:function(a){D&&D.shadow(a);return p},destroy:function(){L(p.element,"mouseenter");L(p.element,"mouseleave");n&&(n=n.destroy());D&&(D=D.destroy());O.prototype.destroy.call(p);p=r=l=m=q=null}})}};xa.prototype={addLabel:function(){var a=this.axis,b=a.options,c=a.chart,d=a.horiz,e=a.categories,
|
||||
f=a.names,g=this.pos,h=b.labels,k=h.rotation,l=a.tickPositions,d=d&&e&&!h.step&&!h.staggerLines&&!h.rotation&&c.plotWidth/l.length||!d&&(c.margin[3]||.33*c.chartWidth),m=g===l[0],q=g===l[l.length-1],r,f=e?n(e[g],f[g],g):g,e=this.label,p=l.info;a.isDatetimeAxis&&p&&(r=b.dateTimeLabelFormats[p.higherRanks[g]||p.unitName]);this.isFirst=m;this.isLast=q;b=a.labelFormatter.call({axis:a,chart:c,isFirst:m,isLast:q,dateTimeLabelFormat:r,value:a.isLog?ha(ia(f)):f});g=d&&{width:x(1,A(d-2*(h.padding||10)))+"px"};
|
||||
w(e)?e&&e.attr({text:b}).css(g):(r={align:a.labelAlign},ga(k)&&(r.rotation=k),d&&h.ellipsis&&(g.HcHeight=a.len/l.length),this.label=e=w(b)&&h.enabled?c.renderer.text(b,0,0,h.useHTML).attr(r).css(v(g,h.style)).add(a.labelGroup):null,a.tickBaseline=c.renderer.fontMetrics(h.style.fontSize,e).b,k&&2===a.side&&(a.tickBaseline*=Fa(k*vb)));this.yOffset=e?n(h.y,a.tickBaseline+(2===a.side?8:-(e.getBBox().height/2))):0},getLabelSize:function(){var a=this.label,b=this.axis;return a?a.getBBox()[b.horiz?"height":
|
||||
"width"]:0},getLabelSides:function(){var a=this.label.getBBox(),b=this.axis,c=b.horiz,d=b.options.labels,a=c?a.width:a.height,b=c?d.x-a*{left:0,center:.5,right:1}[b.labelAlign]:0;return[b,c?a+b:a]},handleOverflow:function(a,b){var c=!0,d=this.axis,e=this.isFirst,f=this.isLast,g=d.horiz?b.x:b.y,h=d.reversed,k=d.tickPositions,l=this.getLabelSides(),m=l[0],l=l[1],q,r,p,n=this.label.line;q=n||0;r=d.labelEdge;p=d.justifyLabels&&(e||f);r[q]===u||g+m>r[q]?r[q]=g+l:p||(c=!1);if(p){q=(r=d.justifyToPlot)?d.pos:
|
||||
0;r=r?q+d.len:d.chart.chartWidth;do a+=e?1:-1,p=d.ticks[k[a]];while(k[a]&&(!p||!p.label||p.label.line!==n));d=p&&p.label.xy&&p.label.xy.x+p.getLabelSides()[e?0:1];e&&!h||f&&h?g+m<q&&(g=q-m,p&&g+l>d&&(c=!1)):g+l>r&&(g=r-l,p&&g+m<d&&(c=!1));b.x=g}return c},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+(e.opposite?(d&&f.oldChartWidth||f.chartWidth)-e.right-e.left:0),y:a?g-e.bottom+e.offset-
|
||||
(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var k=this.axis,l=k.transA,m=k.reversed,q=k.staggerLines;a=a+e.x-(f&&d?f*l*(m?-1:1):0);b=b+this.yOffset-(f&&!d?f*l*(m?1:-1):0);q&&(c.line=g/(h||1)%q,b+=k.labelOffset/q*c.line);return{x:a,y:b}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine(["M",a,b,"L",a+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,f=d.chart.renderer,g=d.horiz,h=this.type,k=this.label,l=
|
||||
this.pos,m=e.labels,q=this.gridLine,r=h?h+"Grid":"grid",p=h?h+"Tick":"tick",t=e[r+"LineWidth"],D=e[r+"LineColor"],H=e[r+"LineDashStyle"],V=e[p+"Length"],r=e[p+"Width"]||0,w=e[p+"Color"],x=e[p+"Position"],p=this.mark,v=m.step,A=!0,z=d.tickmarkOffset,y=this.getPosition(g,l,z,b),B=y.x,y=y.y,C=g&&B===d.pos+d.len||!g&&y===d.pos?-1:1;c=n(c,1);this.isActive=!0;if(t&&(l=d.getPlotLinePath(l+z,t*C,b,!0),q===u&&(q={stroke:D,"stroke-width":t},H&&(q.dashstyle=H),h||(q.zIndex=1),b&&(q.opacity=0),this.gridLine=
|
||||
q=t?f.path(l).attr(q).add(d.gridGroup):null),!b&&q&&l))q[this.isNew?"attr":"animate"]({d:l,opacity:c});r&&V&&("inside"===x&&(V=-V),d.opposite&&(V=-V),h=this.getMarkPath(B,y,V,r*C,g,f),p?p.animate({d:h,opacity:c}):this.mark=f.path(h).attr({stroke:w,"stroke-width":r,opacity:c}).add(d.axisGroup));k&&!isNaN(B)&&(k.xy=y=this.getLabelPosition(B,y,k,g,m,z,a,v),this.isFirst&&!this.isLast&&!n(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!n(e.showLastLabel,1)?A=!1:d.isRadial||m.step||m.rotation||b||0===
|
||||
c||(A=this.handleOverflow(a,y)),v&&a%v&&(A=!1),A&&!isNaN(y.y)?(y.opacity=c,k[this.isNew?"attr":"animate"](y),this.isNew=!1):k.attr("y",-9999))},destroy:function(){Ma(this,this.axis)}};ma.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0",labels:lb,lineColor:"#C0D0E0",lineWidth:1,minPadding:.01,maxPadding:.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,
|
||||
minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#707070"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:.05,minPadding:.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,
|
||||
formatter:function(){return ka(this.total,-1)},style:lb.style}},defaultLeftAxisOptions:{labels:{x:-15,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:null},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-15},title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite=b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:
|
||||
this.opposite?1:3);this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=!1!==d.zoomEnabled;this.categories=d.categories||"category"===e;this.names=[];this.isLog="logarithmic"===e;this.isDatetimeAxis="datetime"===e;this.isLinked=w(d.linkedTo);this.tickmarkOffset=this.categories&&"between"===d.tickmarkPlacement&&1===n(d.tickInterval,1)?.5:0;this.ticks=
|
||||
{};this.labelEdge=[];this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.min=this.max=null;this.crosshair=n(d.crosshair,ea(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;-1===Ia(this,a.axes)&&(c&&!this.isColorAxis?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this));this.series=this.series||
|
||||
[];a.inverted&&c&&this.reversed===u&&(this.reversed=!0);this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)F(this,f,d[f]);this.isLog&&(this.val2lin=ab,this.lin2val=ia)},setOptions:function(a){this.options=G(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],G(M[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,
|
||||
c=a.categories,d=this.dateTimeLabelFormat,e=M.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=ua(h,this);else if(c)g=b;else if(d)g=Ka(d,b);else if(f&&1E3<=a)for(;f--&&g===u;)c=Math.pow(1E3,f+1),a>=c&&null!==e[f]&&(g=ka(b/c,-1)+e[f]);g===u&&(g=1E4<=X(b)?ka(b,0):ka(b,-1,u,""));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=a.ignoreMinPadding=a.ignoreMaxPadding=null;a.buildStacks&&a.buildStacks();t(a.series,
|
||||
function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d;d=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&0>=d&&(d=null);a.isXAxis?(d=c.xData,d.length&&(a.dataMin=T(n(a.dataMin,d[0]),La(d)),a.dataMax=x(n(a.dataMax,d[0]),va(d)))):(c.getExtremes(),e=c.dataMax,c=c.dataMin,w(c)&&w(e)&&(a.dataMin=T(n(a.dataMin,c),c),a.dataMax=x(n(a.dataMax,e),e)),w(d)&&(a.dataMin>=d?(a.dataMin=d,a.ignoreMinPadding=!0):a.dataMax<d&&(a.dataMax=d,a.ignoreMaxPadding=!0)))}})},translate:function(a,b,c,
|
||||
d,e,f){var g=1,h=0,k=d?this.oldTransA:this.transA;d=d?this.oldMin:this.min;var l=this.minPixelPadding;e=(this.options.ordinal||this.isLog&&e)&&this.lin2val;k||(k=this.transA);c&&(g*=-1,h=this.len);this.reversed&&(g*=-1,h-=g*(this.sector||this.len));b?(a=a*g+h-l,a=a/k+d,e&&(a=this.lin2val(a))):(e&&(a=this.val2lin(a)),"between"===f&&(f=.5),a=g*(a-d)*k+h+g*l+(ga(f)?k*f*this.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,
|
||||
b){return this.translate(a-(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,b,c,d,e){var f=this.chart,g=this.left,h=this.top,k,l,m=c&&f.oldChartHeight||f.chartHeight,q=c&&f.oldChartWidth||f.chartWidth,r;k=this.transB;e=n(e,this.translate(a,null,null,c));a=c=A(e+k);k=l=A(m-e-k);if(isNaN(e))r=!0;else if(this.horiz){if(k=h,l=m-this.bottom,a<g||a>g+this.width)r=!0}else if(a=g,c=q-this.right,k<h||k>h+this.height)r=!0;return r&&!d?null:f.renderer.crispLine(["M",a,k,"L",c,l],b||1)},getLinearTickPositions:function(a,
|
||||
b,c){var d,e=ha(K(b/a)*a),f=ha(ya(c/a)*a),g=[];if(b===c&&ga(b))return[b];for(b=e;b<=f;){g.push(b);b=ha(b+a);if(b===d)break;d=b}return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog)for(e=b.length,a=1;a<e;a++)d=d.concat(this.getLogTickPositions(c,b[a-1],b[a],!0));else if(this.isDatetimeAxis&&"auto"===a.minorTickInterval)d=d.concat(this.getTimeTicks(this.normalizeTimeTickInterval(c),this.min,this.max,a.startOfWeek)),d[0]<this.min&&
|
||||
d.shift();else for(b=this.min+(b[0]-this.min)%c;b<=this.max;b+=c)d.push(b);return d},adjustForMinRange:function(){var a=this.options,b=this.min,c=this.max,d,e=this.dataMax-this.dataMin>=this.minRange,f,g,h,k,l;this.isXAxis&&this.minRange===u&&!this.isLog&&(w(a.min)||w(a.max)?this.minRange=null:(t(this.series,function(a){k=a.xData;for(g=l=a.xIncrement?1:k.length-1;0<g;g--)if(h=k[g]-k[g-1],f===u||h<f)f=h}),this.minRange=T(5*f,this.dataMax-this.dataMin)));if(c-b<this.minRange){var m=this.minRange;d=
|
||||
(m-c+b)/2;d=[b-d,n(a.min,b-d)];e&&(d[2]=this.dataMin);b=va(d);c=[b+m,n(a.max,b+m)];e&&(c[2]=this.dataMax);c=La(c);c-b<m&&(d[0]=c-m,d[1]=n(a.min,c-m),b=va(d))}this.min=b;this.max=c},setAxisTranslation:function(a){var b=this,c=b.max-b.min,d=b.axisPointRange||0,e,f=0,g=0,h=b.linkedParent,k=!!b.categories,l=b.transA;if(b.isXAxis||k||d)h?(f=h.minPointOffset,g=h.pointRangePadding):t(b.series,function(a){var h=k?1:b.isXAxis?a.pointRange:b.axisPointRange||0,l=a.options.pointPlacement,p=a.closestPointRange;
|
||||
h>c&&(h=0);d=x(d,h);f=x(f,pa(l)?0:h/2);g=x(g,"on"===l?0:h);!a.noSharedTooltip&&w(p)&&(e=w(e)?T(e,p):p)}),h=b.ordinalSlope&&e?b.ordinalSlope/e:1,b.minPointOffset=f*=h,b.pointRangePadding=g*=h,b.pointRange=T(d,c),b.closestPointRange=e;a&&(b.oldTransA=l);b.translationSlope=b.transA=l=b.len/(c+g||1);b.transB=b.horiz?b.left:b.bottom;b.minPixelPadding=l*f},setTickPositions:function(a){var b=this,c=b.chart,d=b.options,e=d.startOnTick,f=d.endOnTick,g=b.isLog,h=b.isDatetimeAxis,k=b.isXAxis,l=b.isLinked,m=
|
||||
b.options.tickPositioner,q=d.maxPadding,r=d.minPadding,p=d.tickInterval,Q=d.minTickInterval,D=d.tickPixelInterval,H,u=b.categories;l?(b.linkedParent=c[b.coll][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=n(c.min,c.dataMin),b.max=n(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&aa(11,1)):(b.min=n(b.userMin,d.min,b.dataMin),b.max=n(b.userMax,d.max,b.dataMax));g&&(!a&&0>=T(b.min,n(b.dataMin,b.min))&&aa(10,1),b.min=ha(ab(b.min)),b.max=ha(ab(b.max)));b.range&&w(b.max)&&(b.userMin=b.min=x(b.min,
|
||||
b.max-b.range),b.userMax=b.max,b.range=null);b.beforePadding&&b.beforePadding();b.adjustForMinRange();!(u||b.axisPointRange||b.usePercentage||l)&&w(b.min)&&w(b.max)&&(c=b.max-b.min)&&(w(d.min)||w(b.userMin)||!r||!(0>b.dataMin)&&b.ignoreMinPadding||(b.min-=c*r),w(d.max)||w(b.userMax)||!q||!(0<b.dataMax)&&b.ignoreMaxPadding||(b.max+=c*q));ga(d.floor)&&(b.min=x(b.min,d.floor));ga(d.ceiling)&&(b.max=T(b.max,d.ceiling));b.min===b.max||void 0===b.min||void 0===b.max?b.tickInterval=1:l&&!p&&D===b.linkedParent.options.tickPixelInterval?
|
||||
b.tickInterval=b.linkedParent.tickInterval:(b.tickInterval=n(p,u?1:(b.max-b.min)*D/x(b.len,D)),!w(p)&&b.len<D&&!this.isRadial&&!this.isLog&&!u&&e&&f&&(H=!0,b.tickInterval/=4));k&&!a&&t(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&&b.beforeSetTickPositions();b.postProcessTickInterval&&(b.tickInterval=b.postProcessTickInterval(b.tickInterval));b.pointRange&&(b.tickInterval=x(b.pointRange,b.tickInterval));!p&&b.tickInterval<
|
||||
Q&&(b.tickInterval=Q);h||g||p||(b.tickInterval=ob(b.tickInterval,null,R.pow(10,K(R.log(b.tickInterval)/R.LN10)),n(d.allowDecimals,!(1<b.tickInterval&&5>b.tickInterval&&1E3<b.max&&9999>b.max))));b.minorTickInterval="auto"===d.minorTickInterval&&b.tickInterval?b.tickInterval/5:d.minorTickInterval;b.tickPositions=a=d.tickPositions?[].concat(d.tickPositions):m&&m.apply(b,[b.min,b.max]);a||(!b.ordinalPositions&&(b.max-b.min)/b.tickInterval>x(2*b.len,200)&&aa(19,!0),a=h?b.getTimeTicks(b.normalizeTimeTickInterval(b.tickInterval,
|
||||
d.units),b.min,b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange,!0):g?b.getLogTickPositions(b.tickInterval,b.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),H&&a.splice(1,a.length-2),b.tickPositions=a);l||(d=a[0],g=a[a.length-1],h=b.minPointOffset||0,e?b.min=d:b.min-h>d&&a.shift(),f?b.max=g:b.max+h<g&&a.pop(),0===a.length&&w(d)&&a.push((g+d)/2),1===a.length&&(e=1E13<X(b.max)?1:.001,b.min-=e,b.max+=e))},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,
|
||||
d=this._maxTicksKey=[this.coll,this.pos,this.len].join("-");!this.isLinked&&!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&!1!==this.options.alignTicks&&(b[d]=c.length);a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&!1!==this.options.alignTicks&&this.min!==u){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e<a){for(;b.length<a;)b.push(ha(b[b.length-1]+this.tickInterval));
|
||||
this.transA*=(e-1)/(a-1);this.max=b[b.length-1]}w(d)&&a!==d&&(this.isDirty=!0)}},setScale:function(){var a=this.stacks,b,c,d,e;this.oldMin=this.min;this.oldMax=this.max;this.oldAxisLength=this.len;this.setAxisSize();e=this.len!==this.oldAxisLength;t(this.series,function(a){if(a.isDirtyData||a.isDirty||a.xAxis.isDirty)d=!0});if(e||d||this.isLinked||this.forceRedraw||this.userMin!==this.oldUserMin||this.userMax!==this.oldUserMax){if(!this.isXAxis)for(b in a)for(c in a[b])a[b][c].total=null,a[b][c].cum=
|
||||
0;this.forceRedraw=!1;this.getSeriesExtremes();this.setTickPositions();this.oldUserMin=this.userMin;this.oldUserMax=this.userMax;this.isDirty||(this.isDirty=e||this.min!==this.oldMin||this.max!==this.oldMax)}else if(!this.isXAxis)for(b in this.oldStacks&&(a=this.stacks=this.oldStacks),a)for(c in a[b])a[b][c].cum=a[b][c].total;this.setMaxTicks()},setExtremes:function(a,b,c,d,e){var f=this,g=f.chart;c=n(c,!0);e=v(e,{min:a,max:b});B(f,"setExtremes",e,function(){f.userMin=a;f.userMax=b;f.eventArgs=e;
|
||||
f.isDirtyExtremes=!0;c&&g.redraw(d)})},zoom:function(a,b){var c=this.dataMin,d=this.dataMax,e=this.options;this.allowZoomOutside||(w(c)&&a<=T(c,n(e.min,c))&&(a=u),w(d)&&b>=x(d,n(e.max,d))&&(b=u));this.displayBtn=a!==u||b!==u;this.setExtremes(a,b,!1,u,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=n(b.width,a.plotWidth-c+(b.offsetRight||0)),f=n(b.height,a.plotHeight),g=n(b.top,a.plotTop),b=n(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&
|
||||
(f=parseInt(f,10)/100*a.plotHeight);c.test(g)&&(g=parseInt(g,10)/100*a.plotHeight+a.plotTop);this.left=b;this.top=g;this.width=e;this.height=f;this.bottom=a.chartHeight-f-g;this.right=a.chartWidth-e-b;this.len=x(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a=this.isLog;return{min:a?ha(ia(this.min)):this.min,max:a?ha(ia(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?ia(this.min):this.min,
|
||||
b=b?ia(this.max):this.max;c>a||null===a?a=c:b<a&&(a=b);return this.translate(a,0,1,0,1)},autoLabelAlign:function(a){a=(n(a,0)-90*this.side+720)%360;return 15<a&&165>a?"right":195<a&&345>a?"left":"center"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,k=b.inverted?[1,0,3,2][h]:h,l,m,q=0,r,p=0,Q=d.title,D=d.labels,H=0,V=b.axisOffset,b=b.clipOffset,W=[-1,1,1,-1][h],v,A=1,y=n(D.maxStaggerLines,5),z,B,G,C,oa;a.hasData=l=a.hasVisibleSeries||
|
||||
w(a.min)&&w(a.max)&&!!e;a.showAxis=m=l||n(d.showEmpty,!0);a.staggerLines=a.horiz&&D.staggerLines;a.axisGroup||(a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g("axis-labels").attr({zIndex:D.zIndex||7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels").add());if(l||a.isLinked){a.labelAlign=n(D.align||a.autoLabelAlign(D.rotation));t(e,function(b){f[b]?f[b].addLabel():f[b]=new xa(a,b)});if(a.horiz&&!a.staggerLines&&
|
||||
y&&!D.rotation){for(l=a.reversed?[].concat(e).reverse():e;A<y;){z=[];B=!1;for(v=0;v<l.length;v++)G=l[v],C=(C=f[G].label&&f[G].label.getBBox())?C.width:0,oa=v%A,C&&(G=a.translate(G),z[oa]!==u&&G<z[oa]&&(B=!0),z[oa]=G+C);if(B)A++;else break}1<A&&(a.staggerLines=A)}t(e,function(b){if(0===h||2===h||{1:"left",3:"right"}[h]===a.labelAlign)H=x(f[b].getLabelSize(),H)});a.staggerLines&&(H*=a.staggerLines,a.labelOffset=H)}else for(v in f)f[v].destroy(),delete f[v];Q&&Q.text&&!1!==Q.enabled&&(a.axisTitle||(a.axisTitle=
|
||||
c.text(Q.text,0,0,Q.useHTML).attr({zIndex:7,rotation:Q.rotation||0,align:Q.textAlign||{low:"left",middle:"center",high:"right"}[Q.align]}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(Q.style).add(a.axisGroup),a.axisTitle.isNew=!0),m&&(q=a.axisTitle.getBBox()[g?"height":"width"],r=Q.offset,p=w(r)?0:n(Q.margin,g?5:10)),a.axisTitle[m?"show":"hide"]());a.offset=W*n(d.offset,V[h]);c=2===h?a.tickBaseline:0;g=H+p+(H&&W*(g?n(D.y,a.tickBaseline+8):D.x)-c);a.axisTitleMargin=n(r,g);V[h]=x(V[h],
|
||||
a.axisTitleMargin+q+W*a.offset,g);b[k]=x(b[k],2*K(d.lineWidth/2))},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,
|
||||
k=E(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(2===this.side?k:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,b=a.horiz,c=a.reversed,d=a.chart,e=d.renderer,f=a.options,g=a.isLog,h=a.isLinked,k=a.tickPositions,l,m=a.axisTitle,q=a.ticks,r=a.minorTicks,p=a.alternateBands,n=f.stackLabels,D=f.alternateGridColor,H=a.tickmarkOffset,v=f.lineWidth,
|
||||
x=d.hasRendered&&w(a.oldMin)&&!isNaN(a.oldMin),A=a.hasData,y=a.showAxis,z,B=f.labels.overflow,G=a.justifyLabels=b&&!1!==B,E;a.labelEdge.length=0;a.justifyToPlot="justify"===B;t([q,r,p],function(a){for(var b in a)a[b].isActive=!1});if(A||h)a.minorTickInterval&&!a.categories&&t(a.getMinorTickPositions(),function(b){r[b]||(r[b]=new xa(a,b,"minor"));x&&r[b].isNew&&r[b].render(null,!0);r[b].render(null,!1,1)}),k.length&&(l=k.slice(),(b&&c||!b&&!c)&&l.reverse(),G&&(l=l.slice(1).concat([l[0]])),t(l,function(b,
|
||||
c){G&&(c=c===l.length-1?0:c+1);if(!h||b>=a.min&&b<=a.max)q[b]||(q[b]=new xa(a,b)),x&&q[b].isNew&&q[b].render(c,!0,.1),q[b].render(c)}),H&&0===a.min&&(q[-1]||(q[-1]=new xa(a,-1,null,!0)),q[-1].render(-1))),D&&t(k,function(b,c){0===c%2&&b<a.max&&(p[b]||(p[b]=new U.PlotLineOrBand(a)),z=b+H,E=k[c+1]!==u?k[c+1]+H:a.max,p[b].options={from:g?ia(z):z,to:g?ia(E):E,color:D},p[b].render(),p[b].isActive=!0)}),a._addedPlotLB||(t((f.plotLines||[]).concat(f.plotBands||[]),function(b){a.addPlotBandOrLine(b)}),a._addedPlotLB=
|
||||
!0);t([q,r,p],function(a){var b,c,e=[],f=S?S.duration||500:0,g=function(){for(c=e.length;c--;)a[e[c]]&&!a[e[c]].isActive&&(a[e[c]].destroy(),delete a[e[c]])};for(b in a)a[b].isActive||(a[b].render(b,!1,0),a[b].isActive=!1,e.push(b));a!==p&&d.hasRendered&&f?f&&setTimeout(g,f):g()});v&&(b=a.getLinePath(v),a.axisLine?a.axisLine.animate({d:b}):a.axisLine=e.path(b).attr({stroke:f.lineColor,"stroke-width":v,zIndex:7}).add(a.axisGroup),a.axisLine[y?"show":"hide"]());m&&y&&(m[m.isNew?"attr":"animate"](a.getTitlePosition()),
|
||||
m.isNew=!1);n&&n.enabled&&a.renderStackTotals();a.isDirty=!1},redraw:function(){this.render();t(this.plotLinesAndBands,function(a){a.render()});t(this.series,function(a){a.isDirty=!0})},destroy:function(a){var b=this,c=b.stacks,d,e=b.plotLinesAndBands;a||L(b);for(d in c)Ma(c[d]),c[d]=null;t([b.ticks,b.minorTicks,b.alternateBands],function(a){Ma(a)});for(a=e.length;a--;)e[a].destroy();t("stackTotalGroup axisLine axisTitle axisGroup cross gridGroup labelGroup".split(" "),function(a){b[a]&&(b[a]=b[a].destroy())});
|
||||
this.cross&&this.cross.destroy()},drawCrosshair:function(a,b){if(this.crosshair)if(!1===(w(b)||!n(this.crosshair.snap,!0)))this.hideCrosshair();else{var c,d=this.crosshair,e=d.animation;n(d.snap,!0)?w(b)&&(c=this.chart.inverted!=this.horiz?b.plotX:this.len-b.plotY):c=this.horiz?a.chartX-this.pos:this.len-a.chartY+this.pos;c=this.isRadial?this.getPlotLinePath(this.isXAxis?b.x:n(b.stackY,b.y)):this.getPlotLinePath(null,null,null,null,c);if(null===c)this.hideCrosshair();else if(this.cross)this.cross.attr({visibility:"visible"})[e?
|
||||
"animate":"attr"]({d:c},e);else e={"stroke-width":d.width||1,stroke:d.color||"#C0C0C0",zIndex:d.zIndex||2},d.dashStyle&&(e.dashstyle=d.dashStyle),this.cross=this.chart.renderer.path(c).attr(e).add()}},hideCrosshair:function(){this.cross&&this.cross.hide()}};v(ma.prototype,void 0);ma.prototype.getTimeTicks=function(a,b,c,d){var e=[],f={},g=M.global.useUTC,h,k=new la(b-wa),l=a.unitRange,m=a.count;if(w(b)){l>=z.second&&(k.setMilliseconds(0),k.setSeconds(l>=z.minute?0:m*K(k.getSeconds()/m)));if(l>=z.minute)k[qb](l>=
|
||||
z.hour?0:m*K(k[db]()/m));if(l>=z.hour)k[rb](l>=z.day?0:m*K(k[eb]()/m));if(l>=z.day)k[gb](l>=z.month?1:m*K(k[Da]()/m));l>=z.month&&(k[sb](l>=z.year?0:m*K(k[Qa]()/m)),h=k[Ra]());if(l>=z.year)k[tb](h-h%m);if(l===z.week)k[gb](k[Da]()-k[fb]()+n(d,1));b=1;wa&&(k=new la(k.getTime()+wa));h=k[Ra]();d=k.getTime();for(var q=k[Qa](),r=k[Da](),p=(z.day+(g?wa:6E4*k.getTimezoneOffset()))%z.day;d<c;)e.push(d),d=l===z.year?Pa(h+b*m,0):l===z.month?Pa(h,q+b*m):g||l!==z.day&&l!==z.week?d+l*m:Pa(h,q,r+b*m*(l===z.day?
|
||||
1:7)),b++;e.push(d);t(jb(e,function(a){return l<=z.hour&&a%z.day===p}),function(a){f[a]="day"})}e.info=v(a,{higherRanks:f,totalRange:l*m});return e};ma.prototype.normalizeTimeTickInterval=function(a,b){var c=b||[["millisecond",[1,2,5,10,20,25,50,100,200,500]],["second",[1,2,5,10,15,30]],["minute",[1,2,5,10,15,30]],["hour",[1,2,3,4,6,8,12]],["day",[1,2]],["week",[1,2]],["month",[1,2,3,4,6]],["year",null]],d=c[c.length-1],e=z[d[0]],f=d[1],g;for(g=0;g<c.length&&!(d=c[g],e=z[d[0]],f=d[1],c[g+1]&&a<=(e*
|
||||
f[f.length-1]+z[c[g+1][0]])/2);g++);e===z.year&&a<5*e&&(f=[1,2,5]);c=ob(a/e,f,"year"===d[0]?x(R.pow(10,K(R.log(a/e)/R.LN10)),1):1);return{unitRange:e,count:c,unitName:d[0]}};var zb=U.Tooltip=function(){this.init.apply(this,arguments)};zb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=E(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape||"callout",null,null,b.useHTML,null,"tooltip").attr({padding:e,
|
||||
fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-9999});ca||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){this.label&&(this.label=this.label.destroy());clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=!1!==e.options.animation&&!e.isHidden&&(1<X(a-f.x)||1<X(b-f.y)),h=e.followPointer||1<e.len;v(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?u:g?(2*f.anchorX+c)/
|
||||
3:c,anchorY:h?u:g?(f.anchorY+d)/2:d});e.label.attr(f);g&&(clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32))},hide:function(a){var b=this,c;clearTimeout(this.hideTimer);this.isHidden||(c=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){b.label.fadeOut();b.isHidden=!0},n(a,this.options.hideDelay,500)),c&&t(c,function(a){a.setState()}),this.chart.hoverPoints=null)},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=0,h=
|
||||
0,k;a=ea(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===u&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(t(a,function(a){k=a.series.yAxis;g+=a.plotX;h+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&k?k.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&1<a.length&&b?b.chartY-f:e?d.plotHeight-g:h]);return kb(c,A)},getPosition:function(a,b,c){var d=this.chart,e=this.distance,f={},g,h=["y",d.chartHeight,b,c.plotY+d.plotTop],k=["x",d.chartWidth,
|
||||
a,c.plotX+d.plotLeft],l=c.ttBelow||d.inverted&&!c.negative||!d.inverted&&c.negative,m=function(a,b,c,d){var g=c<d-e;b=d+e+c<b;c=d-e-c;d+=e;if(l&&b)f[a]=d;else if(!l&&g)f[a]=c;else if(g)f[a]=c;else if(b)f[a]=d;else return!1},q=function(a,b,c,d){if(d<e||d>b-e)return!1;f[a]=d<c/2?1:d>b-c/2?b-c-2:d-c/2},r=function(a){var b=h;h=k;k=b;g=a},p=function(){!1!==m.apply(0,h)?!1!==q.apply(0,k)||g||(r(!0),p()):g?f.x=f.y=0:(r(!0),p())};(d.inverted||1<this.len)&&r();p();return f},defaultFormatter:function(a){var b=
|
||||
this.points||ea(this),c=b[0].series,d;d=[a.tooltipHeaderFormatter(b[0])];t(b,function(a){c=a.series;d.push(c.tooltipFormatter&&c.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});d.push(a.options.footerFormat||"");return d.join("")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},k,l=[];k=e.formatter||this.defaultFormatter;var h=c.hoverPoints,m,q=this.shared;clearTimeout(this.hideTimer);this.followPointer=ea(a)[0].series.tooltipOptions.followPointer;
|
||||
g=this.getAnchor(a,b);f=g[0];g=g[1];!q||a.series&&a.series.noSharedTooltip?h=a.getLabelConfig():(c.hoverPoints=a,h&&t(h,function(a){a.setState()}),t(a,function(a){a.setState("hover");l.push(a.getLabelConfig())}),h={x:a[0].category,y:a[0].y},h.points=l,this.len=l.length,a=a[0]);k=k.call(h,this);h=a.series;this.distance=n(h.tooltipOptions.distance,16);!1===k?this.hide():(this.isHidden&&(Xa(d),d.attr("opacity",1).show()),d.attr({text:k}),m=e.borderColor||a.color||h.color||"#606060",d.attr({stroke:m}),
|
||||
this.updatePosition({plotX:f,plotY:g,negative:a.negative,ttBelow:a.ttBelow}),this.isHidden=!1);B(c,"tooltipRefresh",{text:k,x:f+c.plotLeft,y:g+c.plotTop,borderColor:m})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||this.getPosition).call(this,c.width,c.height,a);this.move(A(c.x),A(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)},tooltipHeaderFormatter:function(a){var b=a.series,c=b.tooltipOptions,d=c.dateTimeLabelFormats,e=c.xDateFormat,f=b.xAxis,g=f&&"datetime"===
|
||||
f.options.type&&ga(a.key),c=c.headerFormat,f=f&&f.closestPointRange,h;if(g&&!e){if(f)for(h in z){if(z[h]>=f||z[h]<=z.day&&0<a.key%z[h]){e=d[h];break}}else e=d.day;e=e||d.year}g&&e&&(c=c.replace("{point.key}","{point.key:"+e+"}"));return ua(c,{point:a,series:b})}};var Ba;Ga=y.documentElement.ontouchstart!==u;var Ab=U.Pointer=function(a,b){this.init(a,b)};Ab.prototype={init:function(a,b){var c=b.chart,d=c.events,e=ca?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);
|
||||
this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.hasZoom=f||e;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};U.Tooltip&&b.tooltip.enabled&&(a.tooltip=new zb(a,b.tooltip),this.followTouchMove=b.tooltip.followTouchMove);this.setDOMEvents()},normalize:function(a,b){var c,d;a=a||window.event;a=Hb(a);a.target||(a.target=a.srcElement);d=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;b||(this.chartPosition=b=Gb(this.chart.container));
|
||||
d.pageX===u?(c=x(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return v(a,{chartX:A(c),chartY:A(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};t(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f,g=b.hoverPoint,
|
||||
h=b.hoverSeries,k,l,m=b.chartWidth,q=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!h||!h.noSharedTooltip)){f=[];k=c.length;for(l=0;l<k;l++)c[l].visible&&!1!==c[l].options.enableMouseTracking&&!c[l].noSharedTooltip&&!0!==c[l].singularTooltips&&c[l].tooltipPoints.length&&(e=c[l].tooltipPoints[q])&&e.series&&(e._dist=X(q-e.clientX),m=T(m,e._dist),f.push(e));for(k=f.length;k--;)f[k]._dist>m&&f.splice(k,1);f.length&&f[0].clientX!==this.hoverX&&(d.refresh(f,a),this.hoverX=f[0].clientX)}c=h&&h.tooltipOptions.followPointer;
|
||||
if(h&&h.tracker&&!c){if((e=h.tooltipPoints[q])&&e!==g)e.onMouseOver(a)}else d&&c&&!d.isHidden&&(h=d.getAnchor([{}],a),d.updatePosition({plotX:h[0],plotY:h[1]}));d&&!this._onDocumentMouseMove&&(this._onDocumentMouseMove=function(a){if(Y[Ba])Y[Ba].pointer.onDocumentMouseMove(a)},F(y,"mousemove",this._onDocumentMouseMove));t(b.axes,function(b){b.drawCrosshair(a,n(e,g))})},reset:function(a,b){var c=this.chart,d=c.hoverSeries,e=c.hoverPoint,f=c.tooltip,g=f&&f.shared?c.hoverPoints:e;(a=a&&f&&g)&&ea(g)[0].plotX===
|
||||
u&&(a=!1);if(a)f.refresh(g),e&&e.setState(e.state,!0);else{if(e)e.onMouseOut();if(d)d.onMouseOut();f&&f.hide(b);this._onDocumentMouseMove&&(L(y,"mousemove",this._onDocumentMouseMove),this._onDocumentMouseMove=null);t(c.axes,function(a){a.hideCrosshair()});this.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;t(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&
|
||||
e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=this.mouseDownY=a.chartY},drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,k=b.plotTop,l=b.plotWidth,m=b.plotHeight,q,r=this.mouseDownX,p=this.mouseDownY,n=c.panKey&&a[c.panKey+"Key"];d<h?d=h:d>h+l&&(d=h+l);e<k?e=k:e>k+m&&(e=k+m);this.hasDragged=
|
||||
Math.sqrt(Math.pow(r-d,2)+Math.pow(p-e,2));10<this.hasDragged&&(q=b.isInsidePlot(r-h,p-k),b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&q&&!n&&!this.selectionMarker&&(this.selectionMarker=b.renderer.rect(h,k,f?1:l,g?1:m,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add()),this.selectionMarker&&f&&(d-=r,this.selectionMarker.attr({width:X(d),x:(0<d?0:d)+r})),this.selectionMarker&&g&&(d=e-p,this.selectionMarker.attr({height:X(d),y:(0<d?0:d)+p})),q&&!this.selectionMarker&&
|
||||
c.panning&&b.pan(a,c.panning))},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},e=this.selectionMarker,f=e.attr?e.attr("x"):e.x,g=e.attr?e.attr("y"):e.y,h=e.attr?e.attr("width"):e.width,k=e.attr?e.attr("height"):e.height,l;if(this.hasDragged||c)t(b.axes,function(b){if(b.zoomEnabled){var c=b.horiz,e="touchend"===a.type?b.minPixelPadding:0,p=b.toValue((c?f:g)+e),c=b.toValue((c?f+h:g+k)-e);isNaN(p)||isNaN(c)||(d[b.coll].push({axis:b,
|
||||
min:T(p,c),max:x(p,c)}),l=!0)}}),l&&B(b,"selection",d,function(a){b.zoom(v(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}b&&(J(b.container,{cursor:b._cursor}),b.cancelClick=10<this.hasDragged,b.mouseIsDown=this.hasDragged=this.hasPinched=!1,this.pinchDown=[])},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){Y[Ba]&&Y[Ba].pointer.drop(a)},onDocumentMouseMove:function(a){var b=
|
||||
this.chart,c=this.chartPosition,d=b.hoverSeries;a=this.normalize(a,c);c&&d&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(){var a=Y[Ba];a&&(a.pointer.reset(),a.pointer.chartPosition=null)},onContainerMouseMove:function(a){var b=this.chart;Ba=b.index;a=this.normalize(a);a.returnValue=!1;"mousedown"===b.mouseIsDown&&this.drag(a);!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-
|
||||
b.plotLeft,a.chartY-b.plotTop)||b.openMenu||this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=N(a,"class")){if(-1!==c.indexOf(b))return!0;if(-1!==c.indexOf("highcharts-container"))return!1}a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,c=(a=a.relatedTarget||a.toElement)&&a.point&&a.point.series;if(b&&!b.options.stickyTracking&&!this.inClass(a,"highcharts-tooltip")&&c!==b)b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,
|
||||
e=b.plotTop;a=this.normalize(a);a.cancelBubble=!0;b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(B(c.series,"click",v(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(v(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&B(b,"click",a)))},setDOMEvents:function(){var a=this,b=a.chart.container;b.onmousedown=function(b){a.onContainerMouseDown(b)};b.onmousemove=function(b){a.onContainerMouseMove(b)};b.onclick=function(b){a.onContainerClick(b)};F(b,"mouseleave",
|
||||
a.onContainerMouseLeave);1===Ha&&F(y,"mouseup",a.onDocumentMouseUp);Ga&&(b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},1===Ha&&F(y,"touchend",a.onDocumentTouchEnd))},destroy:function(){var a;L(this.chart.container,"mouseleave",this.onContainerMouseLeave);Ha||(L(y,"mouseup",this.onDocumentMouseUp),L(y,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};var Za=U.Legend=function(a,b){this.init(a,
|
||||
b)};Za.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=n(b.padding,8),f=b.itemMarginTop||0;this.options=b;b.enabled&&(c.itemStyle=d,c.itemHiddenStyle=G(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY=e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.symbolWidth=n(b.symbolWidth,16),c.pages=[],c.render(),F(c.chart,"endResize",function(){c.positionCheckboxes()}))},colorizeItem:function(a,b){var c=this.options,d=a.legendItem,e=a.legendLine,
|
||||
f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color||"#CCC":g,g=a.options&&a.options.marker,k={fill:h},l;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(l in k.stroke=h,g=a.convertAttribs(g),g)d=g[l],d!==u&&(k[l]=d);f.attr(k)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);f&&(f.x=e,f.y=
|
||||
d)},destroyItem:function(a){var b=a.checkbox;t(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Na(a.checkbox)},destroy:function(){var a=this.group,b=this.box;b&&(this.box=b.destroy());a&&(this.group=a.destroy())},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;b&&(c=b.translateY,t(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,J(f,{left:b.translateX+e.checkboxOffset+f.x-20+"px",
|
||||
top:g+"px",display:g>c-6&&g<c+d-6?"":"none"}))}))},renderTitle:function(){var a=this.padding,b=this.options.title,c=0;b.text&&(this.title||(this.title=this.chart.renderer.label(b.text,a-3,a-4,null,null,null,null,null,"legend-title").attr({zIndex:1}).css(b.style).add(this.group)),a=this.title.getBBox(),c=a.height,this.offsetWidth=a.width,this.contentGroup.attr({translateY:c}));this.titleHeight=c},renderItem:function(a){var b=this.chart,c=b.renderer,d=this.options,e="horizontal"===d.layout,f=this.symbolWidth,
|
||||
g=d.symbolPadding,h=this.itemStyle,k=this.itemHiddenStyle,l=this.padding,m=e?n(d.itemDistance,20):0,q=!d.rtl,r=d.width,p=d.itemMarginBottom||0,t=this.itemMarginTop,D=this.initialItemX,u=a.legendItem,v=a.series&&a.series.drawLegendSymbol?a.series:a,w=v.options,w=this.createCheckboxForItem&&w&&w.showCheckbox,y=d.useHTML;u||(a.legendGroup=c.g("legend-item").attr({zIndex:1}).add(this.scrollGroup),a.legendItem=u=c.text(d.labelFormat?ua(d.labelFormat,a):d.labelFormatter.call(a),q?f+g:-g,this.baseline||
|
||||
0,y).css(G(a.visible?h:k)).attr({align:q?"left":"right",zIndex:2}).add(a.legendGroup),this.baseline||(this.baseline=c.fontMetrics(h.fontSize,u).f+3+t,u.attr("y",this.baseline)),v.drawLegendSymbol(this,a),this.setItemEvents&&this.setItemEvents(a,u,y,h,k),this.colorizeItem(a,a.visible),w&&this.createCheckboxForItem(a));c=u.getBBox();f=a.checkboxOffset=d.itemWidth||a.legendItemWidth||f+g+c.width+m+(w?20:0);this.itemHeight=g=A(a.legendItemHeight||c.height);e&&this.itemX-D+f>(r||b.chartWidth-2*l-D-d.x)&&
|
||||
(this.itemX=D,this.itemY+=t+this.lastLineHeight+p,this.lastLineHeight=0);this.maxItemWidth=x(this.maxItemWidth,f);this.lastItemY=t+this.itemY+p;this.lastLineHeight=x(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=t+g+p,this.lastLineHeight=g);this.offsetWidth=r||x((e?this.itemX-D-m:f)+l,this.offsetWidth)},getAllItems:function(){var a=[];t(this.chart.series,function(b){var c=b.options;n(c.showInLegend,w(c.linkedTo)?!1:u,!0)&&(a=a.concat(b.legendItems||("point"===
|
||||
c.legendType?b.data:b)))});return a},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,k=a.box,l=a.options,m=a.padding,q=l.borderWidth,r=l.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;d||(a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup));a.renderTitle();e=a.getAllItems();cb(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||
|
||||
0)});l.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;t(e,function(b){a.renderItem(b)});g=l.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(q||r)g+=m,h+=m,k?0<g&&0<h&&(k[k.isNew?"attr":"animate"](k.crisp({width:g,height:h})),k.isNew=!1):(a.box=k=c.rect(0,0,g,h,l.borderRadius,q||0).attr({stroke:l.borderColor,"stroke-width":q||0,fill:r||"none"}).add(d).shadow(l.shadow),k.isNew=!0),k[f?"show":"hide"]();a.legendWidth=g;a.legendHeight=h;t(e,function(b){a.positionItem(b)});
|
||||
f&&d.align(v({width:g,height:h},l),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+("top"===e.verticalAlign?-f:f)-this.padding,g=e.maxHeight,h,k=this.clipRect,l=e.navigation,m=n(l.animation,!0),q=l.arrowSize||12,r=this.nav,p=this.pages,u,D=this.allItems;"horizontal"===e.layout&&(f/=2);g&&(f=T(f,g));p.length=0;a>f&&!e.useHTML?(this.clipHeight=h=x(f-20-this.titleHeight-this.padding,0),
|
||||
this.currentPage=n(this.currentPage,1),this.fullHeight=a,t(D,function(a,b){var c=a._legendItemPos[1],d=A(a.legendItem.getBBox().height),e=p.length;if(!e||c-p[e-1]>h&&(u||c)!==p[e-1])p.push(u||c),e++;b===D.length-1&&c+d-p[e-1]>h&&p.push(c);c!==u&&(u=c)}),k||(k=b.clipRect=d.clipRect(0,this.padding,9999,0),b.contentGroup.clip(k)),k.attr({height:h}),r||(this.nav=r=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,q,q).on("click",function(){b.scroll(-1,m)}).add(r),this.pager=d.text("",
|
||||
15,10).css(l.style).add(r),this.down=d.symbol("triangle-down",0,0,q,q).on("click",function(){b.scroll(1,m)}).add(r)),b.scroll(0),a=f):r&&(k.attr({height:c.chartHeight}),r.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0);return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a,f=this.clipHeight,g=this.options.navigation,h=g.activeColor,g=g.inactiveColor,k=this.pager,l=this.padding;e>d&&(e=d);0<e&&(b!==u&&(S=n(b,this.chart.animation)),this.nav.attr({translateX:l,
|
||||
translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:1===e?g:h}).css({cursor:1===e?"default":"pointer"}),k.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}),this.currentPage=e,this.positionCheckboxes(c))}};var Lb=U.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||12;b.legendSymbol=this.chart.renderer.rect(0,
|
||||
a.baseline-5-c/2,a.symbolWidth,c,a.options.symbolRadius||0).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=this.options,c=b.marker,d;d=a.symbolWidth;var e=this.chart.renderer,f=this.legendGroup;a=a.baseline-A(.3*e.fontMetrics(a.options.itemStyle.fontSize,this.legendItem).b);var g;b.lineWidth&&(g={"stroke-width":b.lineWidth},b.dashStyle&&(g.dashstyle=b.dashStyle),this.legendLine=e.path(["M",0,a,"L",d,a]).attr(g).add(f));c&&!1!==c.enabled&&(b=c.radius,this.legendSymbol=d=e.symbol(this.symbol,
|
||||
d/2-b,a-b,2*b,2*b).add(f),d.isMarker=!0)}};(/Trident\/7\.0/.test(na)||Ta)&&bb(Za.prototype,"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});Ea.prototype={init:function(a,b){var c,d=a.series;a.series=null;c=G(M,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];
|
||||
this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=Y.length;Y.push(f);Ha++;!1!==d.reflow&&F(f,"load",function(){f.initReflow()});if(e)for(g in e)F(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=ca?!1:n(d.animation,!0);f.pointCount=f.colorCounter=f.symbolCounter=0;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=P[a.type||b.type||b.defaultSeriesType])||aa(17,!0);b=new b;b.init(this,a);return b},isInsidePlot:function(a,b,c){var d=c?b:a;a=c?a:b;return 0<=d&&d<=this.plotWidth&&
|
||||
0<=a&&a<=this.plotHeight},adjustTickAmounts:function(){!1!==this.options.chart.alignTicks&&t(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,k=this.hasCartesianSeries,l=this.isDirtyBox,m=c.length,q=m,r=this.renderer,p=r.isHidden(),u=[];S=n(a,this.animation);p&&this.cloneRenderTo();for(this.layOutTitles();q--;)if(a=c[q],a.options.stacking&&(g=!0,a.isDirty)){h=!0;break}if(h)for(q=
|
||||
m;q--;)a=c[q],a.options.stacking&&(a.isDirty=!0);t(c,function(a){a.isDirty&&"point"===a.options.legendType&&(f=!0)});f&&e.options.enabled&&(e.render(),this.isDirtyLegend=!1);g&&this.getStacks();k&&(this.isResizing||(this.maxTicks=null,t(b,function(a){a.setScale()})),this.adjustTickAmounts());this.getMargins();k&&(t(b,function(a){a.isDirty&&(l=!0)}),t(b,function(a){a.isDirtyExtremes&&(a.isDirtyExtremes=!1,u.push(function(){B(a,"afterSetExtremes",v(a.eventArgs,a.getExtremes()));delete a.eventArgs}));
|
||||
(l||g)&&a.redraw()}));l&&this.drawChartBox();t(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset(!0);r.draw();B(this,"redraw");p&&this.cloneRenderTo(!0);t(u,function(a){a.call()})},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d<b.length;d++)if(b[d].options.id===a)return b[d];for(d=0;d<c.length;d++)if(c[d].options.id===a)return c[d];for(d=0;d<c.length;d++)for(e=c[d].points||[],b=0;b<e.length;b++)if(e[b].id===a)return e[b];return null},getAxes:function(){var a=
|
||||
this,b=this.options,c=b.xAxis=ea(b.xAxis||{}),b=b.yAxis=ea(b.yAxis||{});t(c,function(a,b){a.index=b;a.isX=!0});t(b,function(a,b){a.index=b});c=c.concat(b);t(c,function(b){new ma(a,b)});a.adjustTickAmounts()},getSelectedPoints:function(){var a=[];t(this.series,function(b){a=a.concat(jb(b.points||[],function(a){return a.selected}))});return a},getSelectedSeries:function(){return jb(this.series,function(a){return a.selected})},getStacks:function(){var a=this;t(a.yAxis,function(a){a.stacks&&a.hasVisibleSeries&&
|
||||
(a.oldStacks=a.stacks)});t(a.series,function(b){!b.options.stacking||!0!==b.visible&&!1!==a.options.chart.ignoreHiddenSeries||(b.stackKey=b.type+n(b.options.stack,""))})},setTitle:function(a,b,c){var d=this,e=d.options,f;f=e.title=G(e.title,a);e=e.subtitle=G(e.subtitle,b);t([["title",a,f],["subtitle",b,e]],function(a){var b=a[0],c=d[b],e=a[1];a=a[2];c&&e&&(d[b]=c=c.destroy());a&&a.text&&!c&&(d[b]=d.renderer.text(a.text,0,0,a.useHTML).attr({align:a.align,"class":"highcharts-"+b,zIndex:a.zIndex||4}).css(a.style).add())});
|
||||
d.layOutTitles(c)},layOutTitles:function(a){var b=0,c=this.title,d=this.subtitle,e=this.options,f=e.title,e=e.subtitle,g=this.renderer,h=this.spacingBox.width-44;c&&(c.css({width:(f.width||h)+"px"}).align(v({y:g.fontMetrics(f.style.fontSize,c).b-3},f),!1,"spacingBox"),f.floating||f.verticalAlign||(b=c.getBBox().height));d&&(d.css({width:(e.width||h)+"px"}).align(v({y:b+(f.margin-13)+g.fontMetrics(f.style.fontSize,d).b},e),!1,"spacingBox"),e.floating||e.verticalAlign||(b=ya(b+d.getBBox().height)));
|
||||
c=this.titleOffset!==b;this.titleOffset=b;!this.isDirtyBox&&c&&(this.isDirtyBox=c,this.hasRendered&&n(a,!0)&&this.isDirtyBox&&this.redraw())},getChartSize:function(){var a=this.options.chart,b=a.width,a=a.height,c=this.renderToClone||this.renderTo;w(b)||(this.containerWidth=Va(c,"width"));w(a)||(this.containerHeight=Va(c,"height"));this.chartWidth=x(0,b||this.containerWidth||600);this.chartHeight=x(0,n(a,19<this.containerHeight?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,
|
||||
c=this.container;a?b&&(this.renderTo.appendChild(c),Na(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),J(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),y.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+hb++;pa(a)&&(this.renderTo=a=y.getElementById(a));
|
||||
a||aa(13,!0);c=E(N(a,"data-highcharts-chart"));!isNaN(c)&&Y[c]&&Y[c].hasRendered&&Y[c].destroy();N(a,"data-highcharts-chart",this.index);a.innerHTML="";b.skipClone||a.offsetWidth||this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;this.container=a=ja("div",{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},v({position:"relative",overflow:"hidden",width:c+"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},
|
||||
b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=b.forExport?new Ja(a,c,d,b.style,!0):new Ja(a,c,d,b.style);ca&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,f=n(e.margin,20),g=e.x,h=e.y,k=e.align,l=e.verticalAlign,m=this.titleOffset;this.resetMargins();b=this.axisOffset;m&&!w(d[0])&&(this.plotTop=x(this.plotTop,m+this.options.title.margin+a[0]));c.display&&!e.floating&&("right"===k?w(d[1])||
|
||||
(this.marginRight=x(this.marginRight,c.legendWidth-g+f+a[1])):"left"===k?w(d[3])||(this.plotLeft=x(this.plotLeft,c.legendWidth+g+f+a[3])):"top"===l?w(d[0])||(this.plotTop=x(this.plotTop,c.legendHeight+h+f+a[0])):"bottom"!==l||w(d[2])||(this.marginBottom=x(this.marginBottom,c.legendHeight-h+f+a[2])));this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&t(this.axes,function(a){a.getOffset()});w(d[3])||(this.plotLeft+=
|
||||
b[3]);w(d[0])||(this.plotTop+=b[0]);w(d[2])||(this.marginBottom+=b[2]);w(d[1])||(this.marginRight+=b[1]);this.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||Va(d,"width"),f=c.height||Va(d,"height"),c=a?a.target:I,d=function(){b.container&&(b.setSize(e,f,!1),b.hasUserSize=null)};if(!b.hasUserSize&&e&&f&&(c===I||c===y)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),a?b.reflowTimeout=setTimeout(d,100):d();b.containerWidth=e;b.containerHeight=
|
||||
f}},initReflow:function(){var a=this,b=function(b){a.reflow(b)};F(I,"resize",b);F(a,"destroy",function(){L(I,"resize",b)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&B(d,"endResize",null,function(){--d.isResizing})};S=n(c,d.animation);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;w(a)&&(d.chartWidth=e=x(0,A(a)),d.hasUserSize=!!e);w(b)&&(d.chartHeight=f=x(0,A(b)));(S?Wa:J)(d.container,{width:e+"px",height:f+"px"},S);d.setChartSize(!0);d.renderer.setSize(e,
|
||||
f,c);d.maxTicks=null;t(d.axes,function(a){a.isDirty=!0;a.setScale()});t(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.layOutTitles();d.getMargins();d.redraw(c);d.oldChartHeight=null;B(d,"resize");!1===S?g():setTimeout(g,S&&S.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,k,l,m,q;this.plotLeft=k=A(this.plotLeft);this.plotTop=l=A(this.plotTop);this.plotWidth=
|
||||
m=x(0,A(d-k-this.marginRight));this.plotHeight=q=x(0,A(e-l-this.marginBottom));this.plotSizeX=b?q:m;this.plotSizeY=b?m:q;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:k,y:l,width:m,height:q};d=2*K(this.plotBorderWidth/2);b=ya(x(d,h[3])/2);c=ya(x(d,h[0])/2);this.clipBox={x:b,y:c,width:K(this.plotSizeX-x(d,h[1])/2-b),height:x(0,K(this.plotSizeY-x(d,h[2])/2-c))};a||t(this.axes,function(a){a.setAxisSize();
|
||||
a.setAxisTranslation()})},resetMargins:function(){var a=this.spacing,b=this.margin;this.plotTop=n(b[0],a[0]);this.marginRight=n(b[1],a[1]);this.marginBottom=n(b[2],a[2]);this.plotLeft=n(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,k=a.borderWidth||0,l=a.backgroundColor,m=a.plotBackgroundColor,q=a.plotBackgroundImage,
|
||||
r=a.plotBorderWidth||0,p,n=this.plotLeft,t=this.plotTop,u=this.plotWidth,v=this.plotHeight,w=this.plotBox,x=this.clipRect,A=this.clipBox;p=k+(a.shadow?8:0);if(k||l)e?e.animate(e.crisp({width:c-p,height:d-p})):(e={fill:l||"none"},k&&(e.stroke=a.borderColor,e["stroke-width"]=k),this.chartBackground=b.rect(p/2,p/2,c-p,d-p,a.borderRadius,k).attr(e).addClass("highcharts-background").add().shadow(a.shadow));m&&(f?f.animate(w):this.plotBackground=b.rect(n,t,u,v,0).attr({fill:m}).add().shadow(a.plotShadow));
|
||||
q&&(h?h.animate(w):this.plotBGImage=b.image(q,n,t,u,v).add());x?x.animate({width:A.width,height:A.height}):this.clipRect=b.clipRect(A);r&&(g?g.animate(g.crisp({x:n,y:t,width:u,height:v,strokeWidth:-r})):this.plotBorder=b.rect(n,t,u,v,0,-r).attr({stroke:a.plotBorderColor,"stroke-width":r,fill:"none",zIndex:1}).add());this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;t(["inverted","angular","polar"],function(g){c=P[b.type||b.defaultSeriesType];f=a[g]||
|
||||
b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=P[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;t(b,function(a){a.linkedSeries.length=0});t(b,function(b){var d=b.options.linkedTo;pa(d)&&(d=":previous"===d?a.series[b.index-1]:a.get(d))&&(d.linkedSeries.push(b),b.linkedParent=d)})},renderSeries:function(){t(this.series,function(a){a.translate();a.setTooltipPoints&&a.setTooltipPoints();a.render()})},renderLabels:function(){var a=this,b=a.options.labels;
|
||||
b.items&&t(b.items,function(c){var d=v(b.style,c.style),e=E(d.left)+a.plotLeft,f=E(d.top)+a.plotTop+12;delete d.left;delete d.top;a.renderer.text(c.html,e,f).attr({zIndex:2}).css(d).add()})},render:function(){var a=this.axes,b=this.renderer,c=this.options;this.setTitle();this.legend=new Za(this,c.legend);this.getStacks();t(a,function(a){a.setScale()});this.getMargins();this.maxTicks=null;t(a,function(a){a.setTickPositions(!0);a.setMaxTicks()});this.adjustTickAmounts();this.getMargins();this.drawChartBox();
|
||||
this.hasCartesianSeries&&t(a,function(a){a.render()});this.seriesGroup||(this.seriesGroup=b.g("series-group").attr({zIndex:3}).add());this.renderSeries();this.renderLabels();this.showCredits(c.credits);this.hasRendered=!0},showCredits:function(a){a.enabled&&!this.credits&&(this.credits=this.renderer.text(a.text,0,0).on("click",function(){a.href&&(location.href=a.href)}).attr({align:a.position.align,zIndex:8}).css(a.style).add().align(a.position))},destroy:function(){var a=this,b=a.axes,c=a.series,
|
||||
d=a.container,e,f=d&&d.parentNode;B(a,"destroy");Y[a.index]=u;Ha--;a.renderTo.removeAttribute("data-highcharts-chart");L(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();t("title subtitle chartBackground plotBackground plotBGImage plotBorder seriesGroup clipRect credits pointer scroller rangeSelector legend resetZoomButton tooltip renderer".split(" "),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});d&&(d.innerHTML="",L(d),f&&Na(d));for(e in a)delete a[e]},
|
||||
isReadyToRender:function(){var a=this;return!Z&&I==I.top&&"complete"!==y.readyState||ca&&!I.canvg?(ca?CanVGController.push(function(){a.firstRender()},a.options.global.canvasToolsURL):y.attachEvent("onreadystatechange",function(){y.detachEvent("onreadystatechange",a.firstRender);"complete"===y.readyState&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;a.isReadyToRender()&&(a.getContainer(),B(a,"init"),a.resetMargins(),a.setChartSize(),a.propFromSeries(),a.getAxes(),
|
||||
t(b.series||[],function(b){a.initSeries(b)}),a.linkSeries(),B(a,"beforeRender"),U.Pointer&&(a.pointer=new Ab(a,b)),a.render(),a.renderer.draw(),c&&c.apply(a,[a]),t(a.callbacks,function(b){b.apply(a,[a])}),a.cloneRenderTo(!0),B(a,"load"))},splashArray:function(a,b){var c=b[a],c=da(c)?c:[c,c,c,c];return[n(b[a+"Top"],c[0]),n(b[a+"Right"],c[1]),n(b[a+"Bottom"],c[2]),n(b[a+"Left"],c[3])]}};Ea.prototype.callbacks=[];var Ca=function(){};Ca.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,
|
||||
c);this.pointAttr={};a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length&&(a.colorCounter=0));a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.options.pointValKey||c.pointValKey;a=Ca.prototype.optionsToObject.call(this,a);v(this,a);this.options=this.options?v(this.options,a):a;d&&(this.y=this[d]);this.x===u&&c&&(this.x=b===u?c.autoIncrement():b);return this},optionsToObject:function(a){var b=
|
||||
{},c=this.series,d=c.pointArrayMap||["y"],e=d.length,f=0,g=0;if("number"===typeof a||null===a)b[d[0]]=a;else if(ta(a))for(a.length>e&&(c=typeof a[0],"string"===c?b.name=a[0]:"number"===c&&(b.x=a[0]),f++);g<e;)b[d[g++]]=a[f++];else"object"===typeof a&&(b=a,a.dataLabels&&(c._hasPointLabels=!0),a.marker&&(c._hasPointMarkers=!0));return b},destroy:function(){var a=this.series.chart,b=a.hoverPoints,c;a.pointCount--;b&&(this.setState(),qa(b,this),b.length||(a.hoverPoints=null));if(this===a.hoverPoint)this.onMouseOut();
|
||||
if(this.graphic||this.dataLabel)L(this),this.destroyElements();this.legendItem&&a.legend.destroyItem(this);for(c in this)this[c]=null},destroyElements:function(){for(var a="graphic dataLabel dataLabelUpper group connector shadowGroup".split(" "),b,c=6;c--;)b=a[c],this[b]&&(this[b]=this[b].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},tooltipFormatter:function(a){var b=
|
||||
this.series,c=b.tooltipOptions,d=n(c.valueDecimals,""),e=c.valuePrefix||"",f=c.valueSuffix||"";t(b.pointArrayMap||["y"],function(b){b="{point."+b;if(e||f)a=a.replace(b+"}",e+b+"}"+f);a=a.replace(b+"}",b+":,."+d+"f}")});return ua(a,{point:this,series:this.series})},firePointEvent:function(a,b,c){var d=this,e=this.series.options;(e.point.events[a]||d.options&&d.options.events&&d.options.events[a])&&this.importEvents();"click"===a&&e.allowPointSelect&&(c=function(a){d.select(null,a.ctrlKey||a.metaKey||
|
||||
a.shiftKey)});B(this,a,b,c)}};var fa=function(){};fa.prototype={isCartesian:!0,type:"line",pointClass:Ca,sorted:!0,requireSorting:!0,pointAttrToOptions:{stroke:"lineColor","stroke-width":"lineWidth",fill:"fillColor",r:"radius"},axisTypes:["xAxis","yAxis"],colorCounter:0,parallelArrays:["x","y"],init:function(a,b){var c=this,d,e,f=a.series,g=function(a,b){return n(a.options.index,a._i)-n(b.options.index,b._i)};c.chart=a;c.options=b=c.setOptions(b);c.linkedSeries=[];c.bindAxes();v(c,{name:b.name,state:"",
|
||||
pointAttr:{},visible:!1!==b.visible,selected:!0===b.selected});ca&&(b.animation=!1);e=b.events;for(d in e)F(c,d,e[d]);if(e&&e.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;c.getColor();c.getSymbol();t(c.parallelArrays,function(a){c[a+"Data"]=[]});c.setData(b.data,!1);c.isCartesian&&(a.hasCartesianSeries=!0);f.push(c);c._i=f.length-1;cb(f,g);this.yAxis&&cb(this.yAxis.series,g);t(f,function(a,b){a.index=b;a.name=a.name||"Series "+(b+1)})},bindAxes:function(){var a=
|
||||
this,b=a.options,c=a.chart,d;t(a.axisTypes||[],function(e){t(c[e],function(c){d=c.options;if(b[e]===d.index||b[e]!==u&&b[e]===d.id||b[e]===u&&0===d.index)c.series.push(a),a[e]=c,c.isDirty=!0});a[e]||a.optionalAxis===e||aa(18,!0)})},updateParallelArrays:function(a,b){var c=a.series,d=arguments;t(c.parallelArrays,"number"===typeof b?function(d){var f="y"===d&&c.toYData?c.toYData(a):a[d];c[d+"Data"][b]=f}:function(a){Array.prototype[b].apply(c[a+"Data"],Array.prototype.slice.call(d,2))})},autoIncrement:function(){var a=
|
||||
this.options,b=this.xIncrement,b=n(b,a.pointStart,0);this.pointInterval=n(this.pointInterval,a.pointInterval,1);this.xIncrement=b+this.pointInterval;return b},getSegments:function(){var a=-1,b=[],c,d=this.points,e=d.length;if(e)if(this.options.connectNulls){for(c=e;c--;)null===d[c].y&&d.splice(c,1);d.length&&(b=[d])}else t(d,function(c,g){null===c.y?(g>a+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,
|
||||
b=b.userOptions||{},d=b.plotOptions||{},e=c[this.type];this.userOptions=a;c=G(e,c.series,a);this.tooltipOptions=G(M.tooltip,M.plotOptions[this.type].tooltip,b.tooltip,d.series&&d.series.tooltip,d[this.type]&&d[this.type].tooltip,a.tooltip);null===e.marker&&delete c.marker;return c},getCyclic:function(a,b,c){var d=this.userOptions,e="_"+a+"Index",f=a+"Counter";b||(w(d[e])?b=d[e]:(d[e]=b=this.chart[f]%c.length,this.chart[f]+=1),b=c[b]);this[a]=b},getColor:function(){this.options.colorByPoint||this.getCyclic("color",
|
||||
this.options.color||Ya[this.type].color,this.chart.options.colors)},getSymbol:function(){var a=this.options.marker;this.getCyclic("symbol",a.symbol,this.chart.options.symbols);/^url/.test(this.symbol)&&(a.radius=0)},drawLegendSymbol:Lb.drawLineMarker,setData:function(a,b,c,d){var e=this,f=e.points,g=f&&f.length||0,h,k=e.options,l=e.chart,m=null,q=e.xAxis,r=q&&!!q.categories,p=e.tooltipPoints,v=k.turboThreshold,D=this.xData,w=this.yData,x=(h=e.pointArrayMap)&&h.length;a=a||[];h=a.length;b=n(b,!0);
|
||||
if(!1===d||!h||g!==h||e.cropped||e.hasGroupedData){e.xIncrement=null;e.pointRange=r?1:k.pointRange;e.colorCounter=0;t(this.parallelArrays,function(a){e[a+"Data"].length=0});if(v&&h>v){for(c=0;null===m&&c<h;)m=a[c],c++;if(ga(m)){r=n(k.pointStart,0);k=n(k.pointInterval,1);for(c=0;c<h;c++)D[c]=r,w[c]=a[c],r+=k;e.xIncrement=r}else if(ta(m))if(x)for(c=0;c<h;c++)k=a[c],D[c]=k[0],w[c]=k.slice(1,x+1);else for(c=0;c<h;c++)k=a[c],D[c]=k[0],w[c]=k[1];else aa(12)}else for(c=0;c<h;c++)a[c]!==u&&(k={series:e},
|
||||
e.pointClass.prototype.applyOptions.apply(k,[a[c]]),e.updateParallelArrays(k,c),r&&k.name&&(q.names[k.x]=k.name));pa(w[0])&&aa(14,!0);e.data=[];e.options.data=a;for(c=g;c--;)f[c]&&f[c].destroy&&f[c].destroy();p&&(p.length=0);q&&(q.minRange=q.userMinRange);e.isDirty=e.isDirtyData=l.isDirtyBox=!0;c=!1}else t(a,function(a,b){f[b].update(a,!1,null,!1)});b&&l.redraw(c)},processData:function(a){var b=this.xData,c=this.yData,d=b.length,e;e=0;var f,g,h=this.xAxis,k,l=this.options;k=l.cropThreshold;var m=
|
||||
0,q=this.isCartesian,r,p;if(q&&!this.isDirty&&!h.isDirty&&!this.yAxis.isDirty&&!a)return!1;h&&(r=h.getExtremes(),p=r.min,r=r.max);if(q&&this.sorted&&(!k||d>k||this.forceCrop))if(b[d-1]<p||b[0]>r)b=[],c=[];else if(b[0]<p||b[d-1]>r)e=this.cropData(this.xData,this.yData,p,r),b=e.xData,c=e.yData,e=e.start,f=!0,m=b.length;for(k=b.length-1;0<=k;k--)d=b[k]-b[k-1],!f&&b[k]>p&&b[k]<r&&m++,0<d&&(g===u||d<g)?g=d:0>d&&this.requireSorting&&aa(15);this.cropped=f;this.cropStart=e;this.processedXData=b;this.processedYData=
|
||||
c;this.activePointCount=m;null===l.pointRange&&(this.pointRange=g||1);this.closestPointRange=g},cropData:function(a,b,c,d){var e=a.length,f=0,g=e,h=n(this.cropShoulder,1),k;for(k=0;k<e;k++)if(a[k]>=c){f=x(0,k-h);break}for(;k<e;k++)if(a[k]>d){g=k+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,k,l=this.hasGroupedData,m,q=[],
|
||||
r;b||l||(b=[],b.length=a.length,b=this.data=b);for(r=0;r<g;r++)k=h+r,l?q[r]=(new f).init(this,[d[r]].concat(ea(e[r]))):(b[k]?m=b[k]:a[k]!==u&&(b[k]=m=(new f).init(this,a[k],d[r])),q[r]=m),q[r].index=k;if(b&&(g!==(c=b.length)||l))for(r=0;r<c;r++)r!==h||l||(r+=g),b[r]&&(b[r].destroyElements(),b[r].plotX=u);this.data=b;this.points=q},getExtremes:function(a){var b=this.yAxis,c=this.processedXData,d,e=[],f=0;d=this.xAxis.getExtremes();var g=d.min,h=d.max,k,l,m,q;a=a||this.stackedYData||this.processedYData;
|
||||
d=a.length;for(q=0;q<d;q++)if(l=c[q],m=a[q],k=null!==m&&m!==u&&(!b.isLog||m.length||0<m),l=this.getExtremesFromAll||this.cropped||(c[q+1]||l)>=g&&(c[q-1]||l)<=h,k&&l)if(k=m.length)for(;k--;)null!==m[k]&&(e[f++]=m[k]);else e[f++]=m;this.dataMin=n(void 0,La(e));this.dataMax=n(void 0,va(e))},translate:function(){this.processedXData||this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,k=a.pointPlacement,
|
||||
l="between"===k||ga(k),m=a.threshold,a=0;a<g;a++){var q=f[a],r=q.x,p=q.y,t=q.low,v=b&&e.stacks[(this.negStacks&&p<m?"-":"")+this.stackKey];e.isLog&&0>=p&&(q.y=p=null,aa(10));q.plotX=c.translate(r,0,0,0,1,k,"flags"===this.type);b&&this.visible&&v&&v[r]&&(v=v[r],p=v.points[this.index+","+a],t=p[0],p=p[1],0===t&&(t=n(m,e.min)),e.isLog&&0>=t&&(t=null),q.total=q.stackTotal=v.total,q.percentage=v.total&&q.y/v.total*100,q.stackY=p,v.setOffset(this.pointXOffset||0,this.barW||0));q.yBottom=w(t)?e.translate(t,
|
||||
0,1,0,1):null;h&&(p=this.modifyValue(p,q));q.plotY="number"===typeof p&&Infinity!==p?e.translate(p,0,1,0,1):u;q.clientX=l?c.translate(r,0,0,0,1):q.plotX;q.negative=q.y<(m||0);q.category=d&&d[q.x]!==u?d[q.x]:q.x}this.getSegments()},animate:function(a){var b=this.chart,c=b.renderer,d;d=this.options.animation;var e=this.clipBox||b.clipBox,f=b.inverted,g;d&&!da(d)&&(d=Ya[this.type].animation);g=["_sharedClip",d.duration,d.easing,e.height].join();a?(a=b[g],d=b[g+"m"],a||(b[g]=a=c.clipRect(v(e,{width:0})),
|
||||
b[g+"m"]=d=c.clipRect(-99,f?-b.plotLeft:-b.plotTop,99,f?b.chartWidth:b.chartHeight)),this.group.clip(a),this.markerGroup.clip(d),this.sharedClipKey=g):((a=b[g])&&a.animate({width:b.plotSizeX},d),b[g+"m"]&&b[g+"m"].animate({width:b.plotSizeX+99},d),this.animate=null)},afterAnimate:function(){var a=this.chart,b=this.sharedClipKey,c=this.group,d=this.clipBox;c&&!1!==this.options.clip&&(b&&d||c.clip(d?a.renderer.clipRect(d):a.clipRect),this.markerGroup.clip());B(this,"afterAnimate");setTimeout(function(){b&&
|
||||
a[b]&&(d||(a[b]=a[b].destroy()),a[b+"m"]&&(a[b+"m"]=a[b+"m"].destroy()))},100)},drawPoints:function(){var a,b=this.points,c=this.chart,d,e,f,g,h,k,l,m,q=this.options.marker,r=this.pointAttr[""],p,t,w,x=this.markerGroup,y=n(q.enabled,!this.requireSorting||this.activePointCount<.5*this.xAxis.len/q.radius);if(!1!==q.enabled||this._hasPointMarkers)for(f=b.length;f--;)g=b[f],d=K(g.plotX),e=g.plotY,m=g.graphic,p=g.marker||{},t=!!g.marker,a=y&&p.enabled===u||p.enabled,w=c.isInsidePlot(A(d),e,c.inverted),
|
||||
a&&e!==u&&!isNaN(e)&&null!==g.y?(a=g.pointAttr[g.selected?"select":""]||r,h=a.r,k=n(p.symbol,this.symbol),l=0===k.indexOf("url"),m?m[w?"show":"hide"](!0).animate(v({x:d-h,y:e-h},m.symbolName?{width:2*h,height:2*h}:{})):w&&(0<h||l)&&(g.graphic=c.renderer.symbol(k,d-h,e-h,2*h,2*h,t?p:q).attr(a).add(x))):m&&(g.graphic=m.destroy())},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={};a=a||{};b=b||{};c=c||{};d=d||{};for(f in e)g=e[f],h[f]=n(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=
|
||||
this,b=a.options,c=Ya[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color;f={stroke:g,fill:g};var h=a.points||[],k,l=[],m,q=a.pointAttrToOptions;m=a.hasPointSpecificOptions;var r=b.negativeColor,p=c.lineColor,n=c.fillColor;k=b.turboThreshold;var u;b.marker?(e.radius=e.radius||c.radius+e.radiusPlus,e.lineWidth=e.lineWidth||c.lineWidth+e.lineWidthPlus):e.color=e.color||za(e.color||g).brighten(e.brightness).get();l[""]=a.convertAttribs(c,f);t(["hover","select"],function(b){l[b]=a.convertAttribs(d[b],
|
||||
l[""])});a.pointAttr=l;g=h.length;if(!k||g<k||m)for(;g--;){k=h[g];(c=k.options&&k.options.marker||k.options)&&!1===c.enabled&&(c.radius=0);k.negative&&r&&(k.color=k.fillColor=r);m=b.colorByPoint||k.color;if(k.options)for(u in q)w(c[q[u]])&&(m=!0);m?(c=c||{},m=[],d=c.states||{},f=d.hover=d.hover||{},b.marker||(f.color=f.color||!k.options.color&&e.color||za(k.color).brighten(f.brightness||e.brightness).get()),f={color:k.color},n||(f.fillColor=k.color),p||(f.lineColor=k.color),m[""]=a.convertAttribs(v(f,
|
||||
c),l[""]),m.hover=a.convertAttribs(d.hover,l.hover,m[""]),m.select=a.convertAttribs(d.select,l.select,m[""])):m=l;k.pointAttr=m}},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(na),d,e,f=a.data||[],g,h,k;B(a,"destroy");L(a);t(a.axisTypes||[],function(b){if(k=a[b])qa(k.series,a),k.isDirty=k.forceRedraw=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(e=f.length;e--;)(g=f[e])&&g.destroy&&g.destroy();a.points=null;clearTimeout(a.animationTimeout);t("area graph dataLabelsGroup group markerGroup tracker graphNeg areaNeg posClip negClip".split(" "),
|
||||
function(b){a[b]&&(d=c&&"group"===b?"hide":"destroy",a[b][d]())});b.hoverSeries===a&&(b.hoverSeries=null);qa(b.series,a);for(h in a)delete a[h]},getSegmentPath:function(a){var b=this,c=[],d=b.options.step;t(a,function(e,f){var g=e.plotX,h=e.plotY,k;b.getPointSpline?c.push.apply(c,b.getPointSpline(a,e,f)):(c.push(f?"L":"M"),d&&f&&(k=a[f-1],"right"===d?c.push(k.plotX,h):"center"===d?c.push((k.plotX+g)/2,k.plotY,(k.plotX+g)/2,h):c.push(g,k.plotY)),c.push(e.plotX,e.plotY))});return c},getGraphPath:function(){var a=
|
||||
this,b=[],c,d=[];t(a.segments,function(e){c=a.getSegmentPath(e);1<e.length?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f="square"!==b.linecap,g=this.getGraphPath(),h=b.negativeColor;h&&c.push(["graphNeg",h]);t(c,function(c,h){var m=c[0],q=a[m];q?(Xa(q),q.animate({d:g})):d&&g.length&&(q={stroke:c[1],"stroke-width":d,fill:"none",zIndex:1},e?q.dashstyle=e:f&&(q["stroke-linecap"]=
|
||||
q["stroke-linejoin"]="round"),a[m]=a.chart.renderer.path(g).attr(q).add(a.group).shadow(!h&&b.shadow))})},clipNeg:function(){var a=this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,k=this.negClip;e=b.chartWidth;var l=b.chartHeight,m=x(e,l),q=this.yAxis;d&&(f||g)&&(d=A(q.toPixels(a.threshold||0,!0)),0>d&&(m-=d),a={x:0,y:0,width:m,height:d},m={x:0,y:d,width:m,height:m},b.inverted&&(a.height=m.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-
|
||||
d-b.plotLeft,y:0,width:e,height:l},m={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e})),q.reversed?(b=m,e=a):(b=a,e=m),h?(h.animate(b),k.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=k=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(k)),g&&(g.clip(h),this.areaNeg.clip(k))))},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};t(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;b.xAxis&&(F(c,"resize",a),F(b,
|
||||
"destroy",function(){L(c,"resize",a)}),a(),b.invertGroups=a)},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||.1}).add(e));f[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){var a=this.chart,b=this.xAxis,c=this.yAxis;a.inverted&&(b=c,c=this.xAxis);return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,d=a.options,e=(c=d.animation)&&!!a.animate&&
|
||||
b.renderer.isSVG&&n(c.duration,500)||0,f=a.visible?"visible":"hidden",g=d.zIndex,h=a.hasRendered,k=b.seriesGroup;c=a.plotGroup("group","series",f,g,k);a.markerGroup=a.plotGroup("markerGroup","markers",f,g,k);e&&a.animate(!0);a.getAttribs();c.inverted=a.isCartesian?b.inverted:!1;a.drawGraph&&(a.drawGraph(),a.clipNeg());t(a.points,function(a){a.redraw&&a.redraw()});a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&!1!==a.options.enableMouseTracking&&a.drawTracker();b.inverted&&
|
||||
a.invertGroups();!1===d.clip||a.sharedClipKey||h||c.clip(b.clipRect);e&&a.animate();h||(e?a.animationTimeout=setTimeout(function(){a.afterAnimate()},e):a.afterAnimate());a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:n(d&&d.left,a.plotLeft),translateY:n(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints&&this.setTooltipPoints(!0);
|
||||
this.render();b&&B(this,"updatedData")}};v(Ea.prototype,{addSeries:function(a,b,c){var d,e=this;a&&(b=n(b,!0),B(e,"addSeries",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=b?"xAxis":"yAxis",f=this.options;new ma(this,G(a,{index:this[e].length,isX:b}));f[e]=ea(f[e]||{});f[e].push(a);n(c,!0)&&this.redraw(d)},showLoading:function(a){var b=this,c=b.options,d=b.loadingDiv,e=c.loading,f=function(){d&&J(d,{left:b.plotLeft+
|
||||
"px",top:b.plotTop+"px",width:b.plotWidth+"px",height:b.plotHeight+"px"})};d||(b.loadingDiv=d=ja("div",{className:"highcharts-loading"},v(e.style,{zIndex:10,display:"none"}),b.container),b.loadingSpan=ja("span",null,e.labelStyle,d),F(b,"redraw",f));b.loadingSpan.innerHTML=a||c.lang.loading;b.loadingShown||(J(d,{opacity:0,display:""}),Wa(d,{opacity:e.style.opacity},{duration:e.showDuration||0}),b.loadingShown=!0);f()},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&Wa(b,{opacity:0},
|
||||
{duration:a.loading.hideDuration||100,complete:function(){J(b,{display:"none"})}});this.loadingShown=!1}});v(Ca.prototype,{update:function(a,b,c,d){function e(){f.applyOptions(a);da(a)&&!ta(a)&&(f.redraw=function(){h&&(a&&a.marker&&a.marker.symbol?f.graphic=h.destroy():h.attr(f.pointAttr[f.state||""]));a&&a.dataLabels&&f.dataLabel&&(f.dataLabel=f.dataLabel.destroy());f.redraw=null});k=f.index;g.updateParallelArrays(f,k);m.data[k]=f.options;g.isDirty=g.isDirtyData=!0;!g.fixedBox&&g.hasCartesianSeries&&
|
||||
(l.isDirtyBox=!0);"point"===m.legendType&&l.legend.destroyItem(f);b&&l.redraw(c)}var f=this,g=f.series,h=f.graphic,k,l=g.chart,m=g.options;b=n(b,!0);!1===d?e():f.firePointEvent("update",{options:a},e)},remove:function(a,b){var c=this,d=c.series,e=d.points,f=d.chart,g,h=d.data;S=n(b,f.animation);a=n(a,!0);c.firePointEvent("remove",null,function(){g=Ia(c,h);h.length===e.length&&e.splice(g,1);h.splice(g,1);d.options.data.splice(g,1);d.updateParallelArrays(c,"splice",g,1);c.destroy();d.isDirty=!0;d.isDirtyData=
|
||||
!0;a&&f.redraw()})}});v(fa.prototype,{addPoint:function(a,b,c,d){var e=this.options,f=this.data,g=this.graph,h=this.area,k=this.chart,l=this.xAxis&&this.xAxis.names,m=g&&g.shift||0,q=e.data,r,p=this.xData;S=n(d,k.animation);c&&t([g,h,this.graphNeg,this.areaNeg],function(a){a&&(a.shift=m+1)});h&&(h.isArea=!0);b=n(b,!0);d={series:this};this.pointClass.prototype.applyOptions.apply(d,[a]);g=d.x;h=p.length;if(this.requireSorting&&g<p[h-1])for(r=!0;h&&p[h-1]>g;)h--;this.updateParallelArrays(d,"splice",
|
||||
h,0,0);this.updateParallelArrays(d,h);l&&d.name&&(l[g]=d.name);q.splice(h,0,a);r&&(this.data.splice(h,0,null),this.processData());"point"===e.legendType&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):(f.shift(),this.updateParallelArrays(d,"shift"),q.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),k.redraw())},remove:function(a,b){var c=this,d=c.chart;a=n(a,!0);c.isRemoving||(c.isRemoving=!0,B(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();
|
||||
a&&d.redraw(b)}));c.isRemoving=!1},update:function(a,b){var c=this,d=this.chart,e=this.userOptions,f=this.type,g=P[f].prototype,h=["group","markerGroup","dataLabelsGroup"],k;t(h,function(a){h[a]=c[a];delete c[a]});a=G(e,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(k in g)g.hasOwnProperty(k)&&(this[k]=u);v(this,P[a.type||f].prototype);t(h,function(a){c[a]=h[a]});this.init(d,a);d.linkSeries();n(b,!0)&&d.redraw(!1)}});v(ma.prototype,{update:function(a,
|
||||
b){var c=this.chart;a=c.options[this.coll][this.options.index]=G(this.userOptions,a);this.destroy(!0);this._addedPlotLB=u;this.init(c,v(a,{events:u}));c.isDirtyBox=!0;n(b,!0)&&c.redraw()},remove:function(a){for(var b=this.chart,c=this.coll,d=this.series,e=d.length;e--;)d[e]&&d[e].remove(!1);qa(b.axes,this);qa(b[c],this);b.options[c].splice(this.options.index,1);t(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;n(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},
|
||||
b)},setCategories:function(a,b){this.update({categories:a},b)}});var Mb=nb(fa);P.line=Mb;fa.prototype.drawDataLabels=function(){var a=this,b=a.options,c=b.cursor,d=b.dataLabels,e=a.points,f,g,h=a.hasRendered||0,k,l;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),l=a.plotGroup("dataLabelsGroup","data-labels",d.defer?"hidden":"visible",d.zIndex||6),n(d.defer,!0)&&(l.attr({opacity:+h}),h||F(a,"afterAnimate",function(){a.visible&&l.show();l[b.animation?"animate":"attr"]({opacity:1},
|
||||
{duration:200})})),g=d,t(e,function(b){var e,h=b.dataLabel,p,t,x=b.connector,A=!0;f=b.options&&b.options.dataLabels;e=n(f&&f.enabled,g.enabled);if(h&&!e)b.dataLabel=h.destroy();else if(e){d=G(g,f);e=d.rotation;p=b.getLabelConfig();k=d.format?ua(d.format,p):d.formatter.call(p,d);d.style.color=n(d.color,d.style.color,a.color,"black");if(h)w(k)?(h.attr({text:k}),A=!1):(b.dataLabel=h=h.destroy(),x&&(b.connector=x.destroy()));else if(w(k)){h={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,
|
||||
r:d.borderRadius||0,rotation:e,padding:d.padding,zIndex:1};for(t in h)h[t]===u&&delete h[t];h=b.dataLabel=a.chart.renderer[e?"text":"label"](k,0,-999,null,null,null,d.useHTML).attr(h).css(v(d.style,c&&{cursor:c})).add(l).shadow(d.shadow)}h&&a.alignDataLabel(b,h,d,null,A)}})};fa.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=n(a.plotX,-999),k=n(a.plotY,-999),l=b.getBBox();if(a=this.visible&&(a.series.forceDL||f.isInsidePlot(h,A(k),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-
|
||||
1,g)))d=v({x:g?f.plotWidth-k:h,y:A(g?f.plotHeight-h:k),width:0,height:0},d),v(c,{width:l.width,height:l.height}),c.rotation?b[e?"attr":"animate"]({x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2}).attr({align:c.align}):(b.align(c,null,d),g=b.alignAttr,"justify"===n(c.overflow,"justify")?this.justifyDataLabel(b,c,g,l,d,e):n(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+l.width,g.y+l.height)));a||(b.attr({y:-999}),b.placed=!1)};fa.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,
|
||||
h=b.align,k=b.verticalAlign,l,m;l=c.x;0>l&&("right"===h?b.align="left":b.x=-l,m=!0);l=c.x+d.width;l>g.plotWidth&&("left"===h?b.align="right":b.x=g.plotWidth-l,m=!0);l=c.y;0>l&&("bottom"===k?b.verticalAlign="top":b.y=-l,m=!0);l=c.y+d.height;l>g.plotHeight&&("top"===k?b.verticalAlign="bottom":b.y=g.plotHeight-l,m=!0);m&&(a.placed=!f,a.align(b,null,e))};P.pie&&(P.pie.prototype.drawDataLabels=function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=n(e.connectorPadding,10),g=n(e.connectorWidth,
|
||||
1),h=d.plotWidth,k=d.plotHeight,l,m,q=n(e.softConnector,!0),r=e.distance,p=a.center,u=p[2]/2,v=p[1],w=0<r,y,z,B,G=[[],[]],E,F,N,M,C,I=[0,0,0,0],R=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){fa.prototype.drawDataLabels.apply(a);t(b,function(a){a.dataLabel&&a.visible&&G[a.half].push(a)});for(M=2;M--;){var K=[],P=[],J=G[M],O=J.length,L;if(O){a.sortByAngle(J,M-.5);for(C=b=0;!b&&J[C];)b=J[C]&&J[C].dataLabel&&(J[C].dataLabel.getBBox().height||21),C++;if(0<r){z=T(v+u+r,d.plotHeight);
|
||||
for(C=x(0,v-u-r);C<=z;C+=b)K.push(C);z=K.length;if(O>z){c=[].concat(J);c.sort(R);for(C=O;C--;)c[C].rank=C;for(C=O;C--;)J[C].rank>=z&&J.splice(C,1);O=J.length}for(C=0;C<O;C++){c=J[C];B=c.labelPos;c=9999;var U,S;for(S=0;S<z;S++)U=X(K[S]-B[1]),U<c&&(c=U,L=S);if(L<C&&null!==K[C])L=C;else for(z<O-C+L&&null!==K[C]&&(L=z-O+C);null===K[L];)L++;P.push({i:L,y:K[L]});K[L]=null}P.sort(R)}for(C=0;C<O;C++){c=J[C];B=c.labelPos;y=c.dataLabel;N=!1===c.visible?"hidden":"visible";c=B[1];if(0<r){if(z=P.pop(),L=z.i,F=
|
||||
z.y,c>F&&null!==K[L+1]||c<F&&null!==K[L-1])F=T(x(0,c),d.plotHeight)}else F=c;E=e.justify?p[0]+(M?-1:1)*(u+r):a.getX(F===v-u-r||F===v+u+r?c:F,M);y._attr={visibility:N,align:B[6]};y._pos={x:E+e.x+({left:f,right:-f}[B[6]]||0),y:F+e.y-10};y.connX=E;y.connY=F;null===this.options.size&&(z=y.width,E-z<f?I[3]=x(A(z-E+f),I[3]):E+z>h-f&&(I[1]=x(A(E+z-h+f),I[1])),0>F-b/2?I[0]=x(A(-F+b/2),I[0]):F+b/2>k&&(I[2]=x(A(F+b/2-k),I[2])))}}}if(0===va(I)||this.verifyDataLabelOverflow(I))this.placeDataLabels(),w&&g&&t(this.points,
|
||||
function(b){l=b.connector;B=b.labelPos;(y=b.dataLabel)&&y._pos?(N=y._attr.visibility,E=y.connX,F=y.connY,m=q?["M",E+("left"===B[6]?5:-5),F,"C",E,F,2*B[2]-B[4],2*B[3]-B[5],B[2],B[3],"L",B[4],B[5]]:["M",E+("left"===B[6]?5:-5),F,"L",B[2],B[3],"L",B[4],B[5]],l?(l.animate({d:m}),l.attr("visibility",N)):b.connector=l=a.chart.renderer.path(m).attr({"stroke-width":g,stroke:e.connectorColor||b.color||"#606060",visibility:N}).add(a.dataLabelsGroup)):l&&(b.connector=l.destroy())})}},P.pie.prototype.placeDataLabels=
|
||||
function(){t(this.points,function(a){a=a.dataLabel;var b;a&&((b=a._pos)?(a.attr(a._attr),a[a.moved?"animate":"attr"](b),a.moved=!0):a&&a.attr({y:-999}))})},P.pie.prototype.alignDataLabel=Eb,P.pie.prototype.verifyDataLabelOverflow=function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||80,f;null!==d[0]?e=x(b[2]-x(a[1],a[3]),c):(e=x(b[2]-a[1]-a[3],c),b[0]+=(a[3]-a[1])/2);null!==d[1]?e=x(T(e,b[2]-x(a[0],a[2])),c):(e=x(T(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);e<b[2]?(b[2]=e,this.translate(b),
|
||||
t(this.points,function(a){a.dataLabel&&(a.dataLabel._pos=null)}),this.drawDataLabels&&this.drawDataLabels()):f=!0;return f});P.column&&(P.column.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=a.dlBox||a.shapeArgs,k=a.below||a.plotY>n(this.translatedThreshold,f.plotSizeY),l=n(c.inside,!!this.options.stacking);h&&(d=G(h),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),l||(g?(d.x+=k?0:d.width,d.width=0):(d.y+=k?d.height:0,d.height=
|
||||
0)));c.align=n(c.align,!g||l?"center":k?"right":"left");c.verticalAlign=n(c.verticalAlign,g||l?"middle":k?"top":"bottom");fa.prototype.alignDataLabel.call(this,a,b,c,d,e)});var $a=U.TrackerMixin={drawTrackerPoint:function(){var a=this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver();for(;d&&!e;)e=d.point,d=d.parentNode;if(e!==u&&e!==b.hoverPoint)e.onMouseOver(c)};t(a.points,function(a){a.graphic&&(a.graphic.element.point=
|
||||
a);a.dataLabel&&(a.dataLabel.element.point=a)});a._hasTracking||(t(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),Ga))a[b].on("touchstart",f)}),a._hasTracking=!0)},drawTrackerGraph:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,k=f.options.tooltip.snap,l=a.tracker,m=b.cursor,q=m&&{cursor:m},m=a.singlePoints,n,
|
||||
p=function(){if(f.hoverSeries!==a)a.onMouseOver()},u="rgba(192,192,192,"+(Z?1E-4:.002)+")";if(e&&!c)for(n=e+1;n--;)"M"===d[n]&&d.splice(n+1,0,d[n+1]-k,d[n+2],"L"),(n&&"M"===d[n]||n===e)&&d.splice(n,0,"L",d[n-2]+k,d[n-1]);for(n=0;n<m.length;n++)e=m[n],d.push("M",e.plotX-k,e.plotY,"L",e.plotX+k,e.plotY);l?l.attr({d:d}):(a.tracker=h.path(d).attr({"stroke-linejoin":"round",visibility:a.visible?"visible":"hidden",stroke:u,fill:c?u:"none","stroke-width":b.lineWidth+(c?0:2*k),zIndex:2}).add(a.group),t([a.tracker,
|
||||
a.markerGroup],function(a){a.addClass("highcharts-tracker").on("mouseover",p).on("mouseout",function(a){g.onTrackerMouseOut(a)}).css(q);if(Ga)a.on("touchstart",p)}))}};P.column&&(ColumnSeries.prototype.drawTracker=$a.drawTrackerPoint);P.pie&&(P.pie.prototype.drawTracker=$a.drawTrackerPoint);P.scatter&&(ScatterSeries.prototype.drawTracker=$a.drawTrackerPoint);v(Za.prototype,{setItemEvents:function(a,b,c,d,e){var f=this;(c?b:a.legendGroup).on("mouseover",function(){a.setState("hover");b.css(f.options.itemHoverStyle)}).on("mouseout",
|
||||
function(){b.css(a.visible?d:e);a.setState()}).on("click",function(b){var c=function(){a.setVisible()};b={browserEvent:b};a.firePointEvent?a.firePointEvent("legendItemClick",b,c):B(a,"legendItemClick",b,c)})},createCheckboxForItem:function(a){a.checkbox=ja("input",{type:"checkbox",checked:a.selected,defaultChecked:a.selected},this.options.itemCheckboxStyle,this.chart.container);F(a.checkbox,"click",function(b){B(a,"checkboxClick",{checked:b.target.checked},function(){a.select()})})}});M.legend.itemStyle.cursor=
|
||||
"pointer";v(Ea.prototype,{showResetZoom:function(){var a=this,b=M.lang,c=a.options.chart.resetZoomButton,d=c.theme,e=d.states,f="chart"===c.relativeTo?null:"plotBox";this.resetZoomButton=a.renderer.button(b.resetZoom,null,null,function(){a.zoomOut()},d,e&&e.hover).attr({align:c.position.align,title:b.resetZoomTitle}).add().align(c.position,!1,f)},zoomOut:function(){var a=this;B(a,"selection",{resetSelection:!0},function(){a.zoom()})},zoom:function(a){var b,c=this.pointer,d=!1,e;!a||a.resetSelection?
|
||||
t(this.axes,function(a){b=a.zoom()}):t(a.xAxis.concat(a.yAxis),function(a){var e=a.axis,h=e.isXAxis;if(c[h?"zoomX":"zoomY"]||c[h?"pinchX":"pinchY"])b=e.zoom(a.min,a.max),e.displayBtn&&(d=!0)});e=this.resetZoomButton;d&&!e?this.showResetZoom():!d&&da(e)&&(this.resetZoomButton=e.destroy());b&&this.redraw(n(this.options.chart.animation,a&&a.animation,100>this.pointCount))},pan:function(a,b){var c=this,d=c.hoverPoints,e;d&&t(d,function(a){a.setState()});t("xy"===b?[1,0]:[1],function(b){var d=a[b?"chartX":
|
||||
"chartY"],h=c[b?"xAxis":"yAxis"][0],k=c[b?"mouseDownX":"mouseDownY"],l=(h.pointRange||0)/2,m=h.getExtremes(),n=h.toValue(k-d,!0)+l,k=h.toValue(k+c[b?"plotWidth":"plotHeight"]-d,!0)-l;h.series.length&&n>T(m.dataMin,m.min)&&k<x(m.dataMax,m.max)&&(h.setExtremes(n,k,!1,!1,{trigger:"pan"}),e=!0);c[b?"mouseDownX":"mouseDownY"]=d});e&&c.redraw(!1);J(c.container,{cursor:"move"})}});v(Ca.prototype,{select:function(a,b){var c=this,d=c.series,e=d.chart;a=n(a,!c.selected);c.firePointEvent(a?"select":"unselect",
|
||||
{accumulate:b},function(){c.selected=c.options.selected=a;d.options.data[Ia(c,d.data)]=c.options;c.setState(a&&"select");b||t(e.getSelectedPoints(),function(a){a.selected&&a!==c&&(a.selected=a.options.selected=!1,d.options.data[Ia(a,d.data)]=a.options,a.setState(""),a.firePointEvent("unselect"))})})},onMouseOver:function(a){var b=this.series,c=b.chart,d=c.tooltip,e=c.hoverPoint;if(e&&e!==this)e.onMouseOut();this.firePointEvent("mouseOver");!d||d.shared&&!b.noSharedTooltip||d.refresh(this,a);this.setState("hover");
|
||||
c.hoverPoint=this},onMouseOut:function(){var a=this.series.chart,b=a.hoverPoints;this.firePointEvent("mouseOut");b&&-1!==Ia(this,b)||(this.setState(),a.hoverPoint=null)},importEvents:function(){if(!this.hasImportedEvents){var a=G(this.series.options.point,this.options).events,b;this.events=a;for(b in a)F(this,b,a[b]);this.hasImportedEvents=!0}},setState:function(a,b){var c=this.plotX,d=this.plotY,e=this.series,f=e.options.states,g=Ya[e.type].marker&&e.options.marker,h=g&&!g.enabled,k=g&&g.states[a],
|
||||
l=k&&!1===k.enabled,m=e.stateMarkerGraphic,n=this.marker||{},r=e.chart,p=e.halo,t;a=a||"";t=this.pointAttr[a]||e.pointAttr[a];if(!(a===this.state&&!b||this.selected&&"select"!==a||f[a]&&!1===f[a].enabled||a&&(l||h&&!1===k.enabled)||a&&n.states&&n.states[a]&&!1===n.states[a].enabled)){if(this.graphic)g=g&&this.graphic.symbolName&&t.r,this.graphic.attr(G(t,g?{x:c-g,y:d-g,width:2*g,height:2*g}:{})),m&&m.hide();else{if(a&&k)if(g=k.radius,n=n.symbol||e.symbol,m&&m.currentSymbol!==n&&(m=m.destroy()),m)m[b?
|
||||
"animate":"attr"]({x:c-g,y:d-g});else n&&(e.stateMarkerGraphic=m=r.renderer.symbol(n,c-g,d-g,2*g,2*g).attr(t).add(e.markerGroup),m.currentSymbol=n);if(m)m[a&&r.isInsidePlot(c,d,r.inverted)?"show":"hide"]()}(c=f[a]&&f[a].halo)&&c.size?(p||(e.halo=p=r.renderer.path().add(e.seriesGroup)),p.attr(v({fill:za(this.color||e.color).setOpacity(c.opacity).get()},c.attributes))[b?"animate":"attr"]({d:this.haloPath(c.size)})):p&&p.attr({d:[]});this.state=a}},haloPath:function(a){var b=this.series,c=b.chart,d=
|
||||
b.getPlotBox(),e=c.inverted;return c.renderer.symbols.circle(d.translateX+(e?b.yAxis.len-this.plotY:this.plotX)-a,d.translateY+(e?b.xAxis.len-this.plotX:this.plotY)-a,2*a,2*a)}});v(fa.prototype,{onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&B(this,"mouseOver");this.setState("hover");a.hoverSeries=this},onMouseOut:function(){var a=this.options,b=this.chart,c=b.tooltip,d=b.hoverPoint;if(d)d.onMouseOut();this&&a.events.mouseOut&&
|
||||
B(this,"mouseOut");!c||a.stickyTracking||c.shared&&!this.noSharedTooltip||c.hide();this.setState();b.hoverSeries=null},setState:function(a){var b=this.options,c=this.graph,d=this.graphNeg,e=b.states,b=b.lineWidth;a=a||"";this.state!==a&&(this.state=a,e[a]&&!1===e[a].enabled||(a&&(b=e[a].lineWidth||b+(e[a].lineWidthPlus||0)),c&&!c.dashstyle&&(a={"stroke-width":b},c.attr(a),d&&d.attr(a))))},setVisible:function(a,b){var c=this,d=c.chart,e=c.legendItem,f,g=d.options.chart.ignoreHiddenSeries,h=c.visible;
|
||||
f=(c.visible=a=c.userOptions.visible=a===u?!h:a)?"show":"hide";t(["group","dataLabelsGroup","markerGroup","tracker"],function(a){if(c[a])c[a][f]()});if(d.hoverSeries===c)c.onMouseOut();e&&d.legend.colorizeItem(c,a);c.isDirty=!0;c.options.stacking&&t(d.series,function(a){a.options.stacking&&a.visible&&(a.isDirty=!0)});t(c.linkedSeries,function(b){b.setVisible(a,!1)});g&&(d.isDirtyBox=!0);!1!==b&&d.redraw();B(c,f)},setTooltipPoints:function(a){var b=[],c,d,e=this.xAxis,f=e&&e.getExtremes(),g=e?e.tooltipLen||
|
||||
e.len:this.chart.plotSizeX,h,k,l=[];if(!1!==this.options.enableMouseTracking&&!this.singularTooltips){a&&(this.tooltipPoints=null);t(this.segments||this.points,function(a){b=b.concat(a)});e&&e.reversed&&(b=b.reverse());this.orderTooltipPoints&&this.orderTooltipPoints(b);a=b.length;for(k=0;k<a;k++)if(e=b[k],c=e.x,c>=f.min&&c<=f.max)for(h=b[k+1],c=d===u?0:d+1,d=b[k+1]?T(x(0,K((e.clientX+(h?h.wrappedClientX||h.clientX:g))/2)),g):g;0<=c&&c<=d;)l[c++]=e;this.tooltipPoints=l}},show:function(){this.setVisible(!0)},
|
||||
hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===u?!this.selected:a;this.checkbox&&(this.checkbox.checked=a);B(this,a?"select":"unselect")},drawTracker:$a.drawTrackerGraph});v(U,{Axis:ma,Chart:Ea,Color:za,Point:Ca,Tick:xa,Renderer:Ja,Series:fa,SVGElement:O,SVGRenderer:Ja,arrayMin:La,arrayMax:va,charts:Y,dateFormat:Ka,format:ua,pathAnim:ib,getOptions:function(){return M},hasBidiBug:Db,isTouchDevice:wb,numberFormat:ka,seriesTypes:P,setOptions:function(a){M=G(!0,M,a);pb();
|
||||
return M},addEvent:F,removeEvent:L,createElement:ja,discardElement:Na,css:J,each:t,extend:v,map:kb,merge:G,pick:n,splat:ea,extendClass:nb,pInt:E,wrap:bb,svg:Z,canvas:ca,vml:!Z&&!ca,product:"Highcharts 4.0.4",version:"/Highstock 2.0.4"})})();
|
381
domain-server/resources/web/stats/js/json.human.js
Executable file
381
domain-server/resources/web/stats/js/json.human.js
Executable file
|
@ -0,0 +1,381 @@
|
|||
/*globals define, module, require, document*/
|
||||
(function (root, factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], factory);
|
||||
} else if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
root.JsonHuman = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
"use strict";
|
||||
|
||||
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
|
||||
function makePrefixer(prefix) {
|
||||
return function (name) {
|
||||
return prefix + "-" + name;
|
||||
};
|
||||
}
|
||||
|
||||
function isArray(obj) {
|
||||
return toString.call(obj) === '[object Array]';
|
||||
}
|
||||
|
||||
function sn(tagName, className, data, keypath) {
|
||||
var result = document.createElement(tagName);
|
||||
|
||||
result.className = className;
|
||||
|
||||
if (keypath) {
|
||||
result.setAttribute('data-keypath', keypath);
|
||||
}
|
||||
|
||||
result.appendChild(document.createTextNode("" + data));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function scn(tagName, className, child) {
|
||||
var result = document.createElement(tagName),
|
||||
i, len;
|
||||
|
||||
result.className = className;
|
||||
|
||||
if (isArray(child)) {
|
||||
for (i = 0, len = child.length; i < len; i += 1) {
|
||||
result.appendChild(child[i]);
|
||||
}
|
||||
} else {
|
||||
result.appendChild(child);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function linkNode(child, href, target){
|
||||
var a = scn("a", HYPERLINK_CLASS_NAME, child);
|
||||
a.setAttribute('href', href);
|
||||
a.setAttribute('target', target);
|
||||
return a;
|
||||
}
|
||||
|
||||
var toString = Object.prototype.toString,
|
||||
prefixer = makePrefixer("jh"),
|
||||
p = prefixer,
|
||||
ARRAY = 1,
|
||||
BOOL = 2,
|
||||
INT = 3,
|
||||
FLOAT = 4,
|
||||
STRING = 5,
|
||||
OBJECT = 6,
|
||||
FUNCTION = 7,
|
||||
UNK = 99,
|
||||
|
||||
STRING_CLASS_NAME = p("type-string"),
|
||||
STRING_EMPTY_CLASS_NAME = p("type-string") + " " + p("empty"),
|
||||
|
||||
BOOL_TRUE_CLASS_NAME = p("type-bool-true"),
|
||||
BOOL_FALSE_CLASS_NAME = p("type-bool-false"),
|
||||
BOOL_IMAGE = p("type-bool-image"),
|
||||
INT_CLASS_NAME = p("type-int") + " " + p("type-number"),
|
||||
FLOAT_CLASS_NAME = p("type-float") + " " + p("type-number"),
|
||||
|
||||
OBJECT_CLASS_NAME = p("type-object"),
|
||||
OBJ_KEY_CLASS_NAME = p("key") + " " + p("object-key"),
|
||||
OBJ_VAL_CLASS_NAME = p("value") + " " + p("object-value"),
|
||||
OBJ_EMPTY_CLASS_NAME = p("type-object") + " " + p("empty"),
|
||||
|
||||
FUNCTION_CLASS_NAME = p("type-function"),
|
||||
|
||||
ARRAY_KEY_CLASS_NAME = p("key") + " " + p("array-key"),
|
||||
ARRAY_VAL_CLASS_NAME = p("value") + " " + p("array-value"),
|
||||
ARRAY_CLASS_NAME = p("type-array"),
|
||||
ARRAY_EMPTY_CLASS_NAME = p("type-array") + " " + p("empty"),
|
||||
|
||||
HYPERLINK_CLASS_NAME = p('a'),
|
||||
|
||||
UNKNOWN_CLASS_NAME = p("type-unk");
|
||||
|
||||
function getType(obj) {
|
||||
var type = typeof obj;
|
||||
|
||||
switch (type) {
|
||||
case "boolean":
|
||||
return BOOL;
|
||||
case "string":
|
||||
return STRING;
|
||||
case "number":
|
||||
return (obj % 1 === 0) ? INT : FLOAT;
|
||||
case "function":
|
||||
return FUNCTION;
|
||||
default:
|
||||
if (isArray(obj)) {
|
||||
return ARRAY;
|
||||
} else if (obj === Object(obj)) {
|
||||
return OBJECT;
|
||||
} else {
|
||||
return UNK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _format(data, options, parentKey, keypath) {
|
||||
|
||||
var result, container, key, keyNode, valNode, len, childs, tr, value,
|
||||
isEmpty = true,
|
||||
accum = [],
|
||||
type = getType(data);
|
||||
|
||||
// Initialized & used only in case of objects & arrays
|
||||
var hyperlinksEnabled, aTarget, hyperlinkKeys ;
|
||||
|
||||
switch (type) {
|
||||
case BOOL:
|
||||
var boolOpt = options.bool;
|
||||
container = document.createElement('div');
|
||||
|
||||
if(boolOpt.showImage) {
|
||||
var img = document.createElement('img');
|
||||
img.setAttribute('class', BOOL_IMAGE);
|
||||
|
||||
img.setAttribute('src',
|
||||
'' + (data ? boolOpt.img.true : boolOpt.img.false));
|
||||
|
||||
container.appendChild(img);
|
||||
}
|
||||
|
||||
if(boolOpt.showText){
|
||||
container.appendChild(data ?
|
||||
sn("span", BOOL_TRUE_CLASS_NAME, boolOpt.text.true, keypath) :
|
||||
sn("span", BOOL_FALSE_CLASS_NAME, boolOpt.text.false, keypath));
|
||||
}
|
||||
|
||||
result = container;
|
||||
break;
|
||||
|
||||
case STRING:
|
||||
if (data === "") {
|
||||
result = sn("span", STRING_EMPTY_CLASS_NAME, "(Empty Text)", keypath);
|
||||
} else {
|
||||
result = sn("span", STRING_CLASS_NAME, data, keypath);
|
||||
}
|
||||
break;
|
||||
case INT:
|
||||
result = sn("span", INT_CLASS_NAME, data, keypath);
|
||||
break;
|
||||
case FLOAT:
|
||||
result = sn("span", FLOAT_CLASS_NAME, data, keypath);
|
||||
break;
|
||||
case OBJECT:
|
||||
childs = [];
|
||||
|
||||
aTarget = options.hyperlinks.target;
|
||||
hyperlinkKeys = options.hyperlinks.keys;
|
||||
|
||||
// Is Hyperlink Key
|
||||
hyperlinksEnabled =
|
||||
options.hyperlinks.enable &&
|
||||
hyperlinkKeys &&
|
||||
hyperlinkKeys.length > 0;
|
||||
|
||||
for (key in data) {
|
||||
isEmpty = false;
|
||||
|
||||
value = data[key];
|
||||
|
||||
var objectKeypath = keypath ? keypath + "." + key : key;
|
||||
|
||||
valNode = _format(value, options, key, objectKeypath);
|
||||
keyNode = sn("th", OBJ_KEY_CLASS_NAME, key);
|
||||
|
||||
if( hyperlinksEnabled &&
|
||||
typeof(value) === 'string' &&
|
||||
indexOf.call(hyperlinkKeys, key) >= 0){
|
||||
|
||||
valNode = scn("td", OBJ_VAL_CLASS_NAME, linkNode(valNode, value, aTarget));
|
||||
} else {
|
||||
valNode = scn("td", OBJ_VAL_CLASS_NAME, valNode);
|
||||
}
|
||||
|
||||
tr = document.createElement("tr");
|
||||
tr.appendChild(keyNode);
|
||||
tr.appendChild(valNode);
|
||||
|
||||
childs.push(tr);
|
||||
}
|
||||
|
||||
if (isEmpty) {
|
||||
result = sn("span", OBJ_EMPTY_CLASS_NAME, "(Empty Object)", keypath);
|
||||
} else {
|
||||
result = scn("table", OBJECT_CLASS_NAME, scn("tbody", '', childs));
|
||||
}
|
||||
break;
|
||||
case FUNCTION:
|
||||
result = sn("span", FUNCTION_CLASS_NAME, data, keypath);
|
||||
break;
|
||||
case ARRAY:
|
||||
if (data.length > 0) {
|
||||
childs = [];
|
||||
var showArrayIndices = options.showArrayIndex;
|
||||
|
||||
aTarget = options.hyperlinks.target;
|
||||
hyperlinkKeys = options.hyperlinks.keys;
|
||||
|
||||
// Hyperlink of arrays?
|
||||
hyperlinksEnabled = parentKey && options.hyperlinks.enable &&
|
||||
hyperlinkKeys &&
|
||||
hyperlinkKeys.length > 0 &&
|
||||
indexOf.call(hyperlinkKeys, parentKey) >= 0;
|
||||
|
||||
for (key = 0, len = data.length; key < len; key += 1) {
|
||||
|
||||
keyNode = sn("th", ARRAY_KEY_CLASS_NAME, key);
|
||||
value = data[key];
|
||||
|
||||
var arrayKeypath = keypath + "[" + key + "]";
|
||||
|
||||
if(hyperlinksEnabled && typeof(value) === "string") {
|
||||
valNode = _format(value, options, key, arrayKeypath);
|
||||
valNode = scn("td", ARRAY_VAL_CLASS_NAME, linkNode(valNode, value, aTarget));
|
||||
} else {
|
||||
valNode = scn("td", ARRAY_VAL_CLASS_NAME, _format(value, options, key, arrayKeypath));
|
||||
}
|
||||
|
||||
tr = document.createElement("tr");
|
||||
|
||||
if(showArrayIndices) {
|
||||
tr.appendChild(keyNode);
|
||||
}
|
||||
tr.appendChild(valNode);
|
||||
|
||||
childs.push(tr);
|
||||
}
|
||||
|
||||
result = scn("table", ARRAY_CLASS_NAME, scn("tbody", '', childs));
|
||||
} else {
|
||||
result = sn("span", ARRAY_EMPTY_CLASS_NAME, "(Empty List)", keypath);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = sn("span", UNKNOWN_CLASS_NAME, data, keypath);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function format(data, options) {
|
||||
options = validateOptions(options || {});
|
||||
|
||||
var result;
|
||||
|
||||
result = _format(data, options);
|
||||
result.className = result.className + " " + prefixer("root");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function validateOptions(options){
|
||||
options = validateArrayIndexOption(options);
|
||||
options = validateHyperlinkOptions(options);
|
||||
options = validateBoolOptions(options);
|
||||
|
||||
// Add any more option validators here
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
function validateArrayIndexOption(options) {
|
||||
if(options.showArrayIndex === undefined){
|
||||
options.showArrayIndex = true;
|
||||
} else {
|
||||
// Force to boolean just in case
|
||||
options.showArrayIndex = options.showArrayIndex ? true: false;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function validateHyperlinkOptions(options){
|
||||
var hyperlinks = {
|
||||
enable : false,
|
||||
keys : null,
|
||||
target : ''
|
||||
};
|
||||
|
||||
if(options.hyperlinks && options.hyperlinks.enable) {
|
||||
hyperlinks.enable = true;
|
||||
|
||||
hyperlinks.keys = isArray(options.hyperlinks.keys) ? options.hyperlinks.keys : [];
|
||||
|
||||
if(options.hyperlinks.target) {
|
||||
hyperlinks.target = '' + options.hyperlinks.target;
|
||||
} else {
|
||||
hyperlinks.target = '_blank';
|
||||
}
|
||||
}
|
||||
|
||||
options.hyperlinks = hyperlinks;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function validateBoolOptions(options){
|
||||
if(!options.bool){
|
||||
options.bool = {
|
||||
text: {
|
||||
true : "true",
|
||||
false : "false"
|
||||
},
|
||||
img : {
|
||||
true: "",
|
||||
false: ""
|
||||
},
|
||||
showImage : false,
|
||||
showText : true
|
||||
};
|
||||
} else {
|
||||
var boolOptions = options.bool;
|
||||
|
||||
// Show text if no option
|
||||
if(!boolOptions.showText && !boolOptions.showImage){
|
||||
boolOptions.showImage = false;
|
||||
boolOptions.showText = true;
|
||||
}
|
||||
|
||||
if(boolOptions.showText){
|
||||
if(!boolOptions.text){
|
||||
boolOptions.text = {
|
||||
true : "true",
|
||||
false : "false"
|
||||
};
|
||||
} else {
|
||||
var t = boolOptions.text.true, f = boolOptions.text.false;
|
||||
|
||||
if(getType(t) != STRING || t === ''){
|
||||
boolOptions.text.true = 'true';
|
||||
}
|
||||
|
||||
if(getType(f) != STRING || f === ''){
|
||||
boolOptions.text.false = 'false';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(boolOptions.showImage){
|
||||
if(!boolOptions.img.true && !boolOptions.img.false){
|
||||
boolOptions.showImage = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
return {
|
||||
format: format
|
||||
};
|
||||
}));
|
|
@ -1,42 +1,99 @@
|
|||
function qs(key) {
|
||||
key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars
|
||||
var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)"));
|
||||
return match && decodeURIComponent(match[1].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
var currentHighchart;
|
||||
|
||||
// setup a function to grab the nodeStats
|
||||
function getNodeStats() {
|
||||
|
||||
|
||||
var uuid = qs("uuid");
|
||||
|
||||
var statsTableBody = "";
|
||||
|
||||
|
||||
$.getJSON("/nodes/" + uuid + ".json", function(json){
|
||||
|
||||
|
||||
// update the table header with the right node type
|
||||
$('#stats-lead h3').html(json.node_type + " stats (" + uuid + ")");
|
||||
|
||||
|
||||
delete json.node_type;
|
||||
|
||||
$.each(json, function(key, value) {
|
||||
statsTableBody += "<tr>";
|
||||
statsTableBody += "<td class='stats-key'>" + key + "</td>";
|
||||
var formattedValue = (typeof value == 'number' ? value.toLocaleString() : value);
|
||||
statsTableBody += "<td>" + formattedValue + "</td>";
|
||||
statsTableBody += "</tr>";
|
||||
});
|
||||
|
||||
$('#stats-table tbody').html(statsTableBody);
|
||||
|
||||
var stats = JsonHuman.format(json);
|
||||
|
||||
$('#stats-container').html(stats);
|
||||
if (currentHighchart) {
|
||||
// get the current time to set with the point
|
||||
var x = (new Date()).getTime();
|
||||
|
||||
// get the last value using underscore-keypath
|
||||
var y = _(json).valueForKeyPath(graphKeypath);
|
||||
|
||||
// start shifting the chart once we hit 20 data points
|
||||
var shift = currentHighchart.series[0].data.length > 20;
|
||||
currentHighchart.series[0].addPoint([x, y], true, shift);
|
||||
}
|
||||
}).fail(function(data) {
|
||||
$('#stats-table td').each(function(){
|
||||
$('#stats-container th').each(function(){
|
||||
$(this).addClass('stale');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// do the first GET on page load
|
||||
getNodeStats();
|
||||
// grab the new assignments JSON every second
|
||||
var getNodeStatsInterval = setInterval(getNodeStats, 1000);
|
||||
|
||||
var graphKeypath = "";
|
||||
|
||||
// set the global Highcharts option
|
||||
Highcharts.setOptions({
|
||||
global: {
|
||||
useUTC: false
|
||||
}
|
||||
});
|
||||
|
||||
// add a function to help create the graph modal
|
||||
function createGraphModal() {
|
||||
var chartModal = bootbox.dialog({
|
||||
title: graphKeypath,
|
||||
message: "<div id='highchart-container'></div>",
|
||||
buttons: {},
|
||||
className: 'highchart-modal'
|
||||
});
|
||||
|
||||
chartModal.on('hidden.bs.modal', function(e) {
|
||||
currentHighchart.destroy();
|
||||
currentHighchart = null;
|
||||
});
|
||||
|
||||
currentHighchart = new Highcharts.Chart({
|
||||
chart: {
|
||||
renderTo: 'highchart-container',
|
||||
defaultSeriesType: 'line'
|
||||
},
|
||||
title: {
|
||||
text: ''
|
||||
},
|
||||
xAxis: {
|
||||
type: 'datetime',
|
||||
tickPixelInterval: 150
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: 'Value'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
enabled: false
|
||||
},
|
||||
series: [{
|
||||
name: graphKeypath,
|
||||
data: []
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
// handle clicks on numerical values - this lets the user show a line graph in a modal
|
||||
$('#stats-container').on('click', '.jh-type-number', function(){
|
||||
graphKeypath = $(this).data('keypath');
|
||||
|
||||
// setup the new graph modal
|
||||
createGraphModal();
|
||||
});
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -39,121 +39,128 @@ class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
|||
Q_OBJECT
|
||||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
|
||||
|
||||
static int const EXIT_CODE_REBOOT;
|
||||
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
|
||||
|
||||
public slots:
|
||||
/// Called by NodeList to inform us a node has been added
|
||||
void nodeAdded(SharedNodePointer node);
|
||||
/// Called by NodeList to inform us a node has been killed
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
|
||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||
void transactionJSONCallback(const QJsonObject& data);
|
||||
|
||||
|
||||
void restart();
|
||||
|
||||
|
||||
private slots:
|
||||
void aboutToQuit();
|
||||
|
||||
void loginFailed();
|
||||
void readAvailableDatagrams();
|
||||
void setupPendingAssignmentCredits();
|
||||
void sendPendingTransactionsToServer();
|
||||
|
||||
void requestCurrentPublicSocketViaSTUN();
|
||||
|
||||
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
|
||||
void performICEUpdates();
|
||||
void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); }
|
||||
void sendHeartbeatToIceServer();
|
||||
void sendICEPingPackets();
|
||||
void handlePeerPingTimeout();
|
||||
private:
|
||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
bool optionallySetupOAuth();
|
||||
bool optionallyReadX509KeyAndCertificate();
|
||||
bool didSetupAccountManagerWithAccessToken();
|
||||
bool optionallySetupAssignmentPayment();
|
||||
|
||||
|
||||
bool didSetupAccountManagerWithAccessToken();
|
||||
bool resetAccountManagerAccessToken();
|
||||
|
||||
void setupAutomaticNetworking();
|
||||
void sendHeartbeatToDataServer(const QString& networkAddress);
|
||||
void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
void processICEHeartbeatResponse(const QByteArray& packet);
|
||||
|
||||
void processICEPeerInformation(const QByteArray& packet);
|
||||
|
||||
void pingPunchForConnectingPeer(const SharedNetworkPeer& peer);
|
||||
|
||||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
|
||||
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
unsigned int countConnectedUsers();
|
||||
bool verifyUsersKey (const QString& username, const QByteArray& usernameSignature, QString& reasonReturn);
|
||||
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr, QString& reasonReturn);
|
||||
|
||||
|
||||
void preloadAllowedUserPublicKeys();
|
||||
void requestUserPublicKey(const QString& username);
|
||||
|
||||
|
||||
int parseNodeDataFromByteArray(QDataStream& packetStream,
|
||||
NodeType_t& nodeType,
|
||||
HifiSockAddr& publicSockAddr,
|
||||
HifiSockAddr& localSockAddr,
|
||||
const HifiSockAddr& senderSockAddr);
|
||||
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
|
||||
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
|
||||
const NodeSet& nodeInterestList);
|
||||
|
||||
const NodeSet& nodeInterestSet);
|
||||
|
||||
QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB);
|
||||
void broadcastNewNode(const SharedNodePointer& node);
|
||||
|
||||
void parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes);
|
||||
void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
|
||||
void createStaticAssignmentsForType(Assignment::Type type, const QVariantList& configList);
|
||||
void populateDefaultStaticAssignmentsExcludingTypes(const QSet<Assignment::Type>& excludedTypes);
|
||||
void populateStaticScriptedAssignmentsFromSettings();
|
||||
|
||||
|
||||
SharedAssignmentPointer matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType);
|
||||
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
|
||||
void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment);
|
||||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
|
||||
void respondToPathQuery(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
|
||||
bool isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
void handleTokenRequestFinished();
|
||||
QNetworkReply* profileRequestGivenTokenReply(QNetworkReply* tokenReply);
|
||||
void handleProfileRequestFinished();
|
||||
Headers setupCookieHeadersFromProfileReply(QNetworkReply* profileReply);
|
||||
|
||||
|
||||
void loadExistingSessionsFromSettings();
|
||||
|
||||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
|
||||
HTTPManager _httpManager;
|
||||
HTTPSManager* _httpsManager;
|
||||
|
||||
|
||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
||||
QHash<QUuid, PendingAssignedNodeData*> _pendingAssignedNodes;
|
||||
TransactionHash _pendingAssignmentCredits;
|
||||
|
||||
|
||||
bool _isUsingDTLS;
|
||||
|
||||
|
||||
QUrl _oauthProviderURL;
|
||||
QString _oauthClientID;
|
||||
QString _oauthClientSecret;
|
||||
QString _hostname;
|
||||
|
||||
|
||||
QSet<QUuid> _webAuthenticationStateSet;
|
||||
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
|
||||
|
||||
|
||||
QHash<QString, QByteArray> _userPublicKeys;
|
||||
|
||||
QHash<QUuid, NetworkPeer> _connectingICEPeers;
|
||||
QHash<QUuid, HifiSockAddr> _connectedICEPeers;
|
||||
|
||||
|
||||
QHash<QUuid, SharedNetworkPeer> _icePeers;
|
||||
|
||||
QString _automaticNetworkingSetting;
|
||||
|
||||
|
||||
DomainServerSettingsManager _settingsManager;
|
||||
|
||||
|
||||
HifiSockAddr _iceServerSocket;
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
#include <JSONBreakableMarshal.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "DomainServerNodeData.h"
|
||||
|
@ -31,16 +32,8 @@ DomainServerNodeData::DomainServerNodeData() :
|
|||
}
|
||||
|
||||
void DomainServerNodeData::parseJSONStatsPacket(const QByteArray& statsPacket) {
|
||||
// push past the packet header
|
||||
QDataStream packetStream(statsPacket);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(statsPacket));
|
||||
|
||||
QVariantMap unpackedVariantMap;
|
||||
|
||||
packetStream >> unpackedVariantMap;
|
||||
|
||||
QJsonObject unpackedStatsJSON = QJsonObject::fromVariantMap(unpackedVariantMap);
|
||||
_statsJSONObject = mergeJSONStatsFromNewObject(unpackedStatsJSON, _statsJSONObject);
|
||||
QVariantMap packetVariantMap = JSONBreakableMarshal::fromStringBuffer(statsPacket.mid(numBytesForPacketHeader(statsPacket)));
|
||||
_statsJSONObject = mergeJSONStatsFromNewObject(QJsonObject::fromVariantMap(packetVariantMap), _statsJSONObject);
|
||||
}
|
||||
|
||||
QJsonObject DomainServerNodeData::mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject) {
|
||||
|
|
|
@ -12,44 +12,47 @@
|
|||
#ifndef hifi_DomainServerNodeData_h
|
||||
#define hifi_DomainServerNodeData_h
|
||||
|
||||
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <HifiSockAddr.h>
|
||||
#include <NodeData.h>
|
||||
#include <NodeType.h>
|
||||
|
||||
class DomainServerNodeData : public NodeData {
|
||||
public:
|
||||
DomainServerNodeData();
|
||||
int parseData(const QByteArray& packet) { return 0; }
|
||||
|
||||
|
||||
const QJsonObject& getStatsJSONObject() const { return _statsJSONObject; }
|
||||
|
||||
|
||||
void parseJSONStatsPacket(const QByteArray& statsPacket);
|
||||
|
||||
|
||||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||
|
||||
|
||||
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
|
||||
const QUuid& getWalletUUID() const { return _walletUUID; }
|
||||
|
||||
|
||||
void setUsername(const QString& username) { _username = username; }
|
||||
const QString& getUsername() const { return _username; }
|
||||
|
||||
|
||||
QElapsedTimer& getPaymentIntervalTimer() { return _paymentIntervalTimer; }
|
||||
|
||||
|
||||
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
|
||||
const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; }
|
||||
|
||||
|
||||
void setIsAuthenticated(bool isAuthenticated) { _isAuthenticated = isAuthenticated; }
|
||||
bool isAuthenticated() const { return _isAuthenticated; }
|
||||
|
||||
|
||||
QHash<QUuid, QUuid>& getSessionSecretHash() { return _sessionSecretHash; }
|
||||
|
||||
const NodeSet& getNodeInterestSet() const { return _nodeInterestSet; }
|
||||
void setNodeInterestSet(const NodeSet& nodeInterestSet) { _nodeInterestSet = nodeInterestSet; }
|
||||
private:
|
||||
QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject);
|
||||
|
||||
|
||||
QHash<QUuid, QUuid> _sessionSecretHash;
|
||||
QUuid _assignmentUUID;
|
||||
QUuid _walletUUID;
|
||||
|
@ -58,6 +61,7 @@ private:
|
|||
QJsonObject _statsJSONObject;
|
||||
HifiSockAddr _sendingSockAddr;
|
||||
bool _isAuthenticated;
|
||||
NodeSet _nodeInterestSet;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerNodeData_h
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QSettings>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QUrlQuery>
|
||||
|
@ -30,6 +31,9 @@ const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
|||
const QString SETTING_DEFAULT_KEY = "default";
|
||||
const QString DESCRIPTION_NAME_KEY = "name";
|
||||
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
|
||||
const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
||||
|
||||
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
||||
|
||||
DomainServerSettingsManager::DomainServerSettingsManager() :
|
||||
_descriptionArray(),
|
||||
|
@ -38,50 +42,93 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
|
|||
// load the description object from the settings description
|
||||
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
||||
descriptionFile.open(QIODevice::ReadOnly);
|
||||
|
||||
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
|
||||
|
||||
QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll());
|
||||
|
||||
if (descriptionDocument.isObject()) {
|
||||
QJsonObject descriptionObject = descriptionDocument.object();
|
||||
|
||||
const QString DESCRIPTION_VERSION_KEY = "version";
|
||||
|
||||
if (descriptionObject.contains(DESCRIPTION_VERSION_KEY)) {
|
||||
// read the version from the settings description
|
||||
_descriptionVersion = descriptionObject[DESCRIPTION_VERSION_KEY].toDouble();
|
||||
|
||||
if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
|
||||
_descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qCritical() << "Did not find settings decription in JSON at" << SETTINGS_DESCRIPTION_RELATIVE_PATH
|
||||
<< "- Unable to continue. domain-server will quit.";
|
||||
QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
||||
_configMap.loadMasterAndUserConfig(argumentList);
|
||||
|
||||
// for now we perform a temporary transition from http-username and http-password to http_username and http_password
|
||||
const QVariant* oldUsername = valueForKeyPath(_configMap.getUserConfig(), "security.http-username");
|
||||
const QVariant* oldPassword = valueForKeyPath(_configMap.getUserConfig(), "security.http-password");
|
||||
|
||||
if (oldUsername || oldPassword) {
|
||||
QVariantMap& settingsMap = *reinterpret_cast<QVariantMap*>(_configMap.getUserConfig()["security"].data());
|
||||
|
||||
// remove old keys, move to new format
|
||||
if (oldUsername) {
|
||||
settingsMap["http_username"] = oldUsername->toString();
|
||||
settingsMap.remove("http-username");
|
||||
|
||||
// What settings version were we before and what are we using now?
|
||||
// Do we need to do any re-mapping?
|
||||
QSettings appSettings;
|
||||
const QString JSON_SETTINGS_VERSION_KEY = "json-settings/version";
|
||||
double oldVersion = appSettings.value(JSON_SETTINGS_VERSION_KEY, 0.0).toDouble();
|
||||
|
||||
if (oldVersion != _descriptionVersion) {
|
||||
qDebug() << "Previous domain-server settings version was"
|
||||
<< QString::number(oldVersion, 'g', 8) << "and the new version is"
|
||||
<< QString::number(_descriptionVersion, 'g', 8) << "- checking if any re-mapping is required";
|
||||
|
||||
// we have a version mismatch - for now handle custom behaviour here since there are not many remappings
|
||||
if (oldVersion < 1.0) {
|
||||
// This was prior to the introduction of security.restricted_access
|
||||
// If the user has a list of allowed users then set their value for security.restricted_access to true
|
||||
|
||||
QVariant* allowedUsers = valueForKeyPath(_configMap.getMergedConfig(), ALLOWED_USERS_SETTINGS_KEYPATH);
|
||||
|
||||
if (allowedUsers
|
||||
&& allowedUsers->canConvert(QMetaType::QVariantList)
|
||||
&& reinterpret_cast<QVariantList*>(allowedUsers)->size() > 0) {
|
||||
|
||||
qDebug() << "Forcing security.restricted_access to TRUE since there was an"
|
||||
<< "existing list of allowed users.";
|
||||
|
||||
// In the pre-toggle system the user had a list of allowed users, so
|
||||
// we need to set security.restricted_access to true
|
||||
QVariant* restrictedAccess = valueForKeyPath(_configMap.getUserConfig(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (oldPassword) {
|
||||
settingsMap["http_password"] = oldPassword->toString();
|
||||
settingsMap.remove("http-password");
|
||||
}
|
||||
|
||||
// save the updated settings
|
||||
persistToFile();
|
||||
}
|
||||
|
||||
// write the current description version to our settings
|
||||
appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion);
|
||||
}
|
||||
|
||||
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) {
|
||||
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
|
||||
|
||||
|
||||
if (foundValue) {
|
||||
return *foundValue;
|
||||
} else {
|
||||
int dotIndex = keyPath.indexOf('.');
|
||||
|
||||
|
||||
QString groupKey = keyPath.mid(0, dotIndex);
|
||||
QString settingKey = keyPath.mid(dotIndex + 1);
|
||||
|
||||
|
||||
foreach(const QVariant& group, _descriptionArray.toVariantList()) {
|
||||
QVariantMap groupMap = group.toMap();
|
||||
|
||||
|
||||
if (groupMap[DESCRIPTION_NAME_KEY].toString() == groupKey) {
|
||||
foreach(const QVariant& setting, groupMap[DESCRIPTION_SETTINGS_KEY].toList()) {
|
||||
QVariantMap settingMap = setting.toMap();
|
||||
|
@ -89,187 +136,190 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin
|
|||
return settingMap[SETTING_DEFAULT_KEY];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const QString SETTINGS_PATH = "/settings.json";
|
||||
|
||||
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||
// this is a GET operation for our settings
|
||||
|
||||
|
||||
// check if there is a query parameter for settings affecting a particular type of assignment
|
||||
const QString SETTINGS_TYPE_QUERY_KEY = "type";
|
||||
QUrlQuery settingsQuery(url);
|
||||
QString typeValue = settingsQuery.queryItemValue(SETTINGS_TYPE_QUERY_KEY);
|
||||
|
||||
|
||||
if (!typeValue.isEmpty()) {
|
||||
QJsonObject responseObject = responseObjectForType(typeValue);
|
||||
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
|
||||
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||
// this is a POST operation to change one or more settings
|
||||
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
||||
QJsonObject postedObject = postedDocument.object();
|
||||
|
||||
|
||||
qDebug() << "DomainServerSettingsManager postedObject -" << postedObject;
|
||||
|
||||
// we recurse one level deep below each group for the appropriate setting
|
||||
recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig(), _descriptionArray);
|
||||
|
||||
recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig());
|
||||
|
||||
// store whatever the current _settingsMap is to file
|
||||
persistToFile();
|
||||
|
||||
|
||||
// return success to the caller
|
||||
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||
|
||||
|
||||
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
||||
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||
|
||||
|
||||
return true;
|
||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH) {
|
||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||
// setup a JSON Object with descriptions and non-omitted settings
|
||||
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
|
||||
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
|
||||
const QString SETTINGS_RESPONSE_LOCKED_VALUES_KEY = "locked";
|
||||
|
||||
|
||||
QJsonObject rootObject;
|
||||
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
|
||||
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
|
||||
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
|
||||
|
||||
|
||||
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) {
|
||||
QJsonObject responseObject;
|
||||
|
||||
|
||||
if (!typeValue.isEmpty() || isAuthenticated) {
|
||||
// convert the string type value to a QJsonValue
|
||||
QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt());
|
||||
|
||||
|
||||
const QString AFFECTED_TYPES_JSON_KEY = "assignment-types";
|
||||
|
||||
|
||||
// enumerate the groups in the description object to find which settings to pass
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString();
|
||||
QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray();
|
||||
|
||||
|
||||
QJsonObject groupResponseObject;
|
||||
|
||||
|
||||
foreach(const QJsonValue& settingValue, groupSettingsArray) {
|
||||
const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden";
|
||||
|
||||
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
|
||||
|
||||
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) {
|
||||
QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
||||
if (affectedTypesArray.isEmpty()) {
|
||||
affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
||||
}
|
||||
|
||||
|
||||
if (affectedTypesArray.contains(queryType) ||
|
||||
(queryType.isNull() && isAuthenticated)) {
|
||||
// this is a setting we should include in the responseObject
|
||||
|
||||
|
||||
QString settingName = settingObject[DESCRIPTION_NAME_KEY].toString();
|
||||
|
||||
|
||||
// we need to check if the settings map has a value for this setting
|
||||
QVariant variantValue;
|
||||
QVariant settingsMapGroupValue = _configMap.getMergedConfig()
|
||||
.value(groupObject[DESCRIPTION_NAME_KEY].toString());
|
||||
|
||||
if (!settingsMapGroupValue.isNull()) {
|
||||
variantValue = settingsMapGroupValue.toMap().value(settingName);
|
||||
|
||||
if (!groupKey.isEmpty()) {
|
||||
QVariant settingsMapGroupValue = _configMap.getMergedConfig().value(groupKey);
|
||||
|
||||
if (!settingsMapGroupValue.isNull()) {
|
||||
variantValue = settingsMapGroupValue.toMap().value(settingName);
|
||||
}
|
||||
} else {
|
||||
variantValue = _configMap.getMergedConfig().value(settingName);
|
||||
}
|
||||
|
||||
|
||||
QJsonValue result;
|
||||
|
||||
if (variantValue.isNull()) {
|
||||
// no value for this setting, pass the default
|
||||
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
|
||||
groupResponseObject[settingName] = settingObject[SETTING_DEFAULT_KEY];
|
||||
result = settingObject[SETTING_DEFAULT_KEY];
|
||||
} else {
|
||||
// users are allowed not to provide a default for string values
|
||||
// if so we set to the empty string
|
||||
groupResponseObject[settingName] = QString("");
|
||||
result = QString("");
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
groupResponseObject[settingName] = QJsonValue::fromVariant(variantValue);
|
||||
result = QJsonValue::fromVariant(variantValue);
|
||||
}
|
||||
|
||||
if (!groupKey.isEmpty()) {
|
||||
// this belongs in the group object
|
||||
groupResponseObject[settingName] = result;
|
||||
} else {
|
||||
// this is a value that should be at the root
|
||||
responseObject[settingName] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!groupResponseObject.isEmpty()) {
|
||||
|
||||
if (!groupKey.isEmpty() && !groupResponseObject.isEmpty()) {
|
||||
// set this group's object to the constructed object
|
||||
responseObject[groupKey] = groupResponseObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return responseObject;
|
||||
}
|
||||
|
||||
bool DomainServerSettingsManager::settingExists(const QString& groupName, const QString& settingName,
|
||||
const QJsonArray& descriptionArray, QJsonValue& settingDescription) {
|
||||
foreach(const QJsonValue& groupValue, descriptionArray) {
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
if (groupObject[DESCRIPTION_NAME_KEY].toString() == groupName) {
|
||||
|
||||
foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) {
|
||||
settingDescription = settingObject[SETTING_DEFAULT_KEY];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
settingDescription = QJsonValue::Undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
||||
const QJsonValue& settingDescription) {
|
||||
const QJsonObject& settingDescription) {
|
||||
if (newValue.isString()) {
|
||||
if (newValue.toString().isEmpty()) {
|
||||
// this is an empty value, clear it in settings variant so the default is sent
|
||||
settingMap.remove(key);
|
||||
} else {
|
||||
// make sure the resulting json value has the right type
|
||||
const QString settingType = settingDescription.toObject()[SETTING_DESCRIPTION_TYPE_KEY].toString();
|
||||
QString settingType = settingDescription[SETTING_DESCRIPTION_TYPE_KEY].toString();
|
||||
const QString INPUT_DOUBLE_TYPE = "double";
|
||||
const QString INPUT_INTEGER_TYPE = "int";
|
||||
|
||||
|
||||
if (settingType == INPUT_DOUBLE_TYPE) {
|
||||
settingMap[key] = newValue.toString().toDouble();
|
||||
} else if (settingType == INPUT_INTEGER_TYPE) {
|
||||
settingMap[key] = newValue.toString().toInt();
|
||||
} else {
|
||||
settingMap[key] = newValue.toString();
|
||||
QString sanitizedValue = newValue.toString();
|
||||
|
||||
// we perform special handling for viewpoints here
|
||||
// we do not want them to be prepended with a slash
|
||||
if (key == SETTINGS_VIEWPOINT_KEY && !sanitizedValue.startsWith('/')) {
|
||||
sanitizedValue.prepend('/');
|
||||
}
|
||||
|
||||
settingMap[key] = sanitizedValue;
|
||||
}
|
||||
}
|
||||
} else if (newValue.isBool()) {
|
||||
|
@ -279,66 +329,158 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
|||
// we don't have a map below this key yet, so set it up now
|
||||
settingMap[key] = QVariantMap();
|
||||
}
|
||||
|
||||
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingMap[key].data());
|
||||
foreach(const QString childKey, newValue.toObject().keys()) {
|
||||
updateSetting(childKey, newValue.toObject()[childKey], thisMap, settingDescription.toObject()[key]);
|
||||
|
||||
QVariant& possibleMap = settingMap[key];
|
||||
|
||||
if (!possibleMap.canConvert(QMetaType::QVariantMap)) {
|
||||
// if this isn't a map then we need to make it one, otherwise we're about to crash
|
||||
qDebug() << "Value at" << key << "was not the expected QVariantMap while updating DS settings"
|
||||
<< "- removing existing value and making it a QVariantMap";
|
||||
possibleMap = QVariantMap();
|
||||
}
|
||||
|
||||
|
||||
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(possibleMap.data());
|
||||
foreach(const QString childKey, newValue.toObject().keys()) {
|
||||
|
||||
QJsonObject childDescriptionObject = settingDescription;
|
||||
|
||||
// is this the key? if so we have the description already
|
||||
if (key != settingDescription[DESCRIPTION_NAME_KEY].toString()) {
|
||||
// otherwise find the description object for this childKey under columns
|
||||
foreach(const QJsonValue& column, settingDescription[DESCRIPTION_COLUMNS_KEY].toArray()) {
|
||||
if (column.isObject()) {
|
||||
QJsonObject thisDescription = column.toObject();
|
||||
if (thisDescription[DESCRIPTION_NAME_KEY] == childKey) {
|
||||
childDescriptionObject = column.toObject();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString sanitizedKey = childKey;
|
||||
|
||||
if (key == SETTINGS_PATHS_KEY && !sanitizedKey.startsWith('/')) {
|
||||
// We perform special handling for paths here.
|
||||
// If we got sent a path without a leading slash then we add it.
|
||||
sanitizedKey.prepend("/");
|
||||
}
|
||||
|
||||
updateSetting(sanitizedKey, newValue.toObject()[childKey], thisMap, childDescriptionObject);
|
||||
}
|
||||
|
||||
if (settingMap[key].toMap().isEmpty()) {
|
||||
// we've cleared all of the settings below this value, so remove this one too
|
||||
settingMap.remove(key);
|
||||
}
|
||||
} else if (newValue.isArray()) {
|
||||
// we just assume array is replacement
|
||||
// TODO: we still need to recurse here with the description in case values in the array have special types
|
||||
settingMap[key] = newValue.toArray().toVariantList();
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||
QVariantMap& settingsVariant,
|
||||
const QJsonArray& descriptionArray) {
|
||||
// Iterate on the setting groups
|
||||
foreach(const QString& groupKey, postedObject.keys()) {
|
||||
QJsonValue groupValue = postedObject[groupKey];
|
||||
|
||||
if (!settingsVariant.contains(groupKey)) {
|
||||
// we don't have a map below this key yet, so set it up now
|
||||
settingsVariant[groupKey] = QVariantMap();
|
||||
QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) {
|
||||
foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) {
|
||||
return settingObject;
|
||||
}
|
||||
|
||||
// Iterate on the settings
|
||||
foreach(const QString& settingKey, groupValue.toObject().keys()) {
|
||||
QJsonValue settingValue = groupValue.toObject()[settingKey];
|
||||
|
||||
QJsonValue thisDescription;
|
||||
if (settingExists(groupKey, settingKey, descriptionArray, thisDescription)) {
|
||||
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingsVariant[groupKey].data());
|
||||
updateSetting(settingKey, settingValue, thisMap, thisDescription);
|
||||
}
|
||||
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||
QVariantMap& settingsVariant) {
|
||||
// Iterate on the setting groups
|
||||
foreach(const QString& rootKey, postedObject.keys()) {
|
||||
QJsonValue rootValue = postedObject[rootKey];
|
||||
|
||||
if (!settingsVariant.contains(rootKey)) {
|
||||
// we don't have a map below this key yet, so set it up now
|
||||
settingsVariant[rootKey] = QVariantMap();
|
||||
}
|
||||
|
||||
QVariantMap* thisMap = &settingsVariant;
|
||||
|
||||
QJsonObject groupDescriptionObject;
|
||||
|
||||
// we need to check the description array to see if this is a root setting or a group setting
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) {
|
||||
// we matched a group - keep this since we'll use it below to update the settings
|
||||
groupDescriptionObject = groupValue.toObject();
|
||||
|
||||
// change the map we will update to be the map for this group
|
||||
thisMap = reinterpret_cast<QVariantMap*>(settingsVariant[rootKey].data());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsVariant[groupKey].toMap().empty()) {
|
||||
|
||||
if (groupDescriptionObject.isEmpty()) {
|
||||
// this is a root value, so we can call updateSetting for it directly
|
||||
// first we need to find our description value for it
|
||||
|
||||
QJsonObject matchingDescriptionObject;
|
||||
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
// find groups with root values (they don't have a group name)
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
if (!groupObject.contains(DESCRIPTION_NAME_KEY)) {
|
||||
// this is a group with root values - check if our setting is in here
|
||||
matchingDescriptionObject = settingDescriptionFromGroup(groupObject, rootKey);
|
||||
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
|
||||
} else {
|
||||
qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting.";
|
||||
}
|
||||
} else {
|
||||
// this is a group - iterate on the settings in the group
|
||||
foreach(const QString& settingKey, rootValue.toObject().keys()) {
|
||||
// make sure this particular setting exists and we have a description object for it
|
||||
QJsonObject matchingDescriptionObject = settingDescriptionFromGroup(groupDescriptionObject, settingKey);
|
||||
|
||||
// if we matched the setting then update the value
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
QJsonValue settingValue = rootValue.toObject()[settingKey];
|
||||
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
|
||||
} else {
|
||||
qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey <<
|
||||
"- cannot update setting.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsVariant[rootKey].toMap().empty()) {
|
||||
// we've cleared all of the settings below this value, so remove this one too
|
||||
settingsVariant.remove(groupKey);
|
||||
settingsVariant.remove(rootKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::persistToFile() {
|
||||
|
||||
|
||||
// make sure we have the dir the settings file is supposed to live in
|
||||
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
|
||||
|
||||
|
||||
if (!settingsFileInfo.dir().exists()) {
|
||||
settingsFileInfo.dir().mkpath(".");
|
||||
}
|
||||
|
||||
|
||||
QFile settingsFile(_configMap.getUserConfigFilename());
|
||||
|
||||
|
||||
if (settingsFile.open(QIODevice::WriteOnly)) {
|
||||
settingsFile.write(QJsonDocument::fromVariant(_configMap.getUserConfig()).toJson());
|
||||
} else {
|
||||
qCritical("Could not write to JSON settings file. Unable to persist settings.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,29 +18,38 @@
|
|||
#include <HifiConfigVariantMap.h>
|
||||
#include <HTTPManager.h>
|
||||
|
||||
const QString SETTINGS_PATHS_KEY = "paths";
|
||||
|
||||
const QString SETTINGS_PATH = "/settings";
|
||||
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
||||
|
||||
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
|
||||
const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access";
|
||||
|
||||
class DomainServerSettingsManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainServerSettingsManager();
|
||||
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
|
||||
void setupConfigMap(const QStringList& argumentList);
|
||||
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
|
||||
|
||||
|
||||
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); }
|
||||
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
||||
private:
|
||||
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
||||
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
|
||||
const QJsonArray& descriptionArray);
|
||||
bool settingExists(const QString& groupName, const QString& settingName,
|
||||
const QJsonArray& descriptionArray, QJsonValue& settingDescription);
|
||||
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant);
|
||||
|
||||
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
||||
const QJsonValue& settingDescription);
|
||||
const QJsonObject& settingDescription);
|
||||
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
|
||||
void persistToFile();
|
||||
|
||||
|
||||
double _descriptionVersion;
|
||||
QJsonArray _descriptionArray;
|
||||
HifiConfigVariantMap _configMap;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerSettingsManager_h
|
||||
#endif // hifi_DomainServerSettingsManager_h
|
||||
|
|
|
@ -26,18 +26,16 @@ int main(int argc, char* argv[]) {
|
|||
#ifndef WIN32
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
#endif
|
||||
|
||||
qInstallMessageHandler(LogHandler::verboseMessageHandler);
|
||||
|
||||
|
||||
int currentExitCode = 0;
|
||||
|
||||
|
||||
// use a do-while to handle domain-server restart
|
||||
do {
|
||||
DomainServer domainServer(argc, argv);
|
||||
currentExitCode = domainServer.exec();
|
||||
} while (currentExitCode == DomainServer::EXIT_CODE_REBOOT);
|
||||
|
||||
|
||||
|
||||
|
||||
return currentExitCode;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
//For procedural walk animation
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
Script.include("proceduralAnimationAPI.js");
|
||||
Script.include(HIFI_PUBLIC_BUCKET + "scripts/acScripts/proceduralAnimationAPI.js");
|
||||
|
||||
var procAnimAPI = new ProcAnimAPI();
|
||||
|
||||
|
|
416
examples/avatarSelector.js
Normal file
416
examples/avatarSelector.js
Normal file
|
@ -0,0 +1,416 @@
|
|||
//
|
||||
// avatarSelector.js
|
||||
// examples
|
||||
//
|
||||
// Created by David Rowe on 21 Apr 2015.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Based on lobby.js created by Stephen Birarda on 17 Oct 2014.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
var panelWall = false;
|
||||
var orbShell = false;
|
||||
var descriptionText = false;
|
||||
var showText = false;
|
||||
|
||||
// used for formating the description text, in meters
|
||||
var textWidth = 4;
|
||||
var textHeight = .5;
|
||||
var numberOfLines = 2;
|
||||
var textMargin = 0.0625;
|
||||
var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines;
|
||||
|
||||
var avatarStickPosition = {};
|
||||
|
||||
var orbNaturalExtentsMin = { x: -1.230354, y: -1.22077, z: -1.210487 };
|
||||
var orbNaturalExtentsMax = { x: 1.230353, y: 1.229819, z: 1.210487 };
|
||||
var panelsNaturalExtentsMin = { x: -1.223182, y: -0.348487, z: 0.0451369 };
|
||||
var panelsNaturalExtentsMax = { x: 1.223039, y: 0.602978, z: 1.224298 };
|
||||
|
||||
var orbNaturalDimensions = Vec3.subtract(orbNaturalExtentsMax, orbNaturalExtentsMin);
|
||||
var panelsNaturalDimensions = Vec3.subtract(panelsNaturalExtentsMax, panelsNaturalExtentsMin);
|
||||
|
||||
var SCALING_FACTOR = 10;
|
||||
var orbDimensions = Vec3.multiply(orbNaturalDimensions, SCALING_FACTOR);
|
||||
var panelsDimensions = Vec3.multiply(panelsNaturalDimensions, SCALING_FACTOR);
|
||||
|
||||
var orbNaturalCenter = Vec3.sum(orbNaturalExtentsMin, Vec3.multiply(orbNaturalDimensions, 0.5));
|
||||
var panelsNaturalCenter = Vec3.sum(panelsNaturalExtentsMin, Vec3.multiply(panelsNaturalDimensions, 0.5));
|
||||
var orbCenter = Vec3.multiply(orbNaturalCenter, SCALING_FACTOR);
|
||||
var panelsCenter = Vec3.multiply(panelsNaturalCenter, SCALING_FACTOR);
|
||||
var panelsCenterShift = Vec3.subtract(panelsCenter, orbCenter);
|
||||
|
||||
var ORB_SHIFT = { x: 0, y: -1.4, z: -0.8 };
|
||||
|
||||
var LOBBY_PANEL_WALL_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/PanelWallForInterface.fbx";
|
||||
var LOBBY_BLANK_PANEL_TEXTURE_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/Texture.jpg";
|
||||
var LOBBY_SHELL_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/LobbyShellForInterface.fbx";
|
||||
|
||||
var droneSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/drone.stereo.raw")
|
||||
var currentDrone = null;
|
||||
|
||||
var latinSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/latin.stereo.raw")
|
||||
var latinInjector = null;
|
||||
var elevatorSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/elevator.stereo.raw")
|
||||
var elevatorInjector = null;
|
||||
var currentMuzakInjector = null;
|
||||
var currentSound = null;
|
||||
|
||||
function textOverlayPosition() {
|
||||
var TEXT_DISTANCE_OUT = 6;
|
||||
var TEXT_DISTANCE_DOWN = -2;
|
||||
return Vec3.sum(Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.orientation), TEXT_DISTANCE_OUT)),
|
||||
Vec3.multiply(Quat.getUp(Camera.orientation), TEXT_DISTANCE_DOWN));
|
||||
}
|
||||
|
||||
var panelPlaceOrder = [
|
||||
7, 8, 9, 10, 11, 12, 13,
|
||||
0, 1, 2, 3, 4, 5, 6,
|
||||
14, 15, 16, 17, 18, 19, 20
|
||||
];
|
||||
|
||||
// Avatar index is 0-based
|
||||
function avatarIndexToPanelIndex(avatarIndex) {
|
||||
return panelPlaceOrder.indexOf(avatarIndex) + 1;
|
||||
}
|
||||
|
||||
// Panel index is 1-based
|
||||
function panelIndexToPlaceIndex(panelIndex) {
|
||||
return panelPlaceOrder[panelIndex - 1];
|
||||
}
|
||||
|
||||
var MAX_NUM_PANELS = 21;
|
||||
var DRONE_VOLUME = 0.3;
|
||||
|
||||
function drawLobby() {
|
||||
if (!panelWall) {
|
||||
print("Adding overlays for the avatar selector panel wall and orb shell.");
|
||||
|
||||
var cameraEuler = Quat.safeEulerAngles(Camera.orientation);
|
||||
var towardsMe = Quat.angleAxis(cameraEuler.y + 180, { x: 0, y: 1, z: 0 });
|
||||
|
||||
var orbPosition = Vec3.sum(Camera.position, Vec3.multiplyQbyV(towardsMe, ORB_SHIFT));
|
||||
|
||||
var panelWallProps = {
|
||||
url: LOBBY_PANEL_WALL_URL,
|
||||
position: Vec3.sum(orbPosition, Vec3.multiplyQbyV(towardsMe, panelsCenterShift)),
|
||||
rotation: towardsMe,
|
||||
dimensions: panelsDimensions
|
||||
};
|
||||
|
||||
var orbShellProps = {
|
||||
url: LOBBY_SHELL_URL,
|
||||
position: orbPosition,
|
||||
rotation: towardsMe,
|
||||
dimensions: orbDimensions,
|
||||
ignoreRayIntersection: true
|
||||
};
|
||||
|
||||
var windowDimensions = Controller.getViewportDimensions();
|
||||
|
||||
var descriptionTextProps = {
|
||||
position: textOverlayPosition(),
|
||||
dimensions: { x: textWidth, y: textHeight },
|
||||
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
topMargin: textMargin,
|
||||
leftMargin: textMargin,
|
||||
bottomMargin: textMargin,
|
||||
rightMargin: textMargin,
|
||||
text: "",
|
||||
lineHeight: lineHeight,
|
||||
alpha: 0.9,
|
||||
backgroundAlpha: 0.9,
|
||||
ignoreRayIntersection: true,
|
||||
visible: false,
|
||||
isFacingAvatar: true
|
||||
};
|
||||
|
||||
avatarStickPosition = MyAvatar.position;
|
||||
|
||||
panelWall = Overlays.addOverlay("model", panelWallProps);
|
||||
orbShell = Overlays.addOverlay("model", orbShellProps);
|
||||
descriptionText = Overlays.addOverlay("text3d", descriptionTextProps);
|
||||
|
||||
if (droneSound.downloaded) {
|
||||
// start the drone sound
|
||||
if (!currentDrone) {
|
||||
currentDrone = Audio.playSound(droneSound, { stereo: true, loop: true, localOnly: true, volume: DRONE_VOLUME });
|
||||
} else {
|
||||
currentDrone.restart();
|
||||
}
|
||||
}
|
||||
|
||||
// start one of our muzak sounds
|
||||
playRandomMuzak();
|
||||
}
|
||||
}
|
||||
|
||||
var avatars = {};
|
||||
|
||||
function changeLobbyTextures() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", "https://metaverse.highfidelity.com/api/v1/marketplace?category=head+%26+body&limit=21", false);
|
||||
req.send(); // Data returned is randomized.
|
||||
|
||||
avatars = JSON.parse(req.responseText).data.items;
|
||||
|
||||
var NUM_PANELS = avatars.length;
|
||||
|
||||
var textureProp = {
|
||||
textures: {}
|
||||
};
|
||||
|
||||
for (var j = 0; j < NUM_PANELS; j++) {
|
||||
var panelIndex = avatarIndexToPanelIndex(j);
|
||||
textureProp["textures"]["file" + panelIndex] = avatars[j].preview_url;
|
||||
};
|
||||
|
||||
Overlays.editOverlay(panelWall, textureProp);
|
||||
}
|
||||
|
||||
var MUZAK_VOLUME = 0.1;
|
||||
|
||||
function playCurrentSound(secondOffset) {
|
||||
if (currentSound == latinSound) {
|
||||
if (!latinInjector) {
|
||||
latinInjector = Audio.playSound(latinSound, { localOnly: true, secondOffset: secondOffset, volume: MUZAK_VOLUME });
|
||||
} else {
|
||||
latinInjector.restart();
|
||||
}
|
||||
|
||||
currentMuzakInjector = latinInjector;
|
||||
} else if (currentSound == elevatorSound) {
|
||||
if (!elevatorInjector) {
|
||||
elevatorInjector = Audio.playSound(elevatorSound, { localOnly: true, secondOffset: secondOffset, volume: MUZAK_VOLUME });
|
||||
} else {
|
||||
elevatorInjector.restart();
|
||||
}
|
||||
|
||||
currentMuzakInjector = elevatorInjector;
|
||||
}
|
||||
}
|
||||
|
||||
function playNextMuzak() {
|
||||
if (panelWall) {
|
||||
if (currentSound == latinSound) {
|
||||
if (elevatorSound.downloaded) {
|
||||
currentSound = elevatorSound;
|
||||
}
|
||||
} else if (currentSound == elevatorSound) {
|
||||
if (latinSound.downloaded) {
|
||||
currentSound = latinSound;
|
||||
}
|
||||
}
|
||||
|
||||
playCurrentSound(0);
|
||||
}
|
||||
}
|
||||
|
||||
function playRandomMuzak() {
|
||||
currentSound = null;
|
||||
|
||||
if (latinSound.downloaded && elevatorSound.downloaded) {
|
||||
currentSound = Math.random() < 0.5 ? latinSound : elevatorSound;
|
||||
} else if (latinSound.downloaded) {
|
||||
currentSound = latinSound;
|
||||
} else if (elevatorSound.downloaded) {
|
||||
currentSound = elevatorSound;
|
||||
}
|
||||
|
||||
if (currentSound) {
|
||||
// pick a random number of seconds from 0-10 to offset the muzak
|
||||
var secondOffset = Math.random() * 10;
|
||||
|
||||
playCurrentSound(secondOffset);
|
||||
} else {
|
||||
currentMuzakInjector = null;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupLobby() {
|
||||
toggleEnvironmentRendering(true);
|
||||
|
||||
// for each of the 21 placeholder textures, set them back to default so the cached model doesn't have changed textures
|
||||
var panelTexturesReset = {};
|
||||
panelTexturesReset["textures"] = {};
|
||||
|
||||
for (var j = 0; j < MAX_NUM_PANELS; j++) {
|
||||
panelTexturesReset["textures"]["file" + (j + 1)] = LOBBY_BLANK_PANEL_TEXTURE_URL;
|
||||
};
|
||||
|
||||
Overlays.editOverlay(panelWall, panelTexturesReset);
|
||||
|
||||
Overlays.deleteOverlay(panelWall);
|
||||
Overlays.deleteOverlay(orbShell);
|
||||
Overlays.deleteOverlay(descriptionText);
|
||||
|
||||
panelWall = false;
|
||||
orbShell = false;
|
||||
|
||||
if (currentDrone) {
|
||||
currentDrone.stop();
|
||||
currentDrone = null
|
||||
}
|
||||
|
||||
if (currentMuzakInjector) {
|
||||
currentMuzakInjector.stop();
|
||||
currentMuzakInjector = null;
|
||||
}
|
||||
|
||||
avatars = {};
|
||||
|
||||
}
|
||||
|
||||
function actionStartEvent(event) {
|
||||
if (panelWall) {
|
||||
// we've got an action event and our panel wall is up
|
||||
// check if we hit a panel and if we should jump there
|
||||
var result = Overlays.findRayIntersection(event.actionRay);
|
||||
if (result.intersects && result.overlayID == panelWall) {
|
||||
|
||||
var panelName = result.extraInfo;
|
||||
|
||||
var panelStringIndex = panelName.indexOf("Panel");
|
||||
if (panelStringIndex != -1) {
|
||||
var panelIndex = parseInt(panelName.slice(5));
|
||||
var avatarIndex = panelIndexToPlaceIndex(panelIndex);
|
||||
if (avatarIndex < avatars.length) {
|
||||
var actionPlace = avatars[avatarIndex];
|
||||
|
||||
print("Changing avatar to " + actionPlace.name
|
||||
+ " after click on panel " + panelIndex + " with avatar index " + avatarIndex);
|
||||
|
||||
MyAvatar.useFullAvatarURL(actionPlace.content_url);
|
||||
|
||||
maybeCleanupLobby();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var control = false;
|
||||
|
||||
function keyPressEvent(event) {
|
||||
if (event.text === "CONTROL") {
|
||||
control = true;
|
||||
}
|
||||
|
||||
if (control && event.text === "a") {
|
||||
if (!panelWall) {
|
||||
toggleEnvironmentRendering(false);
|
||||
drawLobby();
|
||||
changeLobbyTextures();
|
||||
} else {
|
||||
cleanupLobby();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function keyReleaseEvent(event) {
|
||||
if (event.text === "CONTROL") {
|
||||
control = false;
|
||||
}
|
||||
}
|
||||
|
||||
var CLEANUP_EPSILON_DISTANCE = 0.05;
|
||||
|
||||
function maybeCleanupLobby() {
|
||||
if (panelWall && Vec3.length(Vec3.subtract(avatarStickPosition, MyAvatar.position)) > CLEANUP_EPSILON_DISTANCE) {
|
||||
cleanupLobby();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEnvironmentRendering(shouldRender) {
|
||||
Scene.shouldRenderAvatars = shouldRender;
|
||||
Scene.shouldRenderEntities = shouldRender;
|
||||
}
|
||||
|
||||
function handleLookAt(pickRay) {
|
||||
if (panelWall && descriptionText) {
|
||||
// we've got an action event and our panel wall is up
|
||||
// check if we hit a panel and if we should jump there
|
||||
var result = Overlays.findRayIntersection(pickRay);
|
||||
if (result.intersects && result.overlayID == panelWall) {
|
||||
var panelName = result.extraInfo;
|
||||
var panelStringIndex = panelName.indexOf("Panel");
|
||||
if (panelStringIndex != -1) {
|
||||
var panelIndex = parseInt(panelName.slice(5));
|
||||
var avatarIndex = panelIndexToPlaceIndex(panelIndex);
|
||||
if (avatarIndex < avatars.length) {
|
||||
var actionPlace = avatars[avatarIndex];
|
||||
|
||||
if (actionPlace.description == "") {
|
||||
Overlays.editOverlay(descriptionText, { text: actionPlace.name, visible: showText });
|
||||
} else {
|
||||
// handle line wrapping
|
||||
var allWords = actionPlace.description.split(" ");
|
||||
var currentGoodLine = "";
|
||||
var currentTestLine = "";
|
||||
var formatedDescription = "";
|
||||
var wordsFormated = 0;
|
||||
var currentTestWord = 0;
|
||||
var wordsOnLine = 0;
|
||||
while (wordsFormated < allWords.length) {
|
||||
// first add the "next word" to the line and test it.
|
||||
currentTestLine = currentGoodLine;
|
||||
if (wordsOnLine > 0) {
|
||||
currentTestLine += " " + allWords[currentTestWord];
|
||||
} else {
|
||||
currentTestLine = allWords[currentTestWord];
|
||||
}
|
||||
var lineLength = Overlays.textSize(descriptionText, currentTestLine).width;
|
||||
if (lineLength < textWidth || wordsOnLine == 0) {
|
||||
wordsFormated++;
|
||||
currentTestWord++;
|
||||
wordsOnLine++;
|
||||
currentGoodLine = currentTestLine;
|
||||
} else {
|
||||
formatedDescription += currentGoodLine + "\n";
|
||||
wordsOnLine = 0;
|
||||
currentGoodLine = "";
|
||||
currentTestLine = "";
|
||||
}
|
||||
}
|
||||
formatedDescription += currentGoodLine;
|
||||
Overlays.editOverlay(descriptionText, { text: formatedDescription, visible: showText });
|
||||
}
|
||||
} else {
|
||||
Overlays.editOverlay(descriptionText, { text: "", visible: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
maybeCleanupLobby();
|
||||
if (panelWall) {
|
||||
Overlays.editOverlay(descriptionText, { position: textOverlayPosition() });
|
||||
|
||||
// if the reticle is up then we may need to play the next muzak
|
||||
if (currentMuzakInjector && !currentMuzakInjector.isPlaying) {
|
||||
playNextMuzak();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
if (panelWall) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
handleLookAt(pickRay);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.actionStartEvent.connect(actionStartEvent);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(maybeCleanupLobby);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
201
examples/blockWorld.js
Normal file
201
examples/blockWorld.js
Normal file
|
@ -0,0 +1,201 @@
|
|||
// blockWorld.js
|
||||
// examples
|
||||
//
|
||||
// Created by Eric Levin on May 26, 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Creates a floor of tiles and then drops planky blocks at random points above the tile floor
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var TILE_SIZE = 7
|
||||
var GENERATE_INTERVAL = 50;
|
||||
var NUM_ROWS = 10;
|
||||
var angVelRange = 4;
|
||||
|
||||
var floorTiles = [];
|
||||
var blocks = [];
|
||||
var blockSpawner;
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
|
||||
var floorPos = Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: -2,
|
||||
z: 0
|
||||
});
|
||||
var x = floorPos.x;
|
||||
|
||||
var currentRowIndex = 0;
|
||||
var currentColumnIndex = 0;
|
||||
|
||||
var DROP_HEIGHT = floorPos.y + 5;
|
||||
var BLOCK_GRAVITY = {
|
||||
x: 0,
|
||||
y: -9,
|
||||
z: 0
|
||||
};
|
||||
var BLOCK_SIZE = {
|
||||
x: 0.2,
|
||||
y: 0.1,
|
||||
z: 0.8
|
||||
};
|
||||
|
||||
var bounds = {
|
||||
xMin: floorPos.x,
|
||||
xMax: floorPos.x + (TILE_SIZE * NUM_ROWS) - TILE_SIZE,
|
||||
zMin: floorPos.z,
|
||||
zMax: floorPos.z + (TILE_SIZE * NUM_ROWS) - TILE_SIZE
|
||||
};
|
||||
|
||||
var screenSize = Controller.getViewportDimensions();
|
||||
|
||||
var BUTTON_SIZE = 32;
|
||||
var PADDING = 3;
|
||||
|
||||
var offButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var deleteButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 - BUTTON_SIZE,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/delete.png",
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
|
||||
function generateFloor() {
|
||||
for (var z = floorPos.z; currentColumnIndex < NUM_ROWS; z += TILE_SIZE, currentColumnIndex++) {
|
||||
floorTiles.push(Entities.addEntity({
|
||||
type: 'Box',
|
||||
position: {
|
||||
x: x,
|
||||
y: floorPos.y,
|
||||
z: z
|
||||
},
|
||||
dimensions: {
|
||||
x: TILE_SIZE,
|
||||
y: 2,
|
||||
z: TILE_SIZE
|
||||
},
|
||||
color: {
|
||||
red: randFloat(70, 120),
|
||||
green: randFloat(70, 71),
|
||||
blue: randFloat(70, 80)
|
||||
},
|
||||
// collisionsWillMove: true
|
||||
}));
|
||||
}
|
||||
|
||||
currentRowIndex++;
|
||||
if (currentRowIndex < NUM_ROWS) {
|
||||
currentColumnIndex = 0;
|
||||
x += TILE_SIZE;
|
||||
Script.setTimeout(generateFloor, GENERATE_INTERVAL);
|
||||
} else {
|
||||
//Once we're done generating floor, drop planky blocks at random points on floor
|
||||
blockSpawner = Script.setInterval(function() {
|
||||
dropBlock();
|
||||
}, GENERATE_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
function dropBlock() {
|
||||
var dropPos = floorPos;
|
||||
dropPos.y = DROP_HEIGHT;
|
||||
dropPos.x = randFloat(bounds.xMin, bounds.xMax);
|
||||
dropPos.z = randFloat(bounds.zMin, bounds.zMax);
|
||||
blocks.push(Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: 'http://s3.amazonaws.com/hifi-public/marketplace/hificontent/Games/blocks/block.fbx',
|
||||
shapeType: 'box',
|
||||
position: dropPos,
|
||||
dimensions: BLOCK_SIZE,
|
||||
collisionsWillMove: true,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -9,
|
||||
z: 0
|
||||
},
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: .1,
|
||||
z: 0
|
||||
},
|
||||
angularVelocity: {
|
||||
x: randFloat(-angVelRange, angVelRange),
|
||||
y: randFloat(-angVelRange, angVelRange),
|
||||
z: randFloat(-angVelRange, angVelRange),
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function mousePressEvent(event) {
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({
|
||||
x: event.x,
|
||||
y: event.y
|
||||
});
|
||||
if (clickedOverlay == offButton) {
|
||||
Script.clearInterval(blockSpawner);
|
||||
}
|
||||
if(clickedOverlay == deleteButton){
|
||||
destroyStuff();
|
||||
}
|
||||
}
|
||||
|
||||
generateFloor();
|
||||
|
||||
function cleanup() {
|
||||
// for (var i = 0; i < floorTiles.length; i++) {
|
||||
// Entities.deleteEntity(floorTiles[i]);
|
||||
// }
|
||||
// for (var i = 0; i < blocks.length; i++) {
|
||||
// Entities.deleteEntity(blocks[i]);
|
||||
// }
|
||||
Overlays.deleteOverlay(offButton);
|
||||
Overlays.deleteOverlay(deleteButton)
|
||||
Script.clearInterval(blockSpawner);
|
||||
}
|
||||
|
||||
function destroyStuff() {
|
||||
for (var i = 0; i < floorTiles.length; i++) {
|
||||
Entities.deleteEntity(floorTiles[i]);
|
||||
}
|
||||
for (var i = 0; i < blocks.length; i++) {
|
||||
Entities.deleteEntity(blocks[i]);
|
||||
}
|
||||
Script.clearInterval(blockSpawner);
|
||||
|
||||
}
|
||||
|
||||
function randFloat(low, high) {
|
||||
return Math.floor(low + Math.random() * (high - low));
|
||||
}
|
||||
|
||||
function map(value, min1, max1, min2, max2) {
|
||||
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
|
@ -198,7 +198,7 @@ function checkControllerSide(hand) {
|
|||
var closestEntity = Entities.findClosestEntity(hand.palmPosition(), CATCH_RADIUS);
|
||||
var modelUrl = Entities.getEntityProperties(closestEntity).modelURL;
|
||||
print("lol2"+closestEntity.isKnownID);
|
||||
if (closestEntity.isKnownID && validFrisbeeURL(Entities.getEntityProperties(closestEntity).modelURL)) {
|
||||
if (closestEntity && validFrisbeeURL(Entities.getEntityProperties(closestEntity).modelURL)) {
|
||||
print("lol");
|
||||
Entities.editEntity(closestEntity, {modelScale: 1, inHand: true, position: hand.holdPosition(), shouldDie: true});
|
||||
Entities.deleteEntity(closestEntity);
|
||||
|
@ -448,4 +448,4 @@ Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
|||
Menu.menuItemEvent.connect(menuItemEvent);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Script.update.connect(checkController);
|
||||
Script.update.connect(controlFrisbees);
|
||||
Script.update.connect(controlFrisbees);
|
||||
|
|
|
@ -153,6 +153,26 @@ if (showScore) {
|
|||
|
||||
var BULLET_VELOCITY = 10.0;
|
||||
|
||||
function entityCollisionWithEntity(entity1, entity2, collision) {
|
||||
if (entity2 === targetID) {
|
||||
score++;
|
||||
if (showScore) {
|
||||
Overlays.editOverlay(text, { text: "Score: " + score } );
|
||||
}
|
||||
|
||||
// We will delete the bullet and target in 1/2 sec, but for now we can see them bounce!
|
||||
Script.setTimeout(deleteBulletAndTarget, 500);
|
||||
|
||||
// Turn the target and the bullet white
|
||||
Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }});
|
||||
Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }});
|
||||
|
||||
// play the sound near the camera so the shooter can hear it
|
||||
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
Audio.playSound(targetHitSound, audioOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function shootBullet(position, velocity, grenade) {
|
||||
var BULLET_SIZE = 0.10;
|
||||
var BULLET_LIFETIME = 10.0;
|
||||
|
@ -178,6 +198,7 @@ function shootBullet(position, velocity, grenade) {
|
|||
ignoreCollisions: false,
|
||||
collisionsWillMove: true
|
||||
});
|
||||
Script.addEventHandler(bulletID, "collisionWithEntity", entityCollisionWithEntity);
|
||||
|
||||
// Play firing sounds
|
||||
audioOptions.position = position;
|
||||
|
@ -310,27 +331,6 @@ function makePlatform(gravity, scale, size) {
|
|||
|
||||
}
|
||||
|
||||
function entityCollisionWithEntity(entity1, entity2, collision) {
|
||||
if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) &&
|
||||
((entity2.id == bulletID.id) || (entity2.id == targetID.id))) {
|
||||
score++;
|
||||
if (showScore) {
|
||||
Overlays.editOverlay(text, { text: "Score: " + score } );
|
||||
}
|
||||
|
||||
// We will delete the bullet and target in 1/2 sec, but for now we can see them bounce!
|
||||
Script.setTimeout(deleteBulletAndTarget, 500);
|
||||
|
||||
// Turn the target and the bullet white
|
||||
Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }});
|
||||
Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }});
|
||||
|
||||
// play the sound near the camera so the shooter can hear it
|
||||
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
Audio.playSound(targetHitSound, audioOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
// if our tools are off, then don't do anything
|
||||
if (event.text == "t") {
|
||||
|
@ -386,13 +386,6 @@ MyAvatar.attach(gunModel, "LeftHand", {x:-0.04, y: 0.22, z: 0.02}, Quat.fromPitc
|
|||
Script.setTimeout(playLoadSound, 2000);
|
||||
|
||||
function update(deltaTime) {
|
||||
if (bulletID && !bulletID.isKnownID) {
|
||||
bulletID = Entities.identifyEntity(bulletID);
|
||||
}
|
||||
if (targetID && !targetID.isKnownID) {
|
||||
targetID = Entities.identifyEntity(targetID);
|
||||
}
|
||||
|
||||
if (activeControllers == 0) {
|
||||
if (Controller.getNumberOfSpatialControls() > 0) {
|
||||
activeControllers = Controller.getNumberOfSpatialControls();
|
||||
|
@ -512,7 +505,6 @@ function scriptEnding() {
|
|||
clearPose();
|
||||
}
|
||||
|
||||
Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Script.update.connect(update);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -132,12 +132,6 @@ function update(deltaTime) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!paddle.isKnownID) {
|
||||
paddle = Entities.identifyEntity(paddle);
|
||||
}
|
||||
if (!ball.isKnownID) {
|
||||
ball = Entities.identifyEntity(ball);
|
||||
} else {
|
||||
var paddleOrientation = leftHanded ? PADDLE_ORIENTATION : Quat.multiply(PADDLE_ORIENTATION, Quat.fromPitchYawRollDegrees(0, 180, 0));
|
||||
var paddleWorldOrientation = Quat.multiply(Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), paddleOrientation);
|
||||
var holdPosition = Vec3.sum(leftHanded ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(),
|
||||
|
@ -157,7 +151,7 @@ function update(deltaTime) {
|
|||
Entities.editEntity(paddleModel, { position: Vec3.sum(holdPosition, Vec3.multiplyQbyV(paddleWorldOrientation, PADDLE_BOX_OFFSET)),
|
||||
velocity: Controller.getSpatialControlVelocity(controllerID),
|
||||
rotation: paddleWorldOrientation });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function entityCollisionWithEntity(entity1, entity2, collision) {
|
||||
|
|
|
@ -109,17 +109,10 @@ function checkControllerSide(whichSide) {
|
|||
var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3) || (Controller.getTriggerValue(TRIGGER) > 0.5));
|
||||
|
||||
// If I don't currently have a ball in my hand, then try to catch closest one
|
||||
if (leftHandEntity && !leftHandEntity.isKnownID) {
|
||||
leftHandEntity = Entities.identifyEntity(leftHandEntity);
|
||||
}
|
||||
if (rightHandEntity && !rightHandEntity.isKnownID) {
|
||||
rightHandEntity = Entities.identifyEntity(rightHandEntity);
|
||||
}
|
||||
|
||||
if (!ballAlreadyInHand && grabButtonPressed) {
|
||||
var closestEntity = Entities.findClosestEntity(palmPosition, targetRadius);
|
||||
|
||||
if (closestEntity.isKnownID) {
|
||||
if (closestEntity) {
|
||||
var foundProperties = Entities.getEntityProperties(closestEntity);
|
||||
if (Vec3.length(foundProperties.velocity) > 0.0) {
|
||||
|
||||
|
|
|
@ -12,10 +12,9 @@ Script.load("progress.js");
|
|||
Script.load("edit.js");
|
||||
Script.load("selectAudioDevice.js");
|
||||
Script.load("controllers/hydra/hydraMove.js");
|
||||
Script.load("headMove.js");
|
||||
Script.load("inspect.js");
|
||||
Script.load("lobby.js");
|
||||
Script.load("notifications.js");
|
||||
Script.load("look.js");
|
||||
Script.load("users.js");
|
||||
Script.load("grab.js");
|
||||
Script.load("pointer.js");
|
||||
|
|
23
examples/dialTone.js
Normal file
23
examples/dialTone.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// dialTone.js
|
||||
// examples
|
||||
//
|
||||
// Created by Stephen Birarda on 06/08/15.
|
||||
// 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
|
||||
//
|
||||
|
||||
// setup the local sound we're going to use
|
||||
var connectSound = SoundCache.getSound("file://" + Paths.resources + "sounds/short1.wav");
|
||||
|
||||
// setup the options needed for that sound
|
||||
var connectSoundOptions = {
|
||||
localOnly: true
|
||||
}
|
||||
|
||||
// play the sound locally once we get the first audio packet from a mixer
|
||||
Audio.receivedFirstPacket.connect(function(){
|
||||
Audio.playSound(connectSound, connectSoundOptions);
|
||||
});
|
174
examples/dice.js
174
examples/dice.js
|
@ -12,16 +12,17 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var isDice = false;
|
||||
var NUMBER_OF_DICE = 2;
|
||||
var isDice = false;
|
||||
var NUMBER_OF_DICE = 4;
|
||||
var LIFETIME = 10000; // Dice will live for about 3 hours
|
||||
var dice = [];
|
||||
var DIE_SIZE = 0.20;
|
||||
|
||||
var madeSound = true; // Set false at start of throw to look for collision
|
||||
var madeSound = true; // Set false at start of throw to look for collision
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
SoundCache.getSound("http://s3.amazonaws.com/hifi-public/sounds/dice/diceCollide.wav");
|
||||
|
||||
var rollSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/dice/diceRoll.wav");
|
||||
|
||||
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to create new objects."
|
||||
|
||||
|
@ -31,97 +32,118 @@ var BUTTON_SIZE = 32;
|
|||
var PADDING = 3;
|
||||
|
||||
var offButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 - BUTTON_SIZE,
|
||||
y: screenSize.y- (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var deleteButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 - BUTTON_SIZE,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/delete.png",
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var diceButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 + PADDING,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/die.png",
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
});
|
||||
x: screenSize.x / 2 + PADDING,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/die.png",
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var GRAVITY = -3.5;
|
||||
var LIFETIME = 300;
|
||||
|
||||
// NOTE: angularVelocity is in radians/sec
|
||||
var MAX_ANGULAR_SPEED = Math.PI;
|
||||
|
||||
|
||||
function shootDice(position, velocity) {
|
||||
if (!Entities.canRez()) {
|
||||
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
|
||||
} else {
|
||||
for (var i = 0; i < NUMBER_OF_DICE; i++) {
|
||||
dice.push(Entities.addEntity(
|
||||
{ type: "Model",
|
||||
modelURL: HIFI_PUBLIC_BUCKET + "models/props/Dice/goldDie.fbx",
|
||||
position: position,
|
||||
velocity: velocity,
|
||||
rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360),
|
||||
angularVelocity: { x: Math.random() * MAX_ANGULAR_SPEED,
|
||||
y: Math.random() * MAX_ANGULAR_SPEED,
|
||||
z: Math.random() * MAX_ANGULAR_SPEED },
|
||||
lifetime: LIFETIME,
|
||||
gravity: { x: 0, y: GRAVITY, z: 0 },
|
||||
shapeType: "box",
|
||||
collisionsWillMove: true
|
||||
}));
|
||||
position = Vec3.sum(position, Vec3.multiply(DIE_SIZE, Vec3.normalize(Quat.getRight(Camera.getOrientation()))));
|
||||
}
|
||||
if (!Entities.canRez()) {
|
||||
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
|
||||
} else {
|
||||
for (var i = 0; i < NUMBER_OF_DICE; i++) {
|
||||
dice.push(Entities.addEntity(
|
||||
{
|
||||
type: "Model",
|
||||
modelURL: HIFI_PUBLIC_BUCKET + "models/props/Dice/goldDie.fbx",
|
||||
position: position,
|
||||
velocity: velocity,
|
||||
rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360),
|
||||
angularVelocity: {
|
||||
x: Math.random() * MAX_ANGULAR_SPEED,
|
||||
y: Math.random() * MAX_ANGULAR_SPEED,
|
||||
z: Math.random() * MAX_ANGULAR_SPEED
|
||||
},
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: GRAVITY,
|
||||
z: 0
|
||||
},
|
||||
lifetime: LIFETIME,
|
||||
shapeType: "box",
|
||||
collisionsWillMove: true,
|
||||
collisionSoundURL: "http://s3.amazonaws.com/hifi-public/sounds/dice/diceCollide.wav"
|
||||
}));
|
||||
position = Vec3.sum(position, Vec3.multiply(DIE_SIZE, Vec3.normalize(Quat.getRight(Camera.getOrientation()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteDice() {
|
||||
while(dice.length > 0) {
|
||||
Entities.deleteEntity(dice.pop());
|
||||
}
|
||||
while (dice.length > 0) {
|
||||
Entities.deleteEntity(dice.pop());
|
||||
}
|
||||
}
|
||||
|
||||
function entityCollisionWithEntity(entity1, entity2, collision) {
|
||||
if (!madeSound) {
|
||||
// Is it one of our dice?
|
||||
for (var i = 0; i < dice.length; i++) {
|
||||
if (!dice[i].isKnownID) {
|
||||
dice[i] = Entities.identifyEntity(dice[i]);
|
||||
}
|
||||
if ((entity1.id == dice[i].id) || (entity2.id == dice[i].id)) {
|
||||
madeSound = true;
|
||||
Audio.playSound(rollSound, { position: collision.contactPoint });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mousePressEvent(event) {
|
||||
var clickedText = false;
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
if (clickedOverlay == offButton) {
|
||||
Script.stop();
|
||||
} else if (clickedOverlay == diceButton) {
|
||||
var HOW_HARD = 2.0;
|
||||
var position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
var velocity = Vec3.multiply(HOW_HARD, Quat.getFront(Camera.getOrientation()));
|
||||
shootDice(position, velocity);
|
||||
madeSound = false;
|
||||
}
|
||||
var clickedText = false;
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({
|
||||
x: event.x,
|
||||
y: event.y
|
||||
});
|
||||
if (clickedOverlay == offButton) {
|
||||
deleteDice();
|
||||
Script.stop();
|
||||
} else if (clickedOverlay == deleteButton) {
|
||||
deleteDice();
|
||||
} else if (clickedOverlay == diceButton) {
|
||||
var HOW_HARD = 2.0;
|
||||
var position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
var velocity = Vec3.multiply(HOW_HARD, Quat.getFront(Camera.getOrientation()));
|
||||
shootDice(position, velocity);
|
||||
madeSound = false;
|
||||
}
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
deleteDice();
|
||||
Overlays.deleteOverlay(offButton);
|
||||
Overlays.deleteOverlay(diceButton);
|
||||
|
||||
Overlays.deleteOverlay(offButton);
|
||||
Overlays.deleteOverlay(diceButton);
|
||||
Overlays.deleteOverlay(deleteButton);
|
||||
}
|
||||
|
||||
Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
177
examples/edit.js
177
examples/edit.js
|
@ -1,4 +1,3 @@
|
|||
|
||||
// newEditEntities.js
|
||||
// examples
|
||||
//
|
||||
|
@ -15,7 +14,7 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
|||
|
||||
Script.include([
|
||||
"libraries/stringHelpers.js",
|
||||
"libraries/dataviewHelpers.js",
|
||||
"libraries/dataViewHelpers.js",
|
||||
"libraries/toolBars.js",
|
||||
"libraries/progressDialog.js",
|
||||
|
||||
|
@ -28,7 +27,6 @@ Script.include([
|
|||
"libraries/gridTool.js",
|
||||
"libraries/entityList.js",
|
||||
"libraries/lightOverlayManager.js",
|
||||
"libraries/zoneOverlayManager.js",
|
||||
]);
|
||||
|
||||
var selectionDisplay = SelectionDisplay;
|
||||
|
@ -36,7 +34,6 @@ var selectionManager = SelectionManager;
|
|||
var entityPropertyDialogBox = EntityPropertyDialogBox;
|
||||
|
||||
var lightOverlayManager = new LightOverlayManager();
|
||||
var zoneOverlayManager = new ZoneOverlayManager();
|
||||
|
||||
var cameraManager = new CameraManager();
|
||||
|
||||
|
@ -49,7 +46,6 @@ var entityListTool = EntityListTool();
|
|||
selectionManager.addEventListener(function() {
|
||||
selectionDisplay.updateHandles();
|
||||
lightOverlayManager.updatePositions();
|
||||
zoneOverlayManager.updatePositions();
|
||||
});
|
||||
|
||||
var windowDimensions = Controller.getViewportDimensions();
|
||||
|
@ -140,7 +136,9 @@ var toolBar = (function () {
|
|||
newSphereButton,
|
||||
newLightButton,
|
||||
newTextButton,
|
||||
newWebButton,
|
||||
newZoneButton,
|
||||
newPolyVoxButton,
|
||||
browseMarketplaceButton;
|
||||
|
||||
function initialize() {
|
||||
|
@ -207,9 +205,28 @@ var toolBar = (function () {
|
|||
alpha: 0.9,
|
||||
visible: false
|
||||
});
|
||||
|
||||
newWebButton = toolBar.addTool({
|
||||
imageURL: "https://hifi-public.s3.amazonaws.com/images/www.svg",
|
||||
subImage: { x: 0, y: 0, width: 128, height: 128 },
|
||||
width: toolWidth,
|
||||
height: toolHeight,
|
||||
alpha: 0.9,
|
||||
visible: false
|
||||
});
|
||||
|
||||
newZoneButton = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "zonecube3.svg",
|
||||
subImage: { x: 0, y: Tool.IMAGE_WIDTH + 208, width: 256, height: 256 },
|
||||
imageURL: toolIconUrl + "zonecube_text.svg",
|
||||
subImage: { x: 0, y: 128, width: 128, height: 128 },
|
||||
width: toolWidth,
|
||||
height: toolHeight,
|
||||
alpha: 0.9,
|
||||
visible: false
|
||||
});
|
||||
|
||||
newPolyVoxButton = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "polyvox.svg",
|
||||
subImage: { x: 0, y: 0, width: 256, height: 256 },
|
||||
width: toolWidth,
|
||||
height: toolHeight,
|
||||
alpha: 0.9,
|
||||
|
@ -246,7 +263,7 @@ var toolBar = (function () {
|
|||
}
|
||||
toolBar.selectTool(activeButton, isActive);
|
||||
lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
|
||||
zoneOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
|
||||
Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
|
||||
};
|
||||
|
||||
// Sets visibility of tool buttons, excluding the power button
|
||||
|
@ -256,7 +273,9 @@ var toolBar = (function () {
|
|||
toolBar.showTool(newSphereButton, doShow);
|
||||
toolBar.showTool(newLightButton, doShow);
|
||||
toolBar.showTool(newTextButton, doShow);
|
||||
toolBar.showTool(newWebButton, doShow);
|
||||
toolBar.showTool(newZoneButton, doShow);
|
||||
toolBar.showTool(newPolyVoxButton, doShow);
|
||||
};
|
||||
|
||||
var RESIZE_INTERVAL = 50;
|
||||
|
@ -428,6 +447,22 @@ var toolBar = (function () {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (newWebButton === toolBar.clicked(clickedOverlay)) {
|
||||
var position = getPositionToCreateEntity();
|
||||
|
||||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||
placingEntityID = Entities.addEntity({
|
||||
type: "Web",
|
||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||
dimensions: { x: 1.6, y: 0.9, z: 0.01 },
|
||||
sourceUrl: "https://highfidelity.com/",
|
||||
});
|
||||
} else {
|
||||
print("Can't create Web Entity: would be out of bounds.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (newZoneButton === toolBar.clicked(clickedOverlay)) {
|
||||
var position = getPositionToCreateEntity();
|
||||
|
||||
|
@ -443,6 +478,24 @@ var toolBar = (function () {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (newPolyVoxButton === toolBar.clicked(clickedOverlay)) {
|
||||
var position = getPositionToCreateEntity();
|
||||
|
||||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||
placingEntityID = Entities.addEntity({
|
||||
type: "PolyVox",
|
||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS),
|
||||
DEFAULT_DIMENSIONS),
|
||||
dimensions: { x: 10, y: 10, z: 10 },
|
||||
voxelVolumeSize: {x:16, y:16, z:16},
|
||||
voxelSurfaceStyle: 1
|
||||
});
|
||||
} else {
|
||||
print("Can't create PolyVox: would be out of bounds.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
@ -554,16 +607,6 @@ function findClickedEntity(event) {
|
|||
}
|
||||
|
||||
var foundEntity = result.entityID;
|
||||
|
||||
if (!foundEntity.isKnownID) {
|
||||
var identify = Entities.identifyEntity(foundEntity);
|
||||
if (!identify.isKnownID) {
|
||||
print("Unknown ID " + identify.id + " (update loop " + foundEntity.id + ")");
|
||||
return null;
|
||||
}
|
||||
foundEntity = identify;
|
||||
}
|
||||
|
||||
return { pickRay: pickRay, entityID: foundEntity };
|
||||
}
|
||||
|
||||
|
@ -585,20 +628,21 @@ function mousePressEvent(event) {
|
|||
}
|
||||
}
|
||||
|
||||
var highlightedEntityID = { isKnownID: false };
|
||||
var highlightedEntityID = null;
|
||||
var mouseCapturedByTool = false;
|
||||
var lastMousePosition = null;
|
||||
var idleMouseTimerId = null;
|
||||
var IDLE_MOUSE_TIMEOUT = 200;
|
||||
var DEFAULT_ENTITY_DRAG_DROP_DISTANCE = 2.0;
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
var lastMouseMoveEvent = null;
|
||||
function mouseMoveEventBuffered(event) {
|
||||
lastMouseMoveEvent = event;
|
||||
}
|
||||
function mouseMove(event) {
|
||||
mouseHasMovedSincePress = true;
|
||||
|
||||
if (placingEntityID) {
|
||||
if (!placingEntityID.isKnownID) {
|
||||
placingEntityID = Entities.identifyEntity(placingEntityID);
|
||||
}
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE;
|
||||
var offset = Vec3.multiply(distance, pickRay.direction);
|
||||
|
@ -635,9 +679,9 @@ function highlightEntityUnderCursor(position, accurateRay) {
|
|||
var pickRay = Camera.computePickRay(position.x, position.y);
|
||||
var entityIntersection = Entities.findRayIntersection(pickRay, accurateRay);
|
||||
if (entityIntersection.accurate) {
|
||||
if(highlightedEntityID.isKnownID && highlightedEntityID.id != entityIntersection.entityID.id) {
|
||||
if(highlightedEntityID && highlightedEntityID != entityIntersection.entityID) {
|
||||
selectionDisplay.unhighlightSelectable(highlightedEntityID);
|
||||
highlightedEntityID = { id: -1, isKnownID: false };
|
||||
highlightedEntityID = { id: -1 };
|
||||
}
|
||||
|
||||
var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0;
|
||||
|
@ -648,7 +692,7 @@ function highlightEntityUnderCursor(position, accurateRay) {
|
|||
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
|
||||
&& (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
|
||||
|
||||
if (entityIntersection.entityID.isKnownID && sizeOK) {
|
||||
if (entityIntersection.entityID && sizeOK) {
|
||||
if (wantEntityGlow) {
|
||||
Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 });
|
||||
}
|
||||
|
@ -661,6 +705,10 @@ function highlightEntityUnderCursor(position, accurateRay) {
|
|||
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
if (lastMouseMoveEvent) {
|
||||
mouseMove(lastMouseMoveEvent);
|
||||
lastMouseMoveEvent = null;
|
||||
}
|
||||
if (propertyMenu.mouseReleaseEvent(event) || toolBar.mouseReleaseEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -703,7 +751,7 @@ function mouseClickEvent(event) {
|
|||
} else {
|
||||
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
|
||||
|
||||
print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal);
|
||||
print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal);
|
||||
// P P - Model
|
||||
// /| A - Palm
|
||||
// / | d B - unit vector toward tip
|
||||
|
@ -742,7 +790,7 @@ function mouseClickEvent(event) {
|
|||
selectionManager.addEntity(foundEntity, true);
|
||||
}
|
||||
|
||||
print("Model selected: " + foundEntity.id);
|
||||
print("Model selected: " + foundEntity);
|
||||
selectionDisplay.select(selectedEntityID, event);
|
||||
|
||||
if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) {
|
||||
|
@ -772,7 +820,7 @@ function mouseClickEvent(event) {
|
|||
}
|
||||
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEventBuffered);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
|
||||
|
||||
|
@ -882,6 +930,10 @@ Script.update.connect(function (deltaTime) {
|
|||
lastOrientation = Camera.orientation;
|
||||
lastPosition = Camera.position;
|
||||
}
|
||||
if (lastMouseMoveEvent) {
|
||||
mouseMove(lastMouseMoveEvent);
|
||||
lastMouseMoveEvent = null;
|
||||
}
|
||||
});
|
||||
|
||||
function insideBox(center, dimensions, point) {
|
||||
|
@ -896,7 +948,7 @@ function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) {
|
|||
var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition,
|
||||
Vec3.multiply(selectionManager.worldDimensions, 0.5));
|
||||
var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions);
|
||||
|
||||
|
||||
if (!keepIfTouching) {
|
||||
var isValid;
|
||||
if (selectionManager.localPosition === null) {
|
||||
|
@ -930,8 +982,8 @@ function deleteSelectedEntities() {
|
|||
var savedProperties = [];
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
var entityID = SelectionManager.selections[i];
|
||||
var initialProperties = SelectionManager.savedProperties[entityID.id];
|
||||
SelectionManager.savedProperties[entityID.id];
|
||||
var initialProperties = SelectionManager.savedProperties[entityID];
|
||||
SelectionManager.savedProperties[entityID];
|
||||
savedProperties.push({
|
||||
entityID: entityID,
|
||||
properties: initialProperties
|
||||
|
@ -968,7 +1020,7 @@ function handeMenuEvent(menuItem) {
|
|||
}
|
||||
}
|
||||
} else if (menuItem == "Import Entities" || menuItem == "Import Entities from URL") {
|
||||
|
||||
|
||||
var importURL;
|
||||
if (menuItem == "Import Entities") {
|
||||
importURL = Window.browse("Select models to import", "", "*.json");
|
||||
|
@ -988,7 +1040,7 @@ function handeMenuEvent(menuItem) {
|
|||
} else if (menuItem == MENU_SHOW_LIGHTS_IN_EDIT_MODE) {
|
||||
lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
|
||||
} else if (menuItem == MENU_SHOW_ZONES_IN_EDIT_MODE) {
|
||||
zoneOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
|
||||
Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
|
||||
}
|
||||
tooltip.show(false);
|
||||
}
|
||||
|
@ -1018,8 +1070,11 @@ function importSVO(importURL) {
|
|||
var success = Clipboard.importEntities(importURL);
|
||||
|
||||
if (success) {
|
||||
var position = getPositionToCreateEntity();
|
||||
|
||||
var VERY_LARGE = 10000;
|
||||
var position = { x: 0, y: 0, z: 0};
|
||||
if (Clipboard.getClipboardContentsLargestDimension() < VERY_LARGE) {
|
||||
position = getPositionToCreateEntity();
|
||||
}
|
||||
var pastedEntityIDs = Clipboard.pasteEntities(position);
|
||||
|
||||
if (isActive) {
|
||||
|
@ -1087,8 +1142,8 @@ function applyEntityProperties(data) {
|
|||
var selectedEntityIDs = [];
|
||||
for (var i = 0; i < properties.length; i++) {
|
||||
var entityID = properties[i].entityID;
|
||||
if (DELETED_ENTITY_MAP[entityID.id] !== undefined) {
|
||||
entityID = DELETED_ENTITY_MAP[entityID.id];
|
||||
if (DELETED_ENTITY_MAP[entityID] !== undefined) {
|
||||
entityID = DELETED_ENTITY_MAP[entityID];
|
||||
}
|
||||
Entities.editEntity(entityID, properties[i].properties);
|
||||
selectedEntityIDs.push(entityID);
|
||||
|
@ -1097,15 +1152,15 @@ function applyEntityProperties(data) {
|
|||
var entityID = data.createEntities[i].entityID;
|
||||
var properties = data.createEntities[i].properties;
|
||||
var newEntityID = Entities.addEntity(properties);
|
||||
DELETED_ENTITY_MAP[entityID.id] = newEntityID;
|
||||
DELETED_ENTITY_MAP[entityID] = newEntityID;
|
||||
if (data.selectCreated) {
|
||||
selectedEntityIDs.push(newEntityID);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < data.deleteEntities.length; i++) {
|
||||
var entityID = data.deleteEntities[i].entityID;
|
||||
if (DELETED_ENTITY_MAP[entityID.id] !== undefined) {
|
||||
entityID = DELETED_ENTITY_MAP[entityID.id];
|
||||
if (DELETED_ENTITY_MAP[entityID] !== undefined) {
|
||||
entityID = DELETED_ENTITY_MAP[entityID];
|
||||
}
|
||||
Entities.deleteEntity(entityID);
|
||||
}
|
||||
|
@ -1130,7 +1185,7 @@ function pushCommandForSelections(createdEntityData, deletedEntityData) {
|
|||
};
|
||||
for (var i = 0; i < SelectionManager.selections.length; i++) {
|
||||
var entityID = SelectionManager.selections[i];
|
||||
var initialProperties = SelectionManager.savedProperties[entityID.id];
|
||||
var initialProperties = SelectionManager.savedProperties[entityID];
|
||||
var currentProperties = Entities.getEntityProperties(entityID);
|
||||
undoData.setProperties.push({
|
||||
entityID: entityID,
|
||||
|
@ -1174,7 +1229,7 @@ PropertiesTool = function(opts) {
|
|||
var selections = [];
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
var entity = {};
|
||||
entity.id = selectionManager.selections[i].id;
|
||||
entity.id = selectionManager.selections[i];
|
||||
entity.properties = Entities.getEntityProperties(selectionManager.selections[i]);
|
||||
entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation);
|
||||
selections.push(entity);
|
||||
|
@ -1185,7 +1240,11 @@ PropertiesTool = function(opts) {
|
|||
|
||||
webView.eventBridge.webEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
if (data.type == "update") {
|
||||
if (data.type == "print") {
|
||||
if (data.message) {
|
||||
print(data.message);
|
||||
}
|
||||
} else if (data.type == "update") {
|
||||
selectionManager.saveProperties();
|
||||
if (selectionManager.selections.length > 1) {
|
||||
properties = {
|
||||
|
@ -1201,6 +1260,9 @@ PropertiesTool = function(opts) {
|
|||
data.properties.rotation = Quat.fromPitchYawRollDegrees(rotation.x, rotation.y, rotation.z);
|
||||
}
|
||||
Entities.editEntity(selectionManager.selections[0], data.properties);
|
||||
if (data.properties.name != undefined) {
|
||||
entityListTool.sendUpdate();
|
||||
}
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
|
@ -1217,7 +1279,7 @@ PropertiesTool = function(opts) {
|
|||
var dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2),
|
||||
var diff = { x: 0, y: dY, z: 0 };
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
var properties = selectionManager.savedProperties[selectionManager.selections[i].id];
|
||||
var properties = selectionManager.savedProperties[selectionManager.selections[i]];
|
||||
var newPosition = Vec3.sum(properties.position, diff);
|
||||
Entities.editEntity(selectionManager.selections[i], {
|
||||
position: newPosition,
|
||||
|
@ -1230,7 +1292,7 @@ PropertiesTool = function(opts) {
|
|||
if (selectionManager.hasSelection()) {
|
||||
selectionManager.saveProperties();
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
var properties = selectionManager.savedProperties[selectionManager.selections[i].id];
|
||||
var properties = selectionManager.savedProperties[selectionManager.selections[i]];
|
||||
var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2;
|
||||
var dY = grid.getOrigin().y - bottomY;
|
||||
var diff = { x: 0, y: dY, z: 0 };
|
||||
|
@ -1246,7 +1308,7 @@ PropertiesTool = function(opts) {
|
|||
if (selectionManager.hasSelection()) {
|
||||
selectionManager.saveProperties();
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
var properties = selectionManager.savedProperties[selectionManager.selections[i].id];
|
||||
var properties = selectionManager.savedProperties[selectionManager.selections[i]];
|
||||
var naturalDimensions = properties.naturalDimensions;
|
||||
|
||||
// If any of the natural dimensions are not 0, resize
|
||||
|
@ -1268,7 +1330,7 @@ PropertiesTool = function(opts) {
|
|||
if (selectionManager.hasSelection()) {
|
||||
selectionManager.saveProperties();
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
var properties = selectionManager.savedProperties[selectionManager.selections[i].id];
|
||||
var properties = selectionManager.savedProperties[selectionManager.selections[i]];
|
||||
Entities.editEntity(selectionManager.selections[i], {
|
||||
dimensions: Vec3.multiply(multiplier, properties.dimensions),
|
||||
});
|
||||
|
@ -1276,6 +1338,25 @@ PropertiesTool = function(opts) {
|
|||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
}
|
||||
} else if (data.action == "centerAtmosphereToZone") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
selectionManager.saveProperties();
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
var properties = selectionManager.savedProperties[selectionManager.selections[i]];
|
||||
if (properties.type == "Zone") {
|
||||
var centerOfZone = properties.boundingBox.center;
|
||||
var atmosphereCenter = { x: centerOfZone.x,
|
||||
y: centerOfZone.y - properties.atmosphere.innerRadius,
|
||||
z: centerOfZone.z };
|
||||
|
||||
Entities.editEntity(selectionManager.selections[i], {
|
||||
atmosphere: { center: atmosphereCenter },
|
||||
});
|
||||
}
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
// All callbacks start by updating the properties
|
||||
this.updateProperties = function(entityID) {
|
||||
// Piece ID
|
||||
if (this.entityID === null || !this.entityID.isKnownID) {
|
||||
this.entityID = Entities.identifyEntity(entityID);
|
||||
if (this.entityID === null) {
|
||||
this.entityID = entityID;
|
||||
}
|
||||
// Piece Properties
|
||||
this.properties = Entities.getEntityProperties(this.entityID);
|
||||
|
@ -27,12 +27,7 @@
|
|||
if (this.boardID === null) {
|
||||
// Read user data string and update boardID
|
||||
var userData = JSON.parse(this.properties.userData);
|
||||
var boardID = Entities.identifyEntity(userData.boardID);
|
||||
if (boardID.isKnownID) {
|
||||
this.boardID = boardID;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
this.boardID = userData.boardID;
|
||||
}
|
||||
|
||||
// Board User Data
|
||||
|
@ -52,13 +47,13 @@
|
|||
// Updates user data related objects
|
||||
this.updateUserData = function() {
|
||||
// Get board's user data
|
||||
if (this.boardID !== null && this.boardID.isKnownID) {
|
||||
if (this.boardID !== null) {
|
||||
this.boardUserData = this.getEntityUserData(this.boardID);
|
||||
|
||||
if (!(this.boardUserData &&
|
||||
this.boardUserData.firstTile &&
|
||||
this.boardUserData.tileSize)) {
|
||||
print("Incomplete boardUserData " + this.boardID.id);
|
||||
print("Incomplete boardUserData " + this.boardID);
|
||||
} else {
|
||||
this.FIRST_TILE = this.boardUserData.firstTile;
|
||||
this.TILE_SIZE = this.boardUserData.tileSize;
|
||||
|
@ -137,7 +132,7 @@
|
|||
for (var i = 0; i < others.length; i++) {
|
||||
var piece = others[i];
|
||||
|
||||
if (piece.id != this.entityID.id) {
|
||||
if (piece.id != this.entityID) {
|
||||
var properties = Entities.getEntityProperties(piece);
|
||||
|
||||
var isWhite = properties.modelURL.search("White") !== -1;
|
||||
|
@ -198,4 +193,4 @@
|
|||
this.updateProperties(entityID); // All callbacks start by updating the properties
|
||||
this.release(mouseEvent);
|
||||
};
|
||||
})
|
||||
})
|
||||
|
|
|
@ -35,8 +35,8 @@
|
|||
}
|
||||
// All callbacks start by updating the properties
|
||||
this.updateProperties = function(entityID) {
|
||||
if (this.entityID === null || !this.entityID.isKnownID) {
|
||||
this.entityID = Entities.identifyEntity(entityID);
|
||||
if (this.entityID === null) {
|
||||
this.entityID = entityID;
|
||||
}
|
||||
this.properties = Entities.getEntityProperties(this.entityID);
|
||||
};
|
||||
|
|
|
@ -32,14 +32,14 @@
|
|||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
function didEntityExist(entityID) {
|
||||
return entityID && entityID.isKnownID;
|
||||
return entityID;
|
||||
}
|
||||
function doesEntityExistNow(entityID) {
|
||||
return entityID && getTrueID(entityID).isKnownID;
|
||||
return entityID;
|
||||
}
|
||||
function getTrueID(entityID) {
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
return { id: properties.id, creatorTokenID: properties.creatorTokenID, isKnownID: properties.isKnownID };
|
||||
return { id: properties.id };
|
||||
}
|
||||
function getUserData(entityID) {
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
|
@ -225,4 +225,4 @@
|
|||
this.updateRelativeLightPosition();
|
||||
}
|
||||
};
|
||||
})
|
||||
})
|
||||
|
|
|
@ -251,8 +251,8 @@
|
|||
};
|
||||
// All callbacks start by updating the properties
|
||||
this.updateProperties = function(entityID) {
|
||||
if (this.entityID === null || !this.entityID.isKnownID) {
|
||||
this.entityID = Entities.identifyEntity(entityID);
|
||||
if (this.entityID === null) {
|
||||
this.entityID = entityID;
|
||||
}
|
||||
this.properties = Entities.getEntityProperties(this.entityID);
|
||||
};
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
var bird;
|
||||
|
||||
this.preload = function(entityID) {
|
||||
print("preload("+entityID.id+")");
|
||||
print("preload("+entityID+")");
|
||||
bird = SoundCache.getSound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw");
|
||||
};
|
||||
|
||||
|
|
|
@ -22,17 +22,17 @@
|
|||
}
|
||||
|
||||
this.preload = function(entityID) {
|
||||
print("preload("+entityID.id+")");
|
||||
print("preload("+entityID+")");
|
||||
bird = SoundCache.getSound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw");
|
||||
};
|
||||
|
||||
this.enterEntity = function(entityID) {
|
||||
print("enterEntity("+entityID.id+")");
|
||||
print("enterEntity("+entityID+")");
|
||||
playSound();
|
||||
};
|
||||
|
||||
this.leaveEntity = function(entityID) {
|
||||
print("leaveEntity("+entityID.id+")");
|
||||
print("leaveEntity("+entityID+")");
|
||||
playSound();
|
||||
};
|
||||
})
|
||||
|
|
|
@ -312,7 +312,7 @@
|
|||
this.indicator[i].position,
|
||||
this.indicator[i].scale / 2)) {
|
||||
clickedOnSeat = true;
|
||||
seat.model = this.entityID; // ??
|
||||
seat.model = this.entityID;
|
||||
seat.position = this.indicator[i].position;
|
||||
seat.rotation = this.indicator[i].orientation;
|
||||
}
|
||||
|
@ -333,8 +333,8 @@
|
|||
|
||||
// All callbacks start by updating the properties
|
||||
this.updateProperties = function(entityID) {
|
||||
if (this.entityID === null || !this.entityID.isKnownID) {
|
||||
this.entityID = Entities.identifyEntity(entityID);
|
||||
if (this.entityID === null) {
|
||||
this.entityID = entityID;
|
||||
}
|
||||
this.properties = Entities.getEntityProperties(this.entityID);
|
||||
};
|
||||
|
@ -369,4 +369,4 @@
|
|||
this.updateProperties(entityID); // All callbacks start by updating the properties
|
||||
};
|
||||
|
||||
})
|
||||
})
|
||||
|
|
247
examples/example/brownianFun.js
Normal file
247
examples/example/brownianFun.js
Normal file
|
@ -0,0 +1,247 @@
|
|||
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
var SOUND_PATH = HIFI_PUBLIC_BUCKET + "sounds/Collisions-hitsandslaps/";
|
||||
var soundURLS = ["67LCollision01.wav", "67LCollision02.wav", "airhockey_hit1.wav"];
|
||||
var FLOOR_SIZE = 10;
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(FLOOR_SIZE * 1.5, Quat.getFront(Camera.getOrientation())));
|
||||
var WALL_WIDTH = .1;
|
||||
var FLOOR_HEIGHT_OFFSET = -2;
|
||||
var WALL_HEIGHT = FLOOR_SIZE / 4;
|
||||
var BALL_DROP_HEIGHT = center.y + WALL_HEIGHT;
|
||||
var NUM_BALLS = 50;
|
||||
var BALL_RADIUS = 1;
|
||||
var BROWNIAN_INTERVAL_TIME = 16;
|
||||
var BROWNIAN_FORCE_RANGE = 5;
|
||||
var SPAWN_TIME = 50;
|
||||
var spawnCount = 0;
|
||||
|
||||
var brownianMotionActivated = false;
|
||||
var buttonOffColor = {
|
||||
red: 250,
|
||||
green: 10,
|
||||
blue: 10
|
||||
};
|
||||
var buttonOnColor = {
|
||||
red: 10,
|
||||
green: 200,
|
||||
blue: 100
|
||||
};
|
||||
|
||||
var bounds = {
|
||||
xMin: center.x - FLOOR_SIZE / 2,
|
||||
xMax: center.x + FLOOR_SIZE / 2,
|
||||
zMin: center.z - FLOOR_SIZE / 2,
|
||||
zMax: center.z + FLOOR_SIZE / 2
|
||||
};
|
||||
var balls = [];
|
||||
|
||||
var screenSize = Controller.getViewportDimensions();
|
||||
var BUTTON_SIZE = 32;
|
||||
var PADDING = 3;
|
||||
|
||||
var brownianButton = Overlays.addOverlay("image", {
|
||||
x: screenSize.x / 2 - BUTTON_SIZE,
|
||||
y: screenSize.y - (BUTTON_SIZE + PADDING),
|
||||
width: BUTTON_SIZE,
|
||||
height: BUTTON_SIZE,
|
||||
imageURL: HIFI_PUBLIC_BUCKET + "images/blocks.png",
|
||||
color: buttonOffColor,
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
var floor = Entities.addEntity({
|
||||
type: 'Box',
|
||||
position: Vec3.sum(center, {
|
||||
x: 0,
|
||||
y: FLOOR_HEIGHT_OFFSET,
|
||||
z: 0
|
||||
}),
|
||||
dimensions: {
|
||||
x: FLOOR_SIZE,
|
||||
y: WALL_WIDTH,
|
||||
z: FLOOR_SIZE
|
||||
},
|
||||
color: {
|
||||
red: 100,
|
||||
green: 100,
|
||||
blue: 100
|
||||
}
|
||||
});
|
||||
|
||||
var rightWall = Entities.addEntity({
|
||||
type: 'Box',
|
||||
position: Vec3.sum(center, {
|
||||
x: FLOOR_SIZE / 2,
|
||||
y: FLOOR_HEIGHT_OFFSET / 2,
|
||||
z: 0
|
||||
}),
|
||||
dimensions: {
|
||||
x: WALL_WIDTH,
|
||||
y: WALL_HEIGHT,
|
||||
z: FLOOR_SIZE
|
||||
},
|
||||
color: {
|
||||
red: 120,
|
||||
green: 100,
|
||||
blue: 120
|
||||
}
|
||||
});
|
||||
|
||||
var leftWall = Entities.addEntity({
|
||||
type: 'Box',
|
||||
position: Vec3.sum(center, {
|
||||
x: -FLOOR_SIZE / 2,
|
||||
y: FLOOR_HEIGHT_OFFSET / 2,
|
||||
z: 0
|
||||
}),
|
||||
dimensions: {
|
||||
x: WALL_WIDTH,
|
||||
y: WALL_HEIGHT,
|
||||
z: FLOOR_SIZE
|
||||
},
|
||||
color: {
|
||||
red: 120,
|
||||
green: 100,
|
||||
blue: 120
|
||||
}
|
||||
});
|
||||
|
||||
var backWall = Entities.addEntity({
|
||||
type: 'Box',
|
||||
position: Vec3.sum(center, {
|
||||
x: 0,
|
||||
y: FLOOR_HEIGHT_OFFSET / 2,
|
||||
z: -FLOOR_SIZE / 2,
|
||||
}),
|
||||
dimensions: {
|
||||
x: FLOOR_SIZE,
|
||||
y: WALL_HEIGHT,
|
||||
z: WALL_WIDTH
|
||||
},
|
||||
color: {
|
||||
red: 120,
|
||||
green: 100,
|
||||
blue: 120
|
||||
}
|
||||
});
|
||||
|
||||
var frontWall = Entities.addEntity({
|
||||
type: 'Box',
|
||||
position: Vec3.sum(center, {
|
||||
x: 0,
|
||||
y: FLOOR_HEIGHT_OFFSET / 2,
|
||||
z: FLOOR_SIZE / 2,
|
||||
}),
|
||||
dimensions: {
|
||||
x: FLOOR_SIZE,
|
||||
y: WALL_HEIGHT,
|
||||
z: WALL_WIDTH
|
||||
},
|
||||
color: {
|
||||
red: 120,
|
||||
green: 100,
|
||||
blue: 120
|
||||
}
|
||||
});
|
||||
|
||||
var spawnInterval = Script.setInterval(spawnBalls, SPAWN_TIME);
|
||||
|
||||
function spawnBalls() {
|
||||
balls.push(Entities.addEntity({
|
||||
type: "Sphere",
|
||||
shapeType: "sphere",
|
||||
position: {
|
||||
x: randFloat(bounds.xMin, bounds.xMax),
|
||||
y: BALL_DROP_HEIGHT,
|
||||
z: randFloat(bounds.zMin, bounds.zMax)
|
||||
},
|
||||
dimensions: {
|
||||
x: BALL_RADIUS,
|
||||
y: BALL_RADIUS,
|
||||
z: BALL_RADIUS
|
||||
},
|
||||
color: {
|
||||
red: randFloat(100, 150),
|
||||
green: randFloat(20, 80),
|
||||
blue: randFloat(10, 180)
|
||||
},
|
||||
ignoreCollisions: false,
|
||||
collisionsWillMove: true,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -9.9,
|
||||
z: 0
|
||||
},
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: -.25,
|
||||
z: 0
|
||||
},
|
||||
collisionSoundURL: SOUND_PATH + soundURLS[randInt(0, soundURLS.length - 1)]
|
||||
}));
|
||||
spawnCount++;
|
||||
if (spawnCount === NUM_BALLS) {
|
||||
Script.clearInterval(spawnInterval);
|
||||
}
|
||||
}
|
||||
|
||||
function mousePressEvent(event) {
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({
|
||||
x: event.x,
|
||||
y: event.y
|
||||
});
|
||||
if (clickedOverlay == brownianButton) {
|
||||
brownianMotionActivated = !brownianMotionActivated;
|
||||
if (brownianMotionActivated) {
|
||||
brownianInterval = Script.setInterval(bumpBalls, BROWNIAN_INTERVAL_TIME);
|
||||
Overlays.editOverlay(brownianButton, {
|
||||
color: buttonOnColor
|
||||
})
|
||||
} else {
|
||||
Script.clearInterval(brownianInterval);
|
||||
Overlays.editOverlay(brownianButton, {
|
||||
color: buttonOffColor
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bumpBalls() {
|
||||
balls.forEach(function(ball) {
|
||||
var props = Entities.getEntityProperties(ball);
|
||||
var newVelocity = Vec3.sum(props.velocity, {
|
||||
x: (Math.random() - 0.5) * BROWNIAN_FORCE_RANGE,
|
||||
y: 0,
|
||||
z: (Math.random() - 0.5) * BROWNIAN_FORCE_RANGE
|
||||
});
|
||||
Entities.editEntity(ball, {
|
||||
velocity: newVelocity
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(floor);
|
||||
Entities.deleteEntity(rightWall);
|
||||
Entities.deleteEntity(leftWall);
|
||||
Entities.deleteEntity(backWall);
|
||||
Entities.deleteEntity(frontWall);
|
||||
balls.forEach(function(ball) {
|
||||
Entities.deleteEntity(ball);
|
||||
});
|
||||
Overlays.deleteOverlay(brownianButton);
|
||||
}
|
||||
|
||||
function randFloat(low, high) {
|
||||
return Math.floor(low + Math.random() * (high - low));
|
||||
}
|
||||
|
||||
|
||||
function randInt(low, high) {
|
||||
return Math.floor(randFloat(low, high));
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
89
examples/example/dynamicLandscape.js
Normal file
89
examples/example/dynamicLandscape.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
|
||||
// dynamicLandscape.js
|
||||
// examples
|
||||
//
|
||||
// Created by Eric Levin on June 8
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Meditative ocean landscape
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
Script.include(HIFI_PUBLIC_BUCKET + 'scripts/utilities.js')
|
||||
|
||||
var NUM_ROWS = 10;
|
||||
var CUBE_SIZE = 1;
|
||||
var cubes = [];
|
||||
var cubesSettings = [];
|
||||
var time = 0;
|
||||
|
||||
var OMEGA = 2.0 * Math.PI/8;
|
||||
var RANGE = CUBE_SIZE/2;
|
||||
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(CUBE_SIZE* 10, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
|
||||
for (var x = 0, rowIndex = 0; x < NUM_ROWS * CUBE_SIZE; x += CUBE_SIZE, rowIndex++) {
|
||||
for (var z = 0, columnIndex = 0; z < NUM_ROWS * CUBE_SIZE; z += CUBE_SIZE, columnIndex++) {
|
||||
|
||||
var baseHeight = map( columnIndex + 1, 1, NUM_ROWS, -CUBE_SIZE * 2, -CUBE_SIZE);
|
||||
var relativePosition = {
|
||||
x: x,
|
||||
y: baseHeight,
|
||||
z: z
|
||||
};
|
||||
var position = Vec3.sum(center, relativePosition);
|
||||
cubes.push(Entities.addEntity({
|
||||
type: 'Box',
|
||||
position: position,
|
||||
dimensions: {
|
||||
x: CUBE_SIZE,
|
||||
y: CUBE_SIZE,
|
||||
z: CUBE_SIZE
|
||||
}
|
||||
}));
|
||||
|
||||
var phase = map( (columnIndex + 1) * (rowIndex + 1), 2, NUM_ROWS * NUM_ROWS, Math.PI * 2, Math.PI * 4);
|
||||
cubesSettings.push({
|
||||
baseHeight: center.y + baseHeight,
|
||||
phase: phase
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function update(deleteTime) {
|
||||
time += deleteTime;
|
||||
for (var i = 0; i < cubes.length; i++) {
|
||||
var phase = cubesSettings[i].phase;
|
||||
var props = Entities.getEntityProperties(cubes[i]);
|
||||
var newHeight = Math.sin(time * OMEGA + phase) / 2.0;
|
||||
var hue = map(newHeight, -.5, .5, 0.5, 0.7);
|
||||
var light = map(newHeight, -.5, .5, 0.4, 0.6)
|
||||
newHeight = cubesSettings[i].baseHeight + (newHeight * RANGE);
|
||||
var newVelocityY = Math.cos(time * OMEGA + phase) / 2.0 * RANGE * OMEGA;
|
||||
|
||||
var newPosition = props.position;
|
||||
var newVelocity = props.velocity;
|
||||
|
||||
newPosition.y = newHeight;
|
||||
newVelocity = newVelocityY;
|
||||
Entities.editEntity( cubes[i], {
|
||||
position: newPosition,
|
||||
velocity: props.velocity,
|
||||
color: hslToRgb({hue: hue, sat: 0.7, light: light})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
cubes.forEach(function(cube) {
|
||||
Entities.deleteEntity(cube);
|
||||
})
|
||||
}
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(cleanup)
|
||||
|
||||
|
|
@ -42,7 +42,6 @@ var originalProperties = {
|
|||
};
|
||||
|
||||
var modelID = Entities.addEntity(originalProperties);
|
||||
print("Entities.addEntity()... modelID.creatorTokenID = " + modelID.creatorTokenID);
|
||||
|
||||
var isPlaying = true;
|
||||
var playPauseEveryWhile = 360;
|
||||
|
@ -98,8 +97,6 @@ function moveModel(deltaTime) {
|
|||
|
||||
count++;
|
||||
|
||||
//print("modelID.creatorTokenID = " + modelID.creatorTokenID);
|
||||
|
||||
if (somethingChanged) {
|
||||
var newProperties = {
|
||||
animationIsPlaying: isPlaying,
|
||||
|
@ -123,7 +120,7 @@ Script.update.connect(moveModel);
|
|||
|
||||
Script.scriptEnding.connect(function () {
|
||||
print("cleaning up...");
|
||||
print("modelID="+ modelID.creatorTokenID + ", id:" + modelID.id);
|
||||
print("modelID=" + modelID);
|
||||
Models.deleteModel(modelID);
|
||||
});
|
||||
|
||||
|
|
|
@ -106,9 +106,6 @@ function updateButterflies(deltaTime) {
|
|||
var CHANCE_OF_IMPULSE = 0.04;
|
||||
for (var i = 0; i < numButterflies; i++) {
|
||||
if (Math.random() < CHANCE_OF_IMPULSE) {
|
||||
if (!butterflies[i].isKnownID) {
|
||||
butterflies[i] = Entities.identifyEntity(butterflies[i]);
|
||||
}
|
||||
var properties = Entities.getEntityProperties(butterflies[i]);
|
||||
if (Vec3.length(Vec3.subtract(properties.position, flockPosition)) > range) {
|
||||
Entities.editEntity(butterflies[i], { position: flockPosition } );
|
||||
|
|
71
examples/example/entities/changingAtmosphereExample.js
Normal file
71
examples/example/entities/changingAtmosphereExample.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// changingAtmosphereExample.js
|
||||
// examples
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 4/16/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example script that demonstrates creating a zone using the atmosphere features that changes scatter properties
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
var count = 0;
|
||||
var stopAfter = 10000;
|
||||
|
||||
var zoneEntityA = Entities.addEntity({
|
||||
type: "Zone",
|
||||
position: { x: 1000, y: 1000, z: 1000},
|
||||
dimensions: { x: 2000, y: 2000, z: 2000 },
|
||||
keyLightColor: { red: 255, green: 0, blue: 0 },
|
||||
stageSunModelEnabled: false,
|
||||
shapeType: "sphere",
|
||||
backgroundMode: "atmosphere",
|
||||
atmosphere: {
|
||||
center: { x: 1000, y: 0, z: 1000},
|
||||
innerRadius: 1000.0,
|
||||
outerRadius: 1025.0,
|
||||
rayleighScattering: 0.0025, // Meaningful values 0 to ~0.01
|
||||
mieScattering: 0.0010, // Meaningful values 0 to ~0.01
|
||||
|
||||
// First two, Meaningful values 0 to 1 each, blue, purple; third meaningful 0.3 to 1 - affects shape
|
||||
scatteringWavelengths: { x: 0.650, y: 0.570, z: 0.475 },
|
||||
hasStars: true
|
||||
},
|
||||
stage: {
|
||||
latitude: 37.777,
|
||||
longitude: 122.407,
|
||||
altitude: 0.03,
|
||||
day: 183,
|
||||
hour: 5,
|
||||
sunModelEnabled: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.update.connect(function(deltaTime) {
|
||||
// stop it...
|
||||
if (count >= stopAfter) {
|
||||
print("calling Script.stop()");
|
||||
Script.stop();
|
||||
}
|
||||
count++;
|
||||
var rayleighScattering = (count / 100000) % 0.01;
|
||||
var mieScattering = (count / 100000) % 0.01;
|
||||
var waveX = (count / 2000) % 1;
|
||||
var waveZ = ((count / 2000) % 0.7) + 0.3;
|
||||
|
||||
Entities.editEntity(zoneEntityA, {
|
||||
atmosphere: {
|
||||
rayleighScattering: rayleighScattering,
|
||||
mieScattering: mieScattering,
|
||||
scatteringWavelengths: { x: waveX, y: waveX, z: waveZ }
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue