mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 11:48:09 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into tablet-imporvments
This commit is contained in:
commit
6685fbbfc4
54 changed files with 943 additions and 852 deletions
|
@ -11,6 +11,35 @@
|
||||||
|
|
||||||
include(BundleUtilities)
|
include(BundleUtilities)
|
||||||
|
|
||||||
|
# replace copy_resolved_item_into_bundle
|
||||||
|
#
|
||||||
|
# The official version of copy_resolved_item_into_bundle will print out a "warning:" when
|
||||||
|
# the resolved item matches the resolved embedded item. This not not really an issue that
|
||||||
|
# should rise to the level of a "warning" so we replace this message with a "status:"
|
||||||
|
#
|
||||||
|
# Source: https://github.com/jherico/OculusMinimalExample/blob/master/cmake/templates/FixupBundlePostBuild.cmake.in
|
||||||
|
#
|
||||||
|
function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item)
|
||||||
|
if (WIN32)
|
||||||
|
# ignore case on Windows
|
||||||
|
string(TOLOWER "${resolved_item}" resolved_item_compare)
|
||||||
|
string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare)
|
||||||
|
else()
|
||||||
|
set(resolved_item_compare "${resolved_item}")
|
||||||
|
set(resolved_embedded_item_compare "${resolved_embedded_item}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if ("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}")
|
||||||
|
# this is our only change from the original version
|
||||||
|
message(STATUS "status: resolved_item == resolved_embedded_item - not copying...")
|
||||||
|
else()
|
||||||
|
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}")
|
||||||
|
if (UNIX AND NOT APPLE)
|
||||||
|
file(RPATH_REMOVE FILE "${resolved_embedded_item}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
function(gp_resolved_file_type_override resolved_file type_var)
|
function(gp_resolved_file_type_override resolved_file type_var)
|
||||||
if( file MATCHES ".*VCRUNTIME140.*" )
|
if( file MATCHES ".*VCRUNTIME140.*" )
|
||||||
set(type "system" PARENT_SCOPE)
|
set(type "system" PARENT_SCOPE)
|
||||||
|
|
|
@ -34,8 +34,9 @@ static const chrono::minutes MAX_REFRESH_TIME { 5 };
|
||||||
Q_DECLARE_LOGGING_CATEGORY(asset_backup)
|
Q_DECLARE_LOGGING_CATEGORY(asset_backup)
|
||||||
Q_LOGGING_CATEGORY(asset_backup, "hifi.asset-backup");
|
Q_LOGGING_CATEGORY(asset_backup, "hifi.asset-backup");
|
||||||
|
|
||||||
AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) :
|
AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory, bool assetServerEnabled) :
|
||||||
_assetsDirectory(backupDirectory + ASSETS_DIR)
|
_assetsDirectory(backupDirectory + ASSETS_DIR),
|
||||||
|
_assetServerEnabled(assetServerEnabled)
|
||||||
{
|
{
|
||||||
// Make sure the asset directory exists.
|
// Make sure the asset directory exists.
|
||||||
QDir(_assetsDirectory).mkpath(".");
|
QDir(_assetsDirectory).mkpath(".");
|
||||||
|
@ -53,6 +54,7 @@ void AssetsBackupHandler::setupRefreshTimer() {
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
QObject::connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, [this](SharedNodePointer node) {
|
QObject::connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, [this](SharedNodePointer node) {
|
||||||
if (node->getType() == NodeType::AssetServer) {
|
if (node->getType() == NodeType::AssetServer) {
|
||||||
|
assert(_assetServerEnabled);
|
||||||
// run immediately for the first time.
|
// run immediately for the first time.
|
||||||
_mappingsRefreshTimer.start(0);
|
_mappingsRefreshTimer.start(0);
|
||||||
}
|
}
|
||||||
|
@ -233,12 +235,12 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_lastMappingsRefresh.time_since_epoch().count() == 0) {
|
if (_assetServerEnabled && _lastMappingsRefresh.time_since_epoch().count() == 0) {
|
||||||
qCWarning(asset_backup) << "Current mappings not yet loaded.";
|
qCWarning(asset_backup) << "Current mappings not yet loaded.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) {
|
if (_assetServerEnabled && (p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) {
|
||||||
qCWarning(asset_backup) << "Backing up asset mappings that might be stale.";
|
qCWarning(asset_backup) << "Backing up asset mappings that might be stale.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ class AssetsBackupHandler : public QObject, public BackupHandlerInterface {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AssetsBackupHandler(const QString& backupDirectory);
|
AssetsBackupHandler(const QString& backupDirectory, bool assetServerEnabled);
|
||||||
|
|
||||||
std::pair<bool, float> isAvailable(const QString& backupName) override;
|
std::pair<bool, float> isAvailable(const QString& backupName) override;
|
||||||
std::pair<bool, float> getRecoveryStatus() override;
|
std::pair<bool, float> getRecoveryStatus() override;
|
||||||
|
@ -65,6 +65,7 @@ private:
|
||||||
void updateMappings();
|
void updateMappings();
|
||||||
|
|
||||||
QString _assetsDirectory;
|
QString _assetsDirectory;
|
||||||
|
bool _assetServerEnabled { false };
|
||||||
|
|
||||||
QTimer _mappingsRefreshTimer;
|
QTimer _mappingsRefreshTimer;
|
||||||
p_high_resolution_clock::time_point _lastMappingsRefresh;
|
p_high_resolution_clock::time_point _lastMappingsRefresh;
|
||||||
|
|
|
@ -307,7 +307,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
|
|
||||||
connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){
|
connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){
|
||||||
_contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath())));
|
_contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath())));
|
||||||
_contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir())));
|
_contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir(), isAssetServerEnabled())));
|
||||||
_contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager)));
|
_contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -991,15 +991,11 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
||||||
defaultedType = static_cast<Assignment::Type>(static_cast<int>(defaultedType) + 1)) {
|
defaultedType = static_cast<Assignment::Type>(static_cast<int>(defaultedType) + 1)) {
|
||||||
if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) {
|
if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) {
|
||||||
|
|
||||||
if (defaultedType == Assignment::AssetServerType) {
|
// Make sure the asset-server is enabled before adding it here.
|
||||||
// Make sure the asset-server is enabled before adding it here.
|
// Initially we do not assign it by default so we can test it in HF domains first
|
||||||
// Initially we do not assign it by default so we can test it in HF domains first
|
if (defaultedType == Assignment::AssetServerType && !isAssetServerEnabled()) {
|
||||||
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
|
// skip to the next iteraion if asset-server isn't enabled
|
||||||
|
continue;
|
||||||
if (!_settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool()) {
|
|
||||||
// skip to the next iteration if asset-server isn't enabled
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// type has not been set from a command line or config file config, use the default
|
// type has not been set from a command line or config file config, use the default
|
||||||
|
@ -2946,6 +2942,12 @@ bool DomainServer::shouldReplicateNode(const Node& node) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bool DomainServer::isAssetServerEnabled() {
|
||||||
|
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
|
||||||
|
return _settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServer::nodeAdded(SharedNodePointer node) {
|
void DomainServer::nodeAdded(SharedNodePointer node) {
|
||||||
// we don't use updateNodeWithData, so add the DomainServerNodeData to the node here
|
// we don't use updateNodeWithData, so add the DomainServerNodeData to the node here
|
||||||
node->setLinkedData(std::unique_ptr<DomainServerNodeData> { new DomainServerNodeData() });
|
node->setLinkedData(std::unique_ptr<DomainServerNodeData> { new DomainServerNodeData() });
|
||||||
|
|
|
@ -72,6 +72,8 @@ public:
|
||||||
|
|
||||||
static const QString REPLACEMENT_FILE_EXTENSION;
|
static const QString REPLACEMENT_FILE_EXTENSION;
|
||||||
|
|
||||||
|
bool isAssetServerEnabled();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// Called by NodeList to inform us a node has been added
|
/// Called by NodeList to inform us a node has been added
|
||||||
void nodeAdded(SharedNodePointer node);
|
void nodeAdded(SharedNodePointer node);
|
||||||
|
|
|
@ -165,11 +165,11 @@ TextField {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
||||||
Binding on anchors.right {
|
Binding on anchors.right {
|
||||||
when: parent.right
|
when: textField.right
|
||||||
value: parent.right
|
value: textField.right
|
||||||
}
|
}
|
||||||
Binding on wrapMode {
|
Binding on wrapMode {
|
||||||
when: parent.right
|
when: textField.right
|
||||||
value: Text.WordWrap
|
value: Text.WordWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,11 @@ import QtQuick 2.5
|
||||||
import QtGraphicalEffects 1.0
|
import QtGraphicalEffects 1.0
|
||||||
import "toolbars"
|
import "toolbars"
|
||||||
import "../styles-uit"
|
import "../styles-uit"
|
||||||
|
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root;
|
id: root;
|
||||||
visible: false;
|
visible: !!suggestions.count;
|
||||||
|
|
||||||
property int cardWidth: 212;
|
property int cardWidth: 212;
|
||||||
property int cardHeight: 152;
|
property int cardHeight: 152;
|
||||||
|
@ -32,21 +33,37 @@ Column {
|
||||||
property int stackedCardShadowHeight: 4;
|
property int stackedCardShadowHeight: 4;
|
||||||
property int labelSize: 20;
|
property int labelSize: 20;
|
||||||
|
|
||||||
property string metaverseServerUrl: '';
|
|
||||||
property string protocol: '';
|
property string protocol: '';
|
||||||
property string actions: 'snapshot';
|
property string actions: 'snapshot';
|
||||||
// sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick.
|
// sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick.
|
||||||
property string labelText: actions;
|
property string labelText: actions;
|
||||||
property string filter: '';
|
property string filter: '';
|
||||||
onFilterChanged: filterChoicesByText();
|
|
||||||
property var goFunction: null;
|
property var goFunction: null;
|
||||||
property var rpc: null;
|
property var http: null;
|
||||||
|
|
||||||
HifiConstants { id: hifi }
|
HifiConstants { id: hifi }
|
||||||
ListModel { id: suggestions; }
|
Component.onCompleted: suggestions.getFirstPage();
|
||||||
|
HifiModels.PSFListModel {
|
||||||
|
id: suggestions;
|
||||||
|
http: root.http;
|
||||||
|
property var options: [
|
||||||
|
'include_actions=' + actions,
|
||||||
|
'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
|
||||||
|
'require_online=true',
|
||||||
|
'protocol=' + encodeURIComponent(Window.protocolSignature())
|
||||||
|
];
|
||||||
|
endpoint: '/api/v1/user_stories?' + options.join('&');
|
||||||
|
itemsPerPage: 3;
|
||||||
|
processPage: function (data) {
|
||||||
|
return data.user_stories.map(makeModelData);
|
||||||
|
};
|
||||||
|
listModelName: actions;
|
||||||
|
listView: scroll;
|
||||||
|
searchFilter: filter;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveUrl(url) {
|
function resolveUrl(url) {
|
||||||
return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url;
|
return (url.indexOf('/') === 0) ? (Account.metaverseServerURL + url) : url;
|
||||||
}
|
}
|
||||||
function makeModelData(data) { // create a new obj from data
|
function makeModelData(data) { // create a new obj from data
|
||||||
// ListModel elements will only ever have those properties that are defined by the first obj that is added.
|
// ListModel elements will only ever have those properties that are defined by the first obj that is added.
|
||||||
|
@ -55,16 +72,11 @@ Column {
|
||||||
tags = data.tags || [data.action, data.username],
|
tags = data.tags || [data.action, data.username],
|
||||||
description = data.description || "",
|
description = data.description || "",
|
||||||
thumbnail_url = data.thumbnail_url || "";
|
thumbnail_url = data.thumbnail_url || "";
|
||||||
if (actions === 'concurrency,snapshot') {
|
|
||||||
// A temporary hack for simulating announcements. We won't use this in production, but if requested, we'll use this data like announcements.
|
|
||||||
data.details.connections = 4;
|
|
||||||
data.action = 'announcement';
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
place_name: name,
|
place_name: name,
|
||||||
username: data.username || "",
|
username: data.username || "",
|
||||||
path: data.path || "",
|
path: data.path || "",
|
||||||
created_at: data.created_at || "",
|
created_at: data.created_at || data.updated_at || "", // FIXME why aren't we getting created_at?
|
||||||
action: data.action || "",
|
action: data.action || "",
|
||||||
thumbnail_url: resolveUrl(thumbnail_url),
|
thumbnail_url: resolveUrl(thumbnail_url),
|
||||||
image_url: resolveUrl(data.details && data.details.image_url),
|
image_url: resolveUrl(data.details && data.details.image_url),
|
||||||
|
@ -74,125 +86,9 @@ Column {
|
||||||
tags: tags,
|
tags: tags,
|
||||||
description: description,
|
description: description,
|
||||||
online_users: data.details.connections || data.details.concurrency || 0,
|
online_users: data.details.connections || data.details.concurrency || 0,
|
||||||
drillDownToPlace: false,
|
drillDownToPlace: false
|
||||||
|
|
||||||
searchText: [name].concat(tags, description || []).join(' ').toUpperCase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
property var allStories: [];
|
|
||||||
property var placeMap: ({}); // Used for making stacks.
|
|
||||||
property int requestId: 0;
|
|
||||||
function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey
|
|
||||||
if (!error && (data.status === 'success')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!error) { // Create a message from the data
|
|
||||||
error = data.status + ': ' + data.error;
|
|
||||||
}
|
|
||||||
if (typeof(error) === 'string') { // Make a proper Error object
|
|
||||||
error = new Error(error);
|
|
||||||
}
|
|
||||||
error.message += ' in ' + url; // Include the url.
|
|
||||||
cb(error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
function getUserStoryPage(pageNumber, cb, cb1) { // cb(error) after all pages of domain data have been added to model
|
|
||||||
// If supplied, cb1 will be run after the first page IFF it is not the last, for responsiveness.
|
|
||||||
var options = [
|
|
||||||
'now=' + new Date().toISOString(),
|
|
||||||
'include_actions=' + actions,
|
|
||||||
'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
|
|
||||||
'require_online=true',
|
|
||||||
'protocol=' + protocol,
|
|
||||||
'page=' + pageNumber
|
|
||||||
];
|
|
||||||
var url = metaverseBase + 'user_stories?' + options.join('&');
|
|
||||||
var thisRequestId = ++requestId;
|
|
||||||
rpc('request', url, function (error, data) {
|
|
||||||
if (thisRequestId !== requestId) {
|
|
||||||
error = 'stale';
|
|
||||||
}
|
|
||||||
if (handleError(url, error, data, cb)) {
|
|
||||||
return; // abandon stale requests
|
|
||||||
}
|
|
||||||
allStories = allStories.concat(data.user_stories.map(makeModelData));
|
|
||||||
if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now
|
|
||||||
if ((pageNumber === 1) && cb1) {
|
|
||||||
cb1();
|
|
||||||
}
|
|
||||||
return getUserStoryPage(pageNumber + 1, cb);
|
|
||||||
}
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function fillDestinations() { // Public
|
|
||||||
console.debug('Feed::fillDestinations()')
|
|
||||||
|
|
||||||
function report(label, error) {
|
|
||||||
console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count);
|
|
||||||
}
|
|
||||||
var filter = makeFilteredStoryProcessor(), counter = 0;
|
|
||||||
allStories = [];
|
|
||||||
suggestions.clear();
|
|
||||||
placeMap = {};
|
|
||||||
getUserStoryPage(1, function (error) {
|
|
||||||
allStories.slice(counter).forEach(filter);
|
|
||||||
report('user stories update', error);
|
|
||||||
root.visible = !!suggestions.count;
|
|
||||||
}, function () { // If there's more than a page, put what we have in the model right away, keeping track of how many are processed.
|
|
||||||
allStories.forEach(function (story) {
|
|
||||||
counter++;
|
|
||||||
filter(story);
|
|
||||||
root.visible = !!suggestions.count;
|
|
||||||
});
|
|
||||||
report('user stories');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function identity(x) {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches
|
|
||||||
var words = filter.toUpperCase().split(/\s+/).filter(identity);
|
|
||||||
function suggestable(story) {
|
|
||||||
// We could filter out places we don't want to suggest, such as those where (story.place_name === AddressManager.placename) or (story.username === Account.username).
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
function matches(story) {
|
|
||||||
if (!words.length) {
|
|
||||||
return suggestable(story);
|
|
||||||
}
|
|
||||||
return words.every(function (word) {
|
|
||||||
return story.searchText.indexOf(word) >= 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function addToSuggestions(place) {
|
|
||||||
var collapse = ((actions === 'concurrency,snapshot') && (place.action !== 'concurrency')) || (place.action === 'announcement');
|
|
||||||
if (collapse) {
|
|
||||||
var existing = placeMap[place.place_name];
|
|
||||||
if (existing) {
|
|
||||||
existing.drillDownToPlace = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
suggestions.append(place);
|
|
||||||
if (collapse) {
|
|
||||||
placeMap[place.place_name] = suggestions.get(suggestions.count - 1);
|
|
||||||
} else if (place.action === 'concurrency') {
|
|
||||||
suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories).
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return function (story) {
|
|
||||||
if (matches(story)) {
|
|
||||||
addToSuggestions(story);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function filterChoicesByText() {
|
|
||||||
suggestions.clear();
|
|
||||||
placeMap = {};
|
|
||||||
allStories.forEach(makeFilteredStoryProcessor());
|
|
||||||
root.visible = !!suggestions.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
RalewayBold {
|
RalewayBold {
|
||||||
id: label;
|
id: label;
|
||||||
|
@ -208,6 +104,7 @@ Column {
|
||||||
highlightMoveDuration: -1;
|
highlightMoveDuration: -1;
|
||||||
highlightMoveVelocity: -1;
|
highlightMoveVelocity: -1;
|
||||||
currentIndex: -1;
|
currentIndex: -1;
|
||||||
|
onAtXEndChanged: { if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } }
|
||||||
|
|
||||||
spacing: 12;
|
spacing: 12;
|
||||||
width: parent.width;
|
width: parent.width;
|
||||||
|
@ -239,21 +136,4 @@ Column {
|
||||||
unhoverThunk: function () { hovered = false }
|
unhoverThunk: function () { hovered = false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NumberAnimation {
|
|
||||||
id: anim;
|
|
||||||
target: scroll;
|
|
||||||
property: "contentX";
|
|
||||||
duration: 250;
|
|
||||||
}
|
|
||||||
function scrollToIndex(index) {
|
|
||||||
anim.running = false;
|
|
||||||
var pos = scroll.contentX;
|
|
||||||
var destPos;
|
|
||||||
scroll.positionViewAtIndex(index, ListView.Contain);
|
|
||||||
destPos = scroll.contentX;
|
|
||||||
anim.from = pos;
|
|
||||||
anim.to = destPos;
|
|
||||||
scroll.currentIndex = index;
|
|
||||||
anim.running = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import Qt.labs.settings 1.0
|
||||||
import "../styles-uit"
|
import "../styles-uit"
|
||||||
import "../controls-uit" as HifiControlsUit
|
import "../controls-uit" as HifiControlsUit
|
||||||
import "../controls" as HifiControls
|
import "../controls" as HifiControls
|
||||||
|
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
|
||||||
|
|
||||||
// references HMD, Users, UserActivityLogger from root context
|
// references HMD, Users, UserActivityLogger from root context
|
||||||
|
|
||||||
|
@ -37,13 +38,42 @@ Rectangle {
|
||||||
property var myData: ({profileUrl: "", displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true, placeName: "", connection: "", isPresent: true}); // valid dummy until set
|
property var myData: ({profileUrl: "", displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true, placeName: "", connection: "", isPresent: true}); // valid dummy until set
|
||||||
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
|
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
|
||||||
property var nearbyUserModelData: []; // This simple list is essentially a mirror of the nearbyUserModel listModel without all the extra complexities.
|
property var nearbyUserModelData: []; // This simple list is essentially a mirror of the nearbyUserModel listModel without all the extra complexities.
|
||||||
property var connectionsUserModelData: []; // This simple list is essentially a mirror of the connectionsUserModel listModel without all the extra complexities.
|
|
||||||
property bool iAmAdmin: false;
|
property bool iAmAdmin: false;
|
||||||
property var activeTab: "nearbyTab";
|
property var activeTab: "nearbyTab";
|
||||||
property bool currentlyEditingDisplayName: false
|
property bool currentlyEditingDisplayName: false
|
||||||
property bool punctuationMode: false;
|
property bool punctuationMode: false;
|
||||||
|
|
||||||
HifiConstants { id: hifi; }
|
HifiConstants { id: hifi; }
|
||||||
|
RootHttpRequest { id: http; }
|
||||||
|
HifiModels.PSFListModel {
|
||||||
|
id: connectionsUserModel;
|
||||||
|
http: http;
|
||||||
|
endpoint: "/api/v1/users?filter=connections";
|
||||||
|
property var sortColumn: connectionsTable.getColumn(connectionsTable.sortIndicatorColumn);
|
||||||
|
sortProperty: switch (sortColumn && sortColumn.role) {
|
||||||
|
case 'placeName':
|
||||||
|
'location';
|
||||||
|
break;
|
||||||
|
case 'connection':
|
||||||
|
'is_friend';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
'username';
|
||||||
|
}
|
||||||
|
sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder;
|
||||||
|
itemsPerPage: 9;
|
||||||
|
listView: connectionsTable;
|
||||||
|
processPage: function (data) {
|
||||||
|
return data.users.map(function (user) {
|
||||||
|
return {
|
||||||
|
userName: user.username,
|
||||||
|
connection: user.connection,
|
||||||
|
profileUrl: user.images.thumbnail,
|
||||||
|
placeName: (user.location.root || user.location.domain || {}).name || ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// The letterbox used for popup messages
|
// The letterbox used for popup messages
|
||||||
LetterboxMessage {
|
LetterboxMessage {
|
||||||
|
@ -106,16 +136,6 @@ Rectangle {
|
||||||
});
|
});
|
||||||
return sessionIDs;
|
return sessionIDs;
|
||||||
}
|
}
|
||||||
function getSelectedConnectionsUserNames() {
|
|
||||||
var userNames = [];
|
|
||||||
connectionsTable.selection.forEach(function (userIndex) {
|
|
||||||
var datum = connectionsUserModelData[userIndex];
|
|
||||||
if (datum) {
|
|
||||||
userNames.push(datum.userName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return userNames;
|
|
||||||
}
|
|
||||||
function refreshNearbyWithFilter() {
|
function refreshNearbyWithFilter() {
|
||||||
// We should just be able to set settings.filtered to inViewCheckbox.checked, but see #3249, so send to .js for saving.
|
// We should just be able to set settings.filtered to inViewCheckbox.checked, but see #3249, so send to .js for saving.
|
||||||
var userIds = getSelectedNearbySessionIDs();
|
var userIds = getSelectedNearbySessionIDs();
|
||||||
|
@ -232,9 +252,7 @@ Rectangle {
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (activeTab != "connectionsTab") {
|
if (activeTab != "connectionsTab") {
|
||||||
connectionsLoading.visible = false;
|
connectionsUserModel.getFirstPage();
|
||||||
connectionsLoading.visible = true;
|
|
||||||
pal.sendToScript({method: 'refreshConnections'});
|
|
||||||
}
|
}
|
||||||
activeTab = "connectionsTab";
|
activeTab = "connectionsTab";
|
||||||
connectionsHelpText.color = hifi.colors.blueAccent;
|
connectionsHelpText.color = hifi.colors.blueAccent;
|
||||||
|
@ -258,11 +276,7 @@ Rectangle {
|
||||||
id: reloadConnections;
|
id: reloadConnections;
|
||||||
width: reloadConnections.height;
|
width: reloadConnections.height;
|
||||||
glyph: hifi.glyphs.reload;
|
glyph: hifi.glyphs.reload;
|
||||||
onClicked: {
|
onClicked: connectionsUserModel.getFirstPage('delayRefresh');
|
||||||
connectionsLoading.visible = false;
|
|
||||||
connectionsLoading.visible = true;
|
|
||||||
pal.sendToScript({method: 'refreshConnections'});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// "CONNECTIONS" text
|
// "CONNECTIONS" text
|
||||||
|
@ -472,7 +486,7 @@ Rectangle {
|
||||||
visible: !isCheckBox && !isButton && !isAvgAudio;
|
visible: !isCheckBox && !isButton && !isAvgAudio;
|
||||||
uuid: model ? model.sessionId : "";
|
uuid: model ? model.sessionId : "";
|
||||||
selected: styleData.selected;
|
selected: styleData.selected;
|
||||||
isReplicated: model.isReplicated;
|
isReplicated: model && model.isReplicated;
|
||||||
isAdmin: model && model.admin;
|
isAdmin: model && model.admin;
|
||||||
isPresent: model && model.isPresent;
|
isPresent: model && model.isPresent;
|
||||||
// Size
|
// Size
|
||||||
|
@ -702,7 +716,7 @@ Rectangle {
|
||||||
anchors.top: parent.top;
|
anchors.top: parent.top;
|
||||||
anchors.topMargin: 185;
|
anchors.topMargin: 185;
|
||||||
anchors.horizontalCenter: parent.horizontalCenter;
|
anchors.horizontalCenter: parent.horizontalCenter;
|
||||||
visible: true;
|
visible: !connectionsUserModel.retrievedAtLeastOnePage;
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
connectionsTimeoutTimer.start();
|
connectionsTimeoutTimer.start();
|
||||||
|
@ -747,14 +761,6 @@ Rectangle {
|
||||||
headerVisible: true;
|
headerVisible: true;
|
||||||
sortIndicatorColumn: settings.connectionsSortIndicatorColumn;
|
sortIndicatorColumn: settings.connectionsSortIndicatorColumn;
|
||||||
sortIndicatorOrder: settings.connectionsSortIndicatorOrder;
|
sortIndicatorOrder: settings.connectionsSortIndicatorOrder;
|
||||||
onSortIndicatorColumnChanged: {
|
|
||||||
settings.connectionsSortIndicatorColumn = sortIndicatorColumn;
|
|
||||||
sortConnectionsModel();
|
|
||||||
}
|
|
||||||
onSortIndicatorOrderChanged: {
|
|
||||||
settings.connectionsSortIndicatorOrder = sortIndicatorOrder;
|
|
||||||
sortConnectionsModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
TableViewColumn {
|
TableViewColumn {
|
||||||
id: connectionsUserNameHeader;
|
id: connectionsUserNameHeader;
|
||||||
|
@ -779,8 +785,14 @@ Rectangle {
|
||||||
resizable: false;
|
resizable: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
model: ListModel {
|
model: connectionsUserModel;
|
||||||
id: connectionsUserModel;
|
Connections {
|
||||||
|
target: connectionsTable.flickableItem;
|
||||||
|
onAtYEndChanged: {
|
||||||
|
if (connectionsTable.flickableItem.atYEnd && !connectionsTable.flickableItem.atYBeginning) {
|
||||||
|
connectionsUserModel.getNextPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This Rectangle refers to each Row in the connectionsTable.
|
// This Rectangle refers to each Row in the connectionsTable.
|
||||||
|
@ -859,12 +871,9 @@ Rectangle {
|
||||||
checked: model && (model.connection === "friend");
|
checked: model && (model.connection === "friend");
|
||||||
boxSize: 24;
|
boxSize: 24;
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var newValue = model.connection !== "friend";
|
pal.sendToScript({method: checked ? 'addFriend' : 'removeFriend', params: model.userName});
|
||||||
connectionsUserModel.setProperty(model.userIndex, styleData.role, (newValue ? "friend" : "connection"));
|
|
||||||
connectionsUserModelData[model.userIndex][styleData.role] = newValue; // Defensive programming
|
|
||||||
pal.sendToScript({method: newValue ? 'addFriend' : 'removeFriend', params: model.userName});
|
|
||||||
|
|
||||||
UserActivityLogger["palAction"](newValue ? styleData.role : "un-" + styleData.role, model.sessionId);
|
UserActivityLogger["palAction"](checked ? styleData.role : "un-" + styleData.role, model.sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1130,16 +1139,6 @@ Rectangle {
|
||||||
sortModel();
|
sortModel();
|
||||||
reloadNearby.color = 0;
|
reloadNearby.color = 0;
|
||||||
break;
|
break;
|
||||||
case 'connections':
|
|
||||||
var data = message.params;
|
|
||||||
if (pal.debug) {
|
|
||||||
console.log('Got connection data: ', JSON.stringify(data));
|
|
||||||
}
|
|
||||||
connectionsUserModelData = data;
|
|
||||||
sortConnectionsModel();
|
|
||||||
connectionsLoading.visible = false;
|
|
||||||
connectionsRefreshProblemText.visible = false;
|
|
||||||
break;
|
|
||||||
case 'select':
|
case 'select':
|
||||||
var sessionIds = message.params[0];
|
var sessionIds = message.params[0];
|
||||||
var selected = message.params[1];
|
var selected = message.params[1];
|
||||||
|
@ -1239,6 +1238,14 @@ Rectangle {
|
||||||
reloadNearby.color = 2;
|
reloadNearby.color = 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'inspectionCertificate_resetCert':
|
||||||
|
// marketplaces.js sends out a signal to QML with that method when the tablet screen changes and it's not changed to a commerce-related screen.
|
||||||
|
// We want it to only be handled by the InspectionCertificate.qml, but there's not an easy way of doing that.
|
||||||
|
// As a part of a "cleanup inspectionCertificate_resetCert" ticket, we'll have to figure out less logspammy way of doing what has to be done.
|
||||||
|
break;
|
||||||
|
case 'http.response':
|
||||||
|
http.handleHttpResponse(message);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Unrecognized message:', JSON.stringify(message));
|
console.log('Unrecognized message:', JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
@ -1287,45 +1294,6 @@ Rectangle {
|
||||||
nearbyTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning);
|
nearbyTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function sortConnectionsModel() {
|
|
||||||
var column = connectionsTable.getColumn(connectionsTable.sortIndicatorColumn);
|
|
||||||
var sortProperty = column ? column.role : "userName";
|
|
||||||
var before = (connectionsTable.sortIndicatorOrder === Qt.AscendingOrder) ? -1 : 1;
|
|
||||||
var after = -1 * before;
|
|
||||||
// get selection(s) before sorting
|
|
||||||
var selectedIDs = getSelectedConnectionsUserNames();
|
|
||||||
connectionsUserModelData.sort(function (a, b) {
|
|
||||||
var aValue = a[sortProperty].toString().toLowerCase(), bValue = b[sortProperty].toString().toLowerCase();
|
|
||||||
if (!aValue && !bValue) {
|
|
||||||
return 0;
|
|
||||||
} else if (!aValue) {
|
|
||||||
return after;
|
|
||||||
} else if (!bValue) {
|
|
||||||
return before;
|
|
||||||
}
|
|
||||||
switch (true) {
|
|
||||||
case (aValue < bValue): return before;
|
|
||||||
case (aValue > bValue): return after;
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
connectionsTable.selection.clear();
|
|
||||||
|
|
||||||
connectionsUserModel.clear();
|
|
||||||
var userIndex = 0;
|
|
||||||
var newSelectedIndexes = [];
|
|
||||||
connectionsUserModelData.forEach(function (datum) {
|
|
||||||
datum.userIndex = userIndex++;
|
|
||||||
connectionsUserModel.append(datum);
|
|
||||||
if (selectedIDs.indexOf(datum.sessionId) != -1) {
|
|
||||||
newSelectedIndexes.push(datum.userIndex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (newSelectedIndexes.length > 0) {
|
|
||||||
connectionsTable.selection.select(newSelectedIndexes);
|
|
||||||
connectionsTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
signal sendToScript(var message);
|
signal sendToScript(var message);
|
||||||
function noticeSelection() {
|
function noticeSelection() {
|
||||||
var userIds = [];
|
var userIds = [];
|
||||||
|
|
39
interface/resources/qml/hifi/RootHttpRequest.qml
Normal file
39
interface/resources/qml/hifi/RootHttpRequest.qml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// RootHttpRequest.qml
|
||||||
|
// qml/hifi
|
||||||
|
//
|
||||||
|
// Create an item of this in the ROOT qml to be able to make http requests.
|
||||||
|
// Used by PSFListModel.qml
|
||||||
|
//
|
||||||
|
// Created by Howard Stearns on 5/29/2018
|
||||||
|
// Copyright 2018 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.5
|
||||||
|
|
||||||
|
Item {
|
||||||
|
property var httpCalls: ({});
|
||||||
|
property var httpCounter: 0;
|
||||||
|
// Public function for initiating an http request.
|
||||||
|
// REQUIRES parent to be root to have sendToScript!
|
||||||
|
function request(options, callback) {
|
||||||
|
console.debug('HttpRequest', JSON.stringify(options));
|
||||||
|
httpCalls[httpCounter] = callback;
|
||||||
|
var message = {method: 'http.request', params: options, id: httpCounter++, jsonrpc: "2.0"};
|
||||||
|
parent.sendToScript(message);
|
||||||
|
}
|
||||||
|
// REQUIRES that parent/root handle http.response message.method in fromScript, by calling this function.
|
||||||
|
function handleHttpResponse(message) {
|
||||||
|
var callback = httpCalls[message.id]; // FIXME: as different top level tablet apps gets loaded, the id repeats. We should drop old app callbacks without warning.
|
||||||
|
if (!callback) {
|
||||||
|
console.warn('No callback for', JSON.stringify(message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete httpCalls[message.id];
|
||||||
|
console.debug('HttpRequest response', JSON.stringify(message));
|
||||||
|
callback(message.error, message.response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ Rectangle {
|
||||||
property bool alreadyOwned: false;
|
property bool alreadyOwned: false;
|
||||||
property int itemPrice: -1;
|
property int itemPrice: -1;
|
||||||
property bool isCertified;
|
property bool isCertified;
|
||||||
property string itemType;
|
property string itemType: "unknown";
|
||||||
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar", "unknown"];
|
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar", "unknown"];
|
||||||
property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar", "item"];
|
property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar", "item"];
|
||||||
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR", "REZ"];
|
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR", "REZ"];
|
||||||
|
@ -98,9 +98,6 @@ Rectangle {
|
||||||
} else {
|
} else {
|
||||||
root.certificateId = result.data.certificate_id;
|
root.certificateId = result.data.certificate_id;
|
||||||
root.itemHref = result.data.download_url;
|
root.itemHref = result.data.download_url;
|
||||||
if (result.data.categories.indexOf("Wearables") > -1) {
|
|
||||||
root.itemType = "wearable";
|
|
||||||
}
|
|
||||||
root.activeView = "checkoutSuccess";
|
root.activeView = "checkoutSuccess";
|
||||||
UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned);
|
UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned);
|
||||||
}
|
}
|
||||||
|
@ -170,9 +167,6 @@ Rectangle {
|
||||||
root.activeView = "checkoutFailure";
|
root.activeView = "checkoutFailure";
|
||||||
} else {
|
} else {
|
||||||
root.itemHref = result.data.download_url;
|
root.itemHref = result.data.download_url;
|
||||||
if (result.data.categories.indexOf("Wearables") > -1) {
|
|
||||||
root.itemType = "wearable";
|
|
||||||
}
|
|
||||||
root.activeView = "checkoutSuccess";
|
root.activeView = "checkoutSuccess";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,20 +180,6 @@ Rectangle {
|
||||||
itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
|
itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
|
||||||
}
|
}
|
||||||
|
|
||||||
onItemHrefChanged: {
|
|
||||||
if (root.itemHref.indexOf(".fst") > -1) {
|
|
||||||
root.itemType = "avatar";
|
|
||||||
} else if (root.itemHref.indexOf('.json.gz') > -1 || root.itemHref.indexOf('.content.zip') > -1) {
|
|
||||||
root.itemType = "contentSet";
|
|
||||||
} else if (root.itemHref.indexOf('.app.json') > -1) {
|
|
||||||
root.itemType = "app";
|
|
||||||
} else if (root.itemHref.indexOf('.json') > -1) {
|
|
||||||
root.itemType = "entity"; // "wearable" type handled later
|
|
||||||
} else {
|
|
||||||
root.itemType = "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onItemTypeChanged: {
|
onItemTypeChanged: {
|
||||||
if (root.itemType === "entity" || root.itemType === "wearable" ||
|
if (root.itemType === "entity" || root.itemType === "wearable" ||
|
||||||
root.itemType === "contentSet" || root.itemType === "avatar" || root.itemType === "app") {
|
root.itemType === "contentSet" || root.itemType === "avatar" || root.itemType === "app") {
|
||||||
|
@ -1102,6 +1082,7 @@ Rectangle {
|
||||||
root.referrer = message.params.referrer;
|
root.referrer = message.params.referrer;
|
||||||
root.itemAuthor = message.params.itemAuthor;
|
root.itemAuthor = message.params.itemAuthor;
|
||||||
root.itemEdition = message.params.itemEdition || -1;
|
root.itemEdition = message.params.itemEdition || -1;
|
||||||
|
root.itemType = message.params.itemType || "unknown";
|
||||||
refreshBuyUI();
|
refreshBuyUI();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -44,7 +44,7 @@ Item {
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: avatarImage;
|
id: avatarImage;
|
||||||
visible: profileUrl !== "" && userName !== "";
|
visible: profilePicUrl !== "" && userName !== "";
|
||||||
// Size
|
// Size
|
||||||
anchors.verticalCenter: parent.verticalCenter;
|
anchors.verticalCenter: parent.verticalCenter;
|
||||||
anchors.left: parent.left;
|
anchors.left: parent.left;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import "../../../../styles-uit"
|
||||||
import "../../../../controls-uit" as HifiControlsUit
|
import "../../../../controls-uit" as HifiControlsUit
|
||||||
import "../../../../controls" as HifiControls
|
import "../../../../controls" as HifiControls
|
||||||
import "../" as HifiCommerceCommon
|
import "../" as HifiCommerceCommon
|
||||||
|
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
HifiConstants { id: hifi; }
|
HifiConstants { id: hifi; }
|
||||||
|
@ -36,6 +37,8 @@ Item {
|
||||||
property string assetName: "";
|
property string assetName: "";
|
||||||
property string assetCertID: "";
|
property string assetCertID: "";
|
||||||
property string sendingPubliclyEffectImage;
|
property string sendingPubliclyEffectImage;
|
||||||
|
property var http;
|
||||||
|
property var listModelName;
|
||||||
|
|
||||||
// This object is always used in a popup or full-screen Wallet section.
|
// This object is always used in a popup or full-screen Wallet section.
|
||||||
// This MouseArea is used to prevent a user from being
|
// This MouseArea is used to prevent a user from being
|
||||||
|
@ -118,9 +121,7 @@ Item {
|
||||||
|
|
||||||
if (root.currentActiveView === 'chooseRecipientConnection') {
|
if (root.currentActiveView === 'chooseRecipientConnection') {
|
||||||
// Refresh connections model
|
// Refresh connections model
|
||||||
connectionsLoading.visible = false;
|
connectionsModel.getFirstPage();
|
||||||
connectionsLoading.visible = true;
|
|
||||||
sendSignalToParent({method: 'refreshConnections'});
|
|
||||||
} else if (root.currentActiveView === 'sendAssetHome') {
|
} else if (root.currentActiveView === 'sendAssetHome') {
|
||||||
Commerce.balance();
|
Commerce.balance();
|
||||||
} else if (root.currentActiveView === 'chooseRecipientNearby') {
|
} else if (root.currentActiveView === 'chooseRecipientNearby') {
|
||||||
|
@ -392,11 +393,17 @@ Item {
|
||||||
hoverEnabled: true;
|
hoverEnabled: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel {
|
HifiModels.PSFListModel {
|
||||||
id: connectionsModel;
|
id: connectionsModel;
|
||||||
}
|
http: root.http;
|
||||||
ListModel {
|
listModelName: root.listModelName;
|
||||||
id: filteredConnectionsModel;
|
endpoint: "/api/v1/users?filter=connections";
|
||||||
|
itemsPerPage: 8;
|
||||||
|
listView: connectionsList;
|
||||||
|
processPage: function (data) {
|
||||||
|
return data.users;
|
||||||
|
};
|
||||||
|
searchFilter: filterBar.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -472,10 +479,6 @@ Item {
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
centerPlaceholderGlyph: hifi.glyphs.search;
|
centerPlaceholderGlyph: hifi.glyphs.search;
|
||||||
|
|
||||||
onTextChanged: {
|
|
||||||
buildFilteredConnectionsModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
focus = false;
|
focus = false;
|
||||||
}
|
}
|
||||||
|
@ -495,6 +498,7 @@ Item {
|
||||||
|
|
||||||
AnimatedImage {
|
AnimatedImage {
|
||||||
id: connectionsLoading;
|
id: connectionsLoading;
|
||||||
|
visible: !connectionsModel.retrievedAtLeastOnePage;
|
||||||
source: "../../../../../icons/profilePicLoading.gif"
|
source: "../../../../../icons/profilePicLoading.gif"
|
||||||
width: 120;
|
width: 120;
|
||||||
height: width;
|
height: width;
|
||||||
|
@ -515,14 +519,15 @@ Item {
|
||||||
}
|
}
|
||||||
visible: !connectionsLoading.visible;
|
visible: !connectionsLoading.visible;
|
||||||
clip: true;
|
clip: true;
|
||||||
model: filteredConnectionsModel;
|
model: connectionsModel;
|
||||||
|
onAtYEndChanged: if (connectionsList.atYEnd && !connectionsList.atYBeginning) { connectionsModel.getNextPage(); }
|
||||||
snapMode: ListView.SnapToItem;
|
snapMode: ListView.SnapToItem;
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
delegate: ConnectionItem {
|
delegate: ConnectionItem {
|
||||||
isSelected: connectionsList.currentIndex === index;
|
isSelected: connectionsList.currentIndex === index;
|
||||||
userName: model.userName;
|
userName: model.username;
|
||||||
profilePicUrl: model.profileUrl;
|
profilePicUrl: model.images.thumbnail;
|
||||||
anchors.topMargin: 6;
|
anchors.topMargin: 6;
|
||||||
anchors.bottomMargin: 6;
|
anchors.bottomMargin: 6;
|
||||||
|
|
||||||
|
@ -553,7 +558,7 @@ Item {
|
||||||
// "Make a Connection" instructions
|
// "Make a Connection" instructions
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: connectionInstructions;
|
id: connectionInstructions;
|
||||||
visible: connectionsModel.count === 0 && !connectionsLoading.visible;
|
visible: connectionsModel.count === 0 && !connectionsModel.searchFilter && !connectionsLoading.visible;
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
color: "white";
|
color: "white";
|
||||||
|
|
||||||
|
@ -1806,22 +1811,6 @@ Item {
|
||||||
// FUNCTION DEFINITIONS START
|
// FUNCTION DEFINITIONS START
|
||||||
//
|
//
|
||||||
|
|
||||||
function updateConnections(connections) {
|
|
||||||
connectionsModel.clear();
|
|
||||||
connectionsModel.append(connections);
|
|
||||||
buildFilteredConnectionsModel();
|
|
||||||
connectionsLoading.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildFilteredConnectionsModel() {
|
|
||||||
filteredConnectionsModel.clear();
|
|
||||||
for (var i = 0; i < connectionsModel.count; i++) {
|
|
||||||
if (connectionsModel.get(i).userName.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
|
|
||||||
filteredConnectionsModel.append(connectionsModel.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetSendAssetData() {
|
function resetSendAssetData() {
|
||||||
amountTextField.focus = false;
|
amountTextField.focus = false;
|
||||||
optionalMessage.focus = false;
|
optionalMessage.focus = false;
|
||||||
|
|
|
@ -16,10 +16,12 @@ import QtQuick 2.5
|
||||||
import "../../../styles-uit"
|
import "../../../styles-uit"
|
||||||
import "../../../controls-uit" as HifiControlsUit
|
import "../../../controls-uit" as HifiControlsUit
|
||||||
import "../../../controls" as HifiControls
|
import "../../../controls" as HifiControls
|
||||||
|
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
|
||||||
import "../wallet" as HifiWallet
|
import "../wallet" as HifiWallet
|
||||||
import "../common" as HifiCommerceCommon
|
import "../common" as HifiCommerceCommon
|
||||||
import "../inspectionCertificate" as HifiInspectionCertificate
|
import "../inspectionCertificate" as HifiInspectionCertificate
|
||||||
import "../common/sendAsset" as HifiSendAsset
|
import "../common/sendAsset" as HifiSendAsset
|
||||||
|
import "../.." as HifiCommon
|
||||||
|
|
||||||
// references XXX from root context
|
// references XXX from root context
|
||||||
|
|
||||||
|
@ -34,12 +36,17 @@ Rectangle {
|
||||||
property bool punctuationMode: false;
|
property bool punctuationMode: false;
|
||||||
property bool isShowingMyItems: false;
|
property bool isShowingMyItems: false;
|
||||||
property bool isDebuggingFirstUseTutorial: false;
|
property bool isDebuggingFirstUseTutorial: false;
|
||||||
property int pendingItemCount: 0;
|
|
||||||
property string installedApps;
|
property string installedApps;
|
||||||
property bool keyboardRaised: false;
|
property bool keyboardRaised: false;
|
||||||
property int numUpdatesAvailable: 0;
|
property int numUpdatesAvailable: 0;
|
||||||
// Style
|
// Style
|
||||||
color: hifi.colors.white;
|
color: hifi.colors.white;
|
||||||
|
function getPurchases() {
|
||||||
|
root.activeView = "purchasesMain";
|
||||||
|
root.installedApps = Commerce.getInstalledApps();
|
||||||
|
purchasesModel.getFirstPage();
|
||||||
|
Commerce.getAvailableUpdates();
|
||||||
|
}
|
||||||
Connections {
|
Connections {
|
||||||
target: Commerce;
|
target: Commerce;
|
||||||
|
|
||||||
|
@ -62,10 +69,7 @@ Rectangle {
|
||||||
if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") {
|
if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") {
|
||||||
root.activeView = "firstUseTutorial";
|
root.activeView = "firstUseTutorial";
|
||||||
} else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
|
} else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
|
||||||
root.activeView = "purchasesMain";
|
getPurchases();
|
||||||
root.installedApps = Commerce.getInstalledApps();
|
|
||||||
Commerce.inventory();
|
|
||||||
Commerce.getAvailableUpdates();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus);
|
console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus);
|
||||||
|
@ -81,39 +85,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
onInventoryResult: {
|
onInventoryResult: {
|
||||||
purchasesReceived = true;
|
purchasesModel.handlePage(result.status !== "success" && result.message, result);
|
||||||
|
|
||||||
if (result.status !== 'success') {
|
|
||||||
console.log("Failed to get purchases", result.message);
|
|
||||||
} else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling
|
|
||||||
var inventoryResult = processInventoryResult(result.data.assets);
|
|
||||||
|
|
||||||
var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex;
|
|
||||||
purchasesModel.clear();
|
|
||||||
purchasesModel.append(inventoryResult);
|
|
||||||
|
|
||||||
root.pendingItemCount = 0;
|
|
||||||
for (var i = 0; i < purchasesModel.count; i++) {
|
|
||||||
if (purchasesModel.get(i).status === "pending") {
|
|
||||||
root.pendingItemCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previousPurchasesModel.count !== 0) {
|
|
||||||
checkIfAnyItemStatusChanged();
|
|
||||||
} else {
|
|
||||||
// Fill statusChanged default value
|
|
||||||
// Not doing this results in the default being true...
|
|
||||||
for (var i = 0; i < purchasesModel.count; i++) {
|
|
||||||
purchasesModel.setProperty(i, "statusChanged", false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
previousPurchasesModel.append(inventoryResult);
|
|
||||||
|
|
||||||
buildFilteredPurchasesModel();
|
|
||||||
|
|
||||||
purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAvailableUpdatesResult: {
|
onAvailableUpdatesResult: {
|
||||||
|
@ -134,6 +106,10 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onIsShowingMyItemsChanged: {
|
||||||
|
getPurchases();
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: notSetUpTimer;
|
id: notSetUpTimer;
|
||||||
interval: 200;
|
interval: 200;
|
||||||
|
@ -172,8 +148,14 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HifiCommon.RootHttpRequest {
|
||||||
|
id: http;
|
||||||
|
}
|
||||||
|
|
||||||
HifiSendAsset.SendAsset {
|
HifiSendAsset.SendAsset {
|
||||||
id: sendAsset;
|
id: sendAsset;
|
||||||
|
http: http;
|
||||||
|
listModelName: "Gift Connections";
|
||||||
z: 998;
|
z: 998;
|
||||||
visible: root.activeView === "giftAsset";
|
visible: root.activeView === "giftAsset";
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
|
@ -183,9 +165,7 @@ Rectangle {
|
||||||
Connections {
|
Connections {
|
||||||
onSendSignalToParent: {
|
onSendSignalToParent: {
|
||||||
if (msg.method === 'sendAssetHome_back' || msg.method === 'closeSendAsset') {
|
if (msg.method === 'sendAssetHome_back' || msg.method === 'closeSendAsset') {
|
||||||
root.activeView = "purchasesMain";
|
getPurchases();
|
||||||
Commerce.inventory();
|
|
||||||
Commerce.getAvailableUpdates();
|
|
||||||
} else {
|
} else {
|
||||||
sendToScript(msg);
|
sendToScript(msg);
|
||||||
}
|
}
|
||||||
|
@ -449,10 +429,7 @@ Rectangle {
|
||||||
case 'tutorial_skipClicked':
|
case 'tutorial_skipClicked':
|
||||||
case 'tutorial_finished':
|
case 'tutorial_finished':
|
||||||
Settings.setValue("isFirstUseOfPurchases", false);
|
Settings.setValue("isFirstUseOfPurchases", false);
|
||||||
root.activeView = "purchasesMain";
|
getPurchases();
|
||||||
root.installedApps = Commerce.getInstalledApps();
|
|
||||||
Commerce.inventory();
|
|
||||||
Commerce.getAvailableUpdates();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -528,7 +505,7 @@ Rectangle {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"displayName": "Content Set",
|
"displayName": "Content Set",
|
||||||
"filterName": "contentSet"
|
"filterName": "content_set"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"displayName": "Entity",
|
"displayName": "Entity",
|
||||||
|
@ -540,7 +517,7 @@ Rectangle {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"displayName": "Updatable",
|
"displayName": "Updatable",
|
||||||
"filterName": "updatable"
|
"filterName": "updated"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
filterBar.primaryFilterChoices.clear();
|
filterBar.primaryFilterChoices.clear();
|
||||||
|
@ -548,14 +525,12 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPrimaryFilter_displayNameChanged: {
|
onPrimaryFilter_displayNameChanged: {
|
||||||
buildFilteredPurchasesModel();
|
purchasesModel.tagsFilter = filterBar.primaryFilter_filterName;
|
||||||
purchasesContentsList.positionViewAtIndex(0, ListView.Beginning)
|
|
||||||
filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName;
|
filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
buildFilteredPurchasesModel();
|
purchasesModel.searchFilter = filterBar.text;
|
||||||
purchasesContentsList.positionViewAtIndex(0, ListView.Beginning)
|
|
||||||
filterBar.previousText = filterBar.text;
|
filterBar.previousText = filterBar.text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -574,24 +549,41 @@ Rectangle {
|
||||||
anchors.topMargin: 16;
|
anchors.topMargin: 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel {
|
HifiModels.PSFListModel {
|
||||||
id: purchasesModel;
|
id: purchasesModel;
|
||||||
}
|
itemsPerPage: 6;
|
||||||
ListModel {
|
listModelName: 'purchases';
|
||||||
id: previousPurchasesModel;
|
getPage: function () {
|
||||||
}
|
console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage);
|
||||||
HifiCommerceCommon.SortableListModel {
|
Commerce.inventory(
|
||||||
id: tempPurchasesModel;
|
root.isShowingMyItems ? "proofs" : "purchased",
|
||||||
}
|
filterBar.primaryFilter_filterName,
|
||||||
HifiCommerceCommon.SortableListModel {
|
filterBar.text,
|
||||||
id: filteredPurchasesModel;
|
purchasesModel.currentPageToRetrieve,
|
||||||
|
purchasesModel.itemsPerPage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
processPage: function(data) {
|
||||||
|
purchasesReceived = true; // HRS FIXME?
|
||||||
|
data.assets.forEach(function (item) {
|
||||||
|
if (item.status.length > 1) { console.warn("Unrecognized inventory status", item); }
|
||||||
|
item.status = item.status[0];
|
||||||
|
item.categories = item.categories.join(';');
|
||||||
|
item.cardBackVisible = false;
|
||||||
|
item.isInstalled = root.installedApps.indexOf(item.id) > -1;
|
||||||
|
item.wornEntityID = '';
|
||||||
|
});
|
||||||
|
sendToScript({ method: 'purchases_updateWearables' });
|
||||||
|
|
||||||
|
return data.assets;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: purchasesContentsList;
|
id: purchasesContentsList;
|
||||||
visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
|
visible: purchasesModel.count !== 0;
|
||||||
clip: true;
|
clip: true;
|
||||||
model: filteredPurchasesModel;
|
model: purchasesModel;
|
||||||
snapMode: ListView.SnapToItem;
|
snapMode: ListView.SnapToItem;
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.top: separator.bottom;
|
anchors.top: separator.bottom;
|
||||||
|
@ -608,13 +600,13 @@ Rectangle {
|
||||||
itemEdition: model.edition_number;
|
itemEdition: model.edition_number;
|
||||||
numberSold: model.number_sold;
|
numberSold: model.number_sold;
|
||||||
limitedRun: model.limited_run;
|
limitedRun: model.limited_run;
|
||||||
displayedItemCount: model.displayedItemCount;
|
displayedItemCount: 999; // For now (and maybe longer), we're going to display all the edition numbers.
|
||||||
cardBackVisible: model.cardBackVisible;
|
cardBackVisible: model.cardBackVisible || false;
|
||||||
isInstalled: model.isInstalled;
|
isInstalled: model.isInstalled || false;
|
||||||
wornEntityID: model.wornEntityID;
|
wornEntityID: model.wornEntityID;
|
||||||
upgradeUrl: model.upgrade_url;
|
upgradeUrl: model.upgrade_url;
|
||||||
upgradeTitle: model.upgrade_title;
|
upgradeTitle: model.upgrade_title;
|
||||||
itemType: model.itemType;
|
itemType: model.item_type;
|
||||||
isShowingMyItems: root.isShowingMyItems;
|
isShowingMyItems: root.isShowingMyItems;
|
||||||
valid: model.valid;
|
valid: model.valid;
|
||||||
anchors.topMargin: 10;
|
anchors.topMargin: 10;
|
||||||
|
@ -706,11 +698,11 @@ Rectangle {
|
||||||
} else if (msg.method === "setFilterText") {
|
} else if (msg.method === "setFilterText") {
|
||||||
filterBar.text = msg.filterText;
|
filterBar.text = msg.filterText;
|
||||||
} else if (msg.method === "flipCard") {
|
} else if (msg.method === "flipCard") {
|
||||||
for (var i = 0; i < filteredPurchasesModel.count; i++) {
|
for (var i = 0; i < purchasesModel.count; i++) {
|
||||||
if (i !== index || msg.closeAll) {
|
if (i !== index || msg.closeAll) {
|
||||||
filteredPurchasesModel.setProperty(i, "cardBackVisible", false);
|
purchasesModel.setProperty(i, "cardBackVisible", false);
|
||||||
} else {
|
} else {
|
||||||
filteredPurchasesModel.setProperty(i, "cardBackVisible", true);
|
purchasesModel.setProperty(i, "cardBackVisible", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (msg.method === "updateItemClicked") {
|
} else if (msg.method === "updateItemClicked") {
|
||||||
|
@ -761,7 +753,7 @@ Rectangle {
|
||||||
lightboxPopup.button2text = "CONFIRM";
|
lightboxPopup.button2text = "CONFIRM";
|
||||||
lightboxPopup.button2method = function() {
|
lightboxPopup.button2method = function() {
|
||||||
Entities.deleteEntity(msg.wornEntityID);
|
Entities.deleteEntity(msg.wornEntityID);
|
||||||
filteredPurchasesModel.setProperty(index, 'wornEntityID', '');
|
purchasesModel.setProperty(index, 'wornEntityID', '');
|
||||||
root.activeView = "giftAsset";
|
root.activeView = "giftAsset";
|
||||||
lightboxPopup.visible = false;
|
lightboxPopup.visible = false;
|
||||||
};
|
};
|
||||||
|
@ -773,6 +765,14 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onAtYEndChanged: {
|
||||||
|
if (purchasesContentsList.atYEnd && !purchasesContentsList.atYBeginning) {
|
||||||
|
console.log("User scrolled to the bottom of 'Purchases'.");
|
||||||
|
purchasesModel.getNextPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -953,146 +953,14 @@ Rectangle {
|
||||||
//
|
//
|
||||||
// FUNCTION DEFINITIONS START
|
// FUNCTION DEFINITIONS START
|
||||||
//
|
//
|
||||||
|
|
||||||
function processInventoryResult(inventory) {
|
|
||||||
for (var i = 0; i < inventory.length; i++) {
|
|
||||||
if (inventory[i].status.length > 1) {
|
|
||||||
console.log("WARNING: Inventory result index " + i + " has a status of length >1!")
|
|
||||||
}
|
|
||||||
inventory[i].status = inventory[i].status[0];
|
|
||||||
inventory[i].categories = inventory[i].categories.join(';');
|
|
||||||
}
|
|
||||||
return inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateDisplayedItemCounts() {
|
|
||||||
var itemCountDictionary = {};
|
|
||||||
var currentItemId;
|
|
||||||
for (var i = 0; i < filteredPurchasesModel.count; i++) {
|
|
||||||
currentItemId = filteredPurchasesModel.get(i).id;
|
|
||||||
if (itemCountDictionary[currentItemId] === undefined) {
|
|
||||||
itemCountDictionary[currentItemId] = 1;
|
|
||||||
} else {
|
|
||||||
itemCountDictionary[currentItemId]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < filteredPurchasesModel.count; i++) {
|
|
||||||
filteredPurchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[filteredPurchasesModel.get(i).id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortByDate() {
|
|
||||||
filteredPurchasesModel.sortColumnName = "purchase_date";
|
|
||||||
filteredPurchasesModel.isSortingDescending = true;
|
|
||||||
filteredPurchasesModel.valuesAreNumerical = true;
|
|
||||||
filteredPurchasesModel.quickSort();
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildFilteredPurchasesModel() {
|
|
||||||
var sameItemCount = 0;
|
|
||||||
|
|
||||||
tempPurchasesModel.clear();
|
|
||||||
|
|
||||||
for (var i = 0; i < purchasesModel.count; i++) {
|
|
||||||
if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
|
|
||||||
if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) {
|
|
||||||
tempPurchasesModel.insert(0, purchasesModel.get(i));
|
|
||||||
} else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") ||
|
|
||||||
(!root.isShowingMyItems && purchasesModel.get(i).edition_number !== "0")) {
|
|
||||||
tempPurchasesModel.append(purchasesModel.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// primaryFilter filtering and adding of itemType property to model
|
|
||||||
var currentItemType, currentRootFileUrl, currentCategories;
|
|
||||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
|
||||||
currentRootFileUrl = tempPurchasesModel.get(i).root_file_url;
|
|
||||||
currentCategories = tempPurchasesModel.get(i).categories;
|
|
||||||
|
|
||||||
if (currentRootFileUrl.indexOf(".fst") > -1) {
|
|
||||||
currentItemType = "avatar";
|
|
||||||
} else if (currentCategories.indexOf("Wearables") > -1) {
|
|
||||||
currentItemType = "wearable";
|
|
||||||
} else if (currentRootFileUrl.endsWith('.json.gz') || currentRootFileUrl.endsWith('.content.zip')) {
|
|
||||||
currentItemType = "contentSet";
|
|
||||||
} else if (currentRootFileUrl.endsWith('.app.json')) {
|
|
||||||
currentItemType = "app";
|
|
||||||
} else if (currentRootFileUrl.endsWith('.json')) {
|
|
||||||
currentItemType = "entity";
|
|
||||||
} else {
|
|
||||||
currentItemType = "unknown";
|
|
||||||
}
|
|
||||||
if (filterBar.primaryFilter_displayName !== "" &&
|
|
||||||
((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgrade_url === "") ||
|
|
||||||
(filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) {
|
|
||||||
tempPurchasesModel.remove(i);
|
|
||||||
i--;
|
|
||||||
} else {
|
|
||||||
tempPurchasesModel.setProperty(i, 'itemType', currentItemType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
|
||||||
if (!filteredPurchasesModel.get(i)) {
|
|
||||||
sameItemCount = -1;
|
|
||||||
break;
|
|
||||||
} else if (tempPurchasesModel.get(i).itemId === filteredPurchasesModel.get(i).itemId &&
|
|
||||||
tempPurchasesModel.get(i).edition_number === filteredPurchasesModel.get(i).edition_number &&
|
|
||||||
tempPurchasesModel.get(i).status === filteredPurchasesModel.get(i).status) {
|
|
||||||
sameItemCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sameItemCount !== tempPurchasesModel.count ||
|
|
||||||
filterBar.text !== filterBar.previousText ||
|
|
||||||
filterBar.primaryFilter !== filterBar.previousPrimaryFilter) {
|
|
||||||
filteredPurchasesModel.clear();
|
|
||||||
var currentId;
|
|
||||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
|
||||||
currentId = tempPurchasesModel.get(i).id;
|
|
||||||
filteredPurchasesModel.append(tempPurchasesModel.get(i));
|
|
||||||
filteredPurchasesModel.setProperty(i, 'cardBackVisible', false);
|
|
||||||
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));
|
|
||||||
filteredPurchasesModel.setProperty(i, 'wornEntityID', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToScript({ method: 'purchases_updateWearables' });
|
|
||||||
populateDisplayedItemCounts();
|
|
||||||
sortByDate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkIfAnyItemStatusChanged() {
|
|
||||||
var currentPurchasesModelId, currentPurchasesModelEdition, currentPurchasesModelStatus;
|
|
||||||
var previousPurchasesModelStatus;
|
|
||||||
for (var i = 0; i < purchasesModel.count; i++) {
|
|
||||||
currentPurchasesModelId = purchasesModel.get(i).id;
|
|
||||||
currentPurchasesModelEdition = purchasesModel.get(i).edition_number;
|
|
||||||
currentPurchasesModelStatus = purchasesModel.get(i).status;
|
|
||||||
|
|
||||||
for (var j = 0; j < previousPurchasesModel.count; j++) {
|
|
||||||
previousPurchasesModelStatus = previousPurchasesModel.get(j).status;
|
|
||||||
if (currentPurchasesModelId === previousPurchasesModel.get(j).id &&
|
|
||||||
currentPurchasesModelEdition === previousPurchasesModel.get(j).edition_number &&
|
|
||||||
currentPurchasesModelStatus !== previousPurchasesModelStatus) {
|
|
||||||
|
|
||||||
purchasesModel.setProperty(i, "statusChanged", true);
|
|
||||||
} else {
|
|
||||||
purchasesModel.setProperty(i, "statusChanged", false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCurrentlyWornWearables(wearables) {
|
function updateCurrentlyWornWearables(wearables) {
|
||||||
for (var i = 0; i < filteredPurchasesModel.count; i++) {
|
for (var i = 0; i < purchasesModel.count; i++) {
|
||||||
for (var j = 0; j < wearables.length; j++) {
|
for (var j = 0; j < wearables.length; j++) {
|
||||||
if (filteredPurchasesModel.get(i).itemType === "wearable" &&
|
if (purchasesModel.get(i).itemType === "wearable" &&
|
||||||
wearables[j].entityCertID === filteredPurchasesModel.get(i).certificate_id &&
|
wearables[j].entityCertID === purchasesModel.get(i).certificate_id &&
|
||||||
wearables[j].entityEdition.toString() === filteredPurchasesModel.get(i).edition_number) {
|
wearables[j].entityEdition.toString() === purchasesModel.get(i).edition_number) {
|
||||||
filteredPurchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID);
|
purchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1149,7 +1017,7 @@ Rectangle {
|
||||||
switch (message.method) {
|
switch (message.method) {
|
||||||
case 'updatePurchases':
|
case 'updatePurchases':
|
||||||
referrerURL = message.referrerURL || "";
|
referrerURL = message.referrerURL || "";
|
||||||
titleBarContainer.referrerURL = message.referrerURL;
|
titleBarContainer.referrerURL = message.referrerURL || "";
|
||||||
filterBar.text = message.filterText ? message.filterText : "";
|
filterBar.text = message.filterText ? message.filterText : "";
|
||||||
break;
|
break;
|
||||||
case 'inspectionCertificate_setCertificateId':
|
case 'inspectionCertificate_setCertificateId':
|
||||||
|
@ -1168,6 +1036,9 @@ Rectangle {
|
||||||
case 'updateWearables':
|
case 'updateWearables':
|
||||||
updateCurrentlyWornWearables(message.wornWearables);
|
updateCurrentlyWornWearables(message.wornWearables);
|
||||||
break;
|
break;
|
||||||
|
case 'http.response':
|
||||||
|
http.handleHttpResponse(message);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
|
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import "../../../controls-uit" as HifiControlsUit
|
||||||
import "../../../controls" as HifiControls
|
import "../../../controls" as HifiControls
|
||||||
import "../common" as HifiCommerceCommon
|
import "../common" as HifiCommerceCommon
|
||||||
import "../common/sendAsset"
|
import "../common/sendAsset"
|
||||||
|
import "../.." as HifiCommon
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
HifiConstants { id: hifi; }
|
HifiConstants { id: hifi; }
|
||||||
|
@ -343,8 +344,14 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HifiCommon.RootHttpRequest {
|
||||||
|
id: http;
|
||||||
|
}
|
||||||
|
|
||||||
SendAsset {
|
SendAsset {
|
||||||
id: sendMoney;
|
id: sendMoney;
|
||||||
|
http: http;
|
||||||
|
listModelName: "Send Money Connections";
|
||||||
z: 997;
|
z: 997;
|
||||||
visible: root.activeView === "sendMoney";
|
visible: root.activeView === "sendMoney";
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
|
@ -768,6 +775,13 @@ Rectangle {
|
||||||
case 'updateSelectedRecipientUsername':
|
case 'updateSelectedRecipientUsername':
|
||||||
sendMoney.fromScript(message);
|
sendMoney.fromScript(message);
|
||||||
break;
|
break;
|
||||||
|
case 'http.response':
|
||||||
|
http.handleHttpResponse(message);
|
||||||
|
break;
|
||||||
|
case 'palIsStale':
|
||||||
|
case 'avatarDisconnected':
|
||||||
|
// Because we don't have "channels" for sending messages to a specific QML object, the messages are broadcast to all QML Items. If an Item of yours happens to be visible when some script sends a message with a method you don't expect, you'll get "Unrecognized message..." logs.
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
|
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,27 +18,17 @@ import QtQuick.Controls 2.2
|
||||||
import "../../../styles-uit"
|
import "../../../styles-uit"
|
||||||
import "../../../controls-uit" as HifiControlsUit
|
import "../../../controls-uit" as HifiControlsUit
|
||||||
import "../../../controls" as HifiControls
|
import "../../../controls" as HifiControls
|
||||||
|
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
HifiConstants { id: hifi; }
|
HifiConstants { id: hifi; }
|
||||||
|
|
||||||
id: root;
|
id: root;
|
||||||
property bool initialHistoryReceived: false;
|
|
||||||
property bool historyRequestPending: true;
|
|
||||||
property bool noMoreHistoryData: false;
|
|
||||||
property int pendingCount: 0;
|
|
||||||
property int currentHistoryPage: 1;
|
|
||||||
property var pagesAlreadyAdded: new Array();
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
transactionHistoryModel.clear();
|
|
||||||
Commerce.balance();
|
Commerce.balance();
|
||||||
initialHistoryReceived = false;
|
transactionHistoryModel.getFirstPage();
|
||||||
root.currentHistoryPage = 1;
|
|
||||||
root.noMoreHistoryData = false;
|
|
||||||
root.historyRequestPending = true;
|
|
||||||
Commerce.history(root.currentHistoryPage);
|
|
||||||
Commerce.getAvailableUpdates();
|
Commerce.getAvailableUpdates();
|
||||||
} else {
|
} else {
|
||||||
refreshTimer.stop();
|
refreshTimer.stop();
|
||||||
|
@ -53,86 +43,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
onHistoryResult : {
|
onHistoryResult : {
|
||||||
root.initialHistoryReceived = true;
|
transactionHistoryModel.handlePage(null, result);
|
||||||
root.historyRequestPending = false;
|
|
||||||
|
|
||||||
if (result.status === 'success') {
|
|
||||||
var currentPage = parseInt(result.current_page);
|
|
||||||
|
|
||||||
if (result.data.history.length === 0) {
|
|
||||||
root.noMoreHistoryData = true;
|
|
||||||
console.log("No more data to retrieve from Commerce.history() endpoint.")
|
|
||||||
} else if (root.currentHistoryPage === 1) {
|
|
||||||
var sameItemCount = 0;
|
|
||||||
tempTransactionHistoryModel.clear();
|
|
||||||
|
|
||||||
tempTransactionHistoryModel.append(result.data.history);
|
|
||||||
|
|
||||||
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
|
||||||
if (!transactionHistoryModel.get(i)) {
|
|
||||||
sameItemCount = -1;
|
|
||||||
break;
|
|
||||||
} else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type &&
|
|
||||||
tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) {
|
|
||||||
sameItemCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sameItemCount !== tempTransactionHistoryModel.count) {
|
|
||||||
transactionHistoryModel.clear();
|
|
||||||
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
|
||||||
transactionHistoryModel.append(tempTransactionHistoryModel.get(i));
|
|
||||||
}
|
|
||||||
calculatePendingAndInvalidated();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) {
|
|
||||||
console.log("Page " + currentPage + " of history has already been added to the list.");
|
|
||||||
} else {
|
|
||||||
// First, add the history result to a temporary model
|
|
||||||
tempTransactionHistoryModel.clear();
|
|
||||||
tempTransactionHistoryModel.append(result.data.history);
|
|
||||||
|
|
||||||
// Make a note that we've already added this page to the model...
|
|
||||||
root.pagesAlreadyAdded.push(currentPage);
|
|
||||||
|
|
||||||
var insertionIndex = 0;
|
|
||||||
// If there's nothing in the model right now, we don't need to modify insertionIndex.
|
|
||||||
if (transactionHistoryModel.count !== 0) {
|
|
||||||
var currentIteratorPage;
|
|
||||||
// Search through the whole transactionHistoryModel and look for the insertion point.
|
|
||||||
// The insertion point is found when the result page from the server is less than
|
|
||||||
// the page that the current item came from, OR when we've reached the end of the whole model.
|
|
||||||
for (var i = 0; i < transactionHistoryModel.count; i++) {
|
|
||||||
currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage;
|
|
||||||
|
|
||||||
if (currentPage < currentIteratorPage) {
|
|
||||||
insertionIndex = i;
|
|
||||||
break;
|
|
||||||
} else if (i === transactionHistoryModel.count - 1) {
|
|
||||||
insertionIndex = i + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through the results we just got back from the server, setting the "resultIsFromPage"
|
|
||||||
// property of those results and adding them to the main model.
|
|
||||||
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
|
||||||
tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage);
|
|
||||||
transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
calculatePendingAndInvalidated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only auto-refresh if the user hasn't scrolled
|
|
||||||
// and there is more data to grab
|
|
||||||
if (transactionHistory.atYBeginning && !root.noMoreHistoryData) {
|
|
||||||
refreshTimer.start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAvailableUpdatesResult: {
|
onAvailableUpdatesResult: {
|
||||||
|
@ -147,7 +58,7 @@ Item {
|
||||||
Connections {
|
Connections {
|
||||||
target: GlobalServices
|
target: GlobalServices
|
||||||
onMyUsernameChanged: {
|
onMyUsernameChanged: {
|
||||||
transactionHistoryModel.clear();
|
transactionHistoryModel.resetModel();
|
||||||
usernameText.text = Account.username;
|
usernameText.text = Account.username;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,9 +146,8 @@ Item {
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (transactionHistory.atYBeginning) {
|
if (transactionHistory.atYBeginning) {
|
||||||
console.log("Refreshing 1st Page of Recent Activity...");
|
console.log("Refreshing 1st Page of Recent Activity...");
|
||||||
root.historyRequestPending = true;
|
|
||||||
Commerce.balance();
|
Commerce.balance();
|
||||||
Commerce.history(1);
|
transactionHistoryModel.getFirstPage("delayedClear");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -299,11 +209,42 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel {
|
HifiModels.PSFListModel {
|
||||||
id: tempTransactionHistoryModel;
|
|
||||||
}
|
|
||||||
ListModel {
|
|
||||||
id: transactionHistoryModel;
|
id: transactionHistoryModel;
|
||||||
|
listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly.
|
||||||
|
itemsPerPage: 6;
|
||||||
|
getPage: function () {
|
||||||
|
console.debug('getPage', transactionHistoryModel.listModelName, transactionHistoryModel.currentPageToRetrieve);
|
||||||
|
Commerce.history(transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage);
|
||||||
|
}
|
||||||
|
processPage: function (data) {
|
||||||
|
console.debug('processPage', transactionHistoryModel.listModelName, JSON.stringify(data));
|
||||||
|
var result, pending; // Set up or get the accumulator for pending.
|
||||||
|
if (transactionHistoryModel.currentPageToRetrieve == 1) {
|
||||||
|
pending = {transaction_type: "pendingCount", count: 0};
|
||||||
|
result = [pending];
|
||||||
|
} else {
|
||||||
|
pending = transactionHistoryModel.get(0);
|
||||||
|
result = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either add to pending, or to result.
|
||||||
|
// Note that you only see a page of pending stuff until you scroll...
|
||||||
|
data.history.forEach(function (item) {
|
||||||
|
if (item.status === 'pending') {
|
||||||
|
pending.count++;
|
||||||
|
} else {
|
||||||
|
result = result.concat(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only auto-refresh if the user hasn't scrolled
|
||||||
|
// and there is more data to grab
|
||||||
|
if (transactionHistory.atYBeginning && data.history.length) {
|
||||||
|
refreshTimer.start();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Item {
|
Item {
|
||||||
anchors.top: recentActivityText.bottom;
|
anchors.top: recentActivityText.bottom;
|
||||||
|
@ -312,8 +253,8 @@ Item {
|
||||||
anchors.left: parent.left;
|
anchors.left: parent.left;
|
||||||
anchors.right: parent.right;
|
anchors.right: parent.right;
|
||||||
|
|
||||||
Item {
|
Item { // On empty history. We don't want to flash and then replace, so don't show until we know we're zero.
|
||||||
visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived;
|
visible: transactionHistoryModel.count === 0 && transactionHistoryModel.currentPageToRetrieve < 0;
|
||||||
anchors.centerIn: parent;
|
anchors.centerIn: parent;
|
||||||
width: parent.width - 12;
|
width: parent.width - 12;
|
||||||
height: parent.height;
|
height: parent.height;
|
||||||
|
@ -385,10 +326,10 @@ Item {
|
||||||
model: transactionHistoryModel;
|
model: transactionHistoryModel;
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: parent.width;
|
width: parent.width;
|
||||||
height: (model.transaction_type === "pendingCount" && root.pendingCount !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0);
|
height: (model.transaction_type === "pendingCount" && model.count !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0);
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
visible: model.transaction_type === "pendingCount" && root.pendingCount !== 0;
|
visible: model.transaction_type === "pendingCount" && model.count !== 0;
|
||||||
anchors.top: parent.top;
|
anchors.top: parent.top;
|
||||||
anchors.left: parent.left;
|
anchors.left: parent.left;
|
||||||
width: parent.width;
|
width: parent.width;
|
||||||
|
@ -397,7 +338,7 @@ Item {
|
||||||
AnonymousProRegular {
|
AnonymousProRegular {
|
||||||
id: pendingCountText;
|
id: pendingCountText;
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
text: root.pendingCount + ' Transaction' + (root.pendingCount > 1 ? 's' : '') + ' Pending';
|
text: model.count + ' Transaction' + (model.count > 1 ? 's' : '') + ' Pending';
|
||||||
size: 18;
|
size: 18;
|
||||||
color: hifi.colors.blueAccent;
|
color: hifi.colors.blueAccent;
|
||||||
verticalAlignment: Text.AlignVCenter;
|
verticalAlignment: Text.AlignVCenter;
|
||||||
|
@ -460,14 +401,9 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onAtYEndChanged: {
|
onAtYEndChanged: {
|
||||||
if (transactionHistory.atYEnd) {
|
if (transactionHistory.atYEnd && !transactionHistory.atYBeginning) {
|
||||||
console.log("User scrolled to the bottom of 'Recent Activity'.");
|
console.log("User scrolled to the bottom of 'Recent Activity'.");
|
||||||
if (!root.historyRequestPending && !root.noMoreHistoryData) {
|
transactionHistoryModel.getNextPage();
|
||||||
// Grab next page of results and append to model
|
|
||||||
root.historyRequestPending = true;
|
|
||||||
Commerce.history(++root.currentHistoryPage);
|
|
||||||
console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity...");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -506,40 +442,6 @@ Item {
|
||||||
return year + '-' + month + '-' + day + '<br>' + drawnHour + ':' + min + amOrPm;
|
return year + '-' + month + '-' + day + '<br>' + drawnHour + ':' + min + amOrPm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function calculatePendingAndInvalidated(startingPendingCount) {
|
|
||||||
var pendingCount = startingPendingCount ? startingPendingCount : 0;
|
|
||||||
for (var i = 0; i < transactionHistoryModel.count; i++) {
|
|
||||||
if (transactionHistoryModel.get(i).status === "pending") {
|
|
||||||
pendingCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
root.pendingCount = pendingCount;
|
|
||||||
if (pendingCount > 0) {
|
|
||||||
transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Function Name: fromScript()
|
|
||||||
//
|
|
||||||
// Relevant Variables:
|
|
||||||
// None
|
|
||||||
//
|
|
||||||
// Arguments:
|
|
||||||
// message: The message sent from the JavaScript.
|
|
||||||
// Messages are in format "{method, params}", like json-rpc.
|
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
// Called when a message is received from a script.
|
|
||||||
//
|
|
||||||
function fromScript(message) {
|
|
||||||
switch (message.method) {
|
|
||||||
default:
|
|
||||||
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
signal sendSignalToWallet(var msg);
|
signal sendSignalToWallet(var msg);
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
168
interface/resources/qml/hifi/models/PSFListModel.qml
Normal file
168
interface/resources/qml/hifi/models/PSFListModel.qml
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
//
|
||||||
|
// PSFListModel.qml
|
||||||
|
// qml/hifi/commerce/common
|
||||||
|
//
|
||||||
|
// PSFListModel
|
||||||
|
// "PSF" stands for:
|
||||||
|
// - Paged
|
||||||
|
// - Sortable
|
||||||
|
// - Filterable
|
||||||
|
//
|
||||||
|
// Created by Zach Fox on 2018-05-15
|
||||||
|
// Copyright 2018 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.7
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: root;
|
||||||
|
// Used when printing debug statements
|
||||||
|
property string listModelName: endpoint;
|
||||||
|
|
||||||
|
// Parameters. Even if you override getPage, below, please set these for clarity and consistency, when applicable.
|
||||||
|
// E.g., your getPage function could refer to this sortKey, etc.
|
||||||
|
property string endpoint;
|
||||||
|
property string sortProperty; // Currently only handles sorting on one column, which fits with current needs and tables.
|
||||||
|
property bool sortAscending;
|
||||||
|
property string sortKey: !sortProperty ? '' : (sortProperty + "," + (sortAscending ? "ASC" : "DESC"));
|
||||||
|
property string searchFilter: "";
|
||||||
|
property string tagsFilter;
|
||||||
|
|
||||||
|
// QML fires the following changed handlers even when first instantiating the Item. So we need a guard against firing them too early.
|
||||||
|
property bool initialized: false;
|
||||||
|
Component.onCompleted: initialized = true;
|
||||||
|
onEndpointChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||||
|
onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||||
|
onSearchFilterChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||||
|
onTagsFilterChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||||
|
|
||||||
|
property int itemsPerPage: 100;
|
||||||
|
|
||||||
|
// State.
|
||||||
|
property int currentPageToRetrieve: 0; // 0 = before first page. -1 = we have them all. Otherwise 1-based page number.
|
||||||
|
property bool retrievedAtLeastOnePage: false;
|
||||||
|
// We normally clear on reset. But if we want to "refresh", we can delay clearing the model until we get a result.
|
||||||
|
// Not normally set directly, but rather by giving a truthy argument to getFirstPage(true);
|
||||||
|
property bool delayedClear: false;
|
||||||
|
function resetModel() {
|
||||||
|
if (!delayedClear) { root.clear(); }
|
||||||
|
currentPageToRetrieve = 1;
|
||||||
|
retrievedAtLeastOnePage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page processing.
|
||||||
|
|
||||||
|
// Override to return one property of data, and/or to transform the elements. Must return an array of model elements.
|
||||||
|
property var processPage: function (data) { return data; }
|
||||||
|
|
||||||
|
property var listView; // Optional. For debugging.
|
||||||
|
// Check consistency and call processPage.
|
||||||
|
function handlePage(error, response) {
|
||||||
|
var processed;
|
||||||
|
console.debug('handlePage', listModelName, additionalFirstPageRequested, error, JSON.stringify(response));
|
||||||
|
function fail(message) {
|
||||||
|
console.warn("Warning page fail", listModelName, JSON.stringify(message));
|
||||||
|
currentPageToRetrieve = -1;
|
||||||
|
requestPending = false;
|
||||||
|
delayedClear = false;
|
||||||
|
}
|
||||||
|
if (error || (response.status !== 'success')) {
|
||||||
|
return fail(error || response.status);
|
||||||
|
}
|
||||||
|
if (!requestPending) {
|
||||||
|
return fail("No request in flight.");
|
||||||
|
}
|
||||||
|
requestPending = false;
|
||||||
|
if (response.current_page && response.current_page !== currentPageToRetrieve) { // Not all endpoints specify this property.
|
||||||
|
return fail("Mismatched page, expected:" + currentPageToRetrieve);
|
||||||
|
}
|
||||||
|
processed = processPage(response.data || response);
|
||||||
|
if (response.total_pages && (response.total_pages === currentPageToRetrieve)) {
|
||||||
|
currentPageToRetrieve = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delayedClear) {
|
||||||
|
root.clear();
|
||||||
|
delayedClear = false;
|
||||||
|
}
|
||||||
|
root.append(processed); // FIXME keep index steady, and apply any post sort
|
||||||
|
retrievedAtLeastOnePage = true;
|
||||||
|
// Suppose two properties change at once, and both of their change handlers request a new first page.
|
||||||
|
// (An example is when the a filter box gets cleared with text in it, so that the search and tags are both reset.)
|
||||||
|
// Or suppose someone just types new search text quicker than the server response.
|
||||||
|
// In these cases, we would have multiple requests in flight, and signal based responses aren't generally very good
|
||||||
|
// at matching up the right handler with the right message. Rather than require all the APIs to carefully handle such,
|
||||||
|
// and also to cut down on useless requests, we take care of that case here.
|
||||||
|
if (additionalFirstPageRequested) {
|
||||||
|
console.debug('deferred getFirstPage', listModelName);
|
||||||
|
additionalFirstPageRequested = false;
|
||||||
|
getFirstPage('delayedClear');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function debugView(label) {
|
||||||
|
if (!listView) { return; }
|
||||||
|
console.debug(label, listModelName, 'perPage:', itemsPerPage, 'count:', listView.count,
|
||||||
|
'index:', listView.currentIndex, 'section:', listView.currentSection,
|
||||||
|
'atYBeginning:', listView.atYBeginning, 'atYEnd:', listView.atYEnd,
|
||||||
|
'y:', listView.y, 'contentY:', listView.contentY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override either http or getPage.
|
||||||
|
property var http; // An Item that has a request function.
|
||||||
|
property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty.
|
||||||
|
if (!http) { return console.warn("Neither http nor getPage was set for", listModelName); }
|
||||||
|
// If it is a path starting with slash, add the metaverseServer domain.
|
||||||
|
var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint;
|
||||||
|
var parameters = [
|
||||||
|
'per_page=' + itemsPerPage,
|
||||||
|
'page=' + currentPageToRetrieve
|
||||||
|
];
|
||||||
|
if (searchFilter) {
|
||||||
|
parameters.splice(parameters.length, 0, 'search=' + searchFilter);
|
||||||
|
}
|
||||||
|
if (sortKey) {
|
||||||
|
parameters.splice(parameters.length, 0, 'sort=' + sortKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
var parametersSeparator = /\?/.test(url) ? '&' : '?';
|
||||||
|
url = url + parametersSeparator + parameters.join('&');
|
||||||
|
console.debug('getPage', listModelName, currentPageToRetrieve);
|
||||||
|
http.request({uri: url}, handlePage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the show by retrieving data according to `getPage()`.
|
||||||
|
// It can be custom-defined by this item's Parent.
|
||||||
|
property var getFirstPage: function (delayClear) {
|
||||||
|
if (requestPending) {
|
||||||
|
console.debug('deferring getFirstPage', listModelName);
|
||||||
|
additionalFirstPageRequested = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delayedClear = !!delayClear;
|
||||||
|
resetModel();
|
||||||
|
requestPending = true;
|
||||||
|
console.debug("getFirstPage", listModelName, currentPageToRetrieve);
|
||||||
|
getPage();
|
||||||
|
}
|
||||||
|
property bool additionalFirstPageRequested: false;
|
||||||
|
property bool requestPending: false; // For de-bouncing getNextPage.
|
||||||
|
// This function, will get the _next_ page of data according to `getPage()`.
|
||||||
|
// It can be custom-defined by this item's Parent. Typical usage:
|
||||||
|
// ListView {
|
||||||
|
// id: theList
|
||||||
|
// model: thisPSFListModelId
|
||||||
|
// onAtYEndChanged: if (theList.atYEnd && !theList.atYBeginning) { thisPSFListModelId.getNextPage(); }
|
||||||
|
// ...}
|
||||||
|
property var getNextPage: function () {
|
||||||
|
if (requestPending || currentPageToRetrieve < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentPageToRetrieve++;
|
||||||
|
console.debug("getNextPage", listModelName, currentPageToRetrieve);
|
||||||
|
requestPending = true;
|
||||||
|
getPage();
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,41 +34,16 @@ StackView {
|
||||||
height: parent !== null ? parent.height : undefined
|
height: parent !== null ? parent.height : undefined
|
||||||
property int cardWidth: 212;
|
property int cardWidth: 212;
|
||||||
property int cardHeight: 152;
|
property int cardHeight: 152;
|
||||||
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
|
|
||||||
property var tablet: null;
|
property var tablet: null;
|
||||||
|
|
||||||
// This version only implements rpc(method, parameters, callback(error, result)) calls initiated from here, not initiated from .js, nor "notifications".
|
RootHttpRequest { id: http; }
|
||||||
property var rpcCalls: ({});
|
|
||||||
property var rpcCounter: 0;
|
|
||||||
signal sendToScript(var message);
|
signal sendToScript(var message);
|
||||||
function rpc(method, parameters, callback) {
|
|
||||||
console.debug('TabletAddressDialog: rpc: method = ', method, 'parameters = ', parameters, 'callback = ', callback)
|
|
||||||
|
|
||||||
rpcCalls[rpcCounter] = callback;
|
|
||||||
var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"};
|
|
||||||
sendToScript(message);
|
|
||||||
}
|
|
||||||
function fromScript(message) {
|
function fromScript(message) {
|
||||||
if (message.method === 'refreshFeeds') {
|
switch (message.method) {
|
||||||
var feeds = [happeningNow, places, snapshots];
|
case 'http.response':
|
||||||
console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds);
|
http.handleHttpResponse(message);
|
||||||
|
break;
|
||||||
feeds.forEach(function(feed) {
|
|
||||||
feed.protocol = encodeURIComponent(message.protocolSignature);
|
|
||||||
Qt.callLater(feed.fillDestinations);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var callback = rpcCalls[message.id];
|
|
||||||
if (!callback) {
|
|
||||||
// FIXME: We often recieve very long messages here, the logging of which is drastically slowing down the main thread
|
|
||||||
//console.log('No callback for message fromScript', JSON.stringify(message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
delete rpcCalls[message.id];
|
|
||||||
callback(message.error, message.result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component { id: tabletWebView; TabletWebView {} }
|
Component { id: tabletWebView; TabletWebView {} }
|
||||||
|
@ -346,12 +321,11 @@ StackView {
|
||||||
width: parent.width;
|
width: parent.width;
|
||||||
cardWidth: 312 + (2 * 4);
|
cardWidth: 312 + (2 * 4);
|
||||||
cardHeight: 163 + (2 * 4);
|
cardHeight: 163 + (2 * 4);
|
||||||
metaverseServerUrl: addressBarDialog.metaverseServerUrl;
|
|
||||||
labelText: 'HAPPENING NOW';
|
labelText: 'HAPPENING NOW';
|
||||||
actions: 'announcement';
|
actions: 'announcement';
|
||||||
filter: addressLine.text;
|
filter: addressLine.text;
|
||||||
goFunction: goCard;
|
goFunction: goCard;
|
||||||
rpc: root.rpc;
|
http: http;
|
||||||
}
|
}
|
||||||
Feed {
|
Feed {
|
||||||
id: places;
|
id: places;
|
||||||
|
@ -359,12 +333,11 @@ StackView {
|
||||||
cardWidth: 210;
|
cardWidth: 210;
|
||||||
cardHeight: 110 + messageHeight;
|
cardHeight: 110 + messageHeight;
|
||||||
messageHeight: 44;
|
messageHeight: 44;
|
||||||
metaverseServerUrl: addressBarDialog.metaverseServerUrl;
|
|
||||||
labelText: 'PLACES';
|
labelText: 'PLACES';
|
||||||
actions: 'concurrency';
|
actions: 'concurrency';
|
||||||
filter: addressLine.text;
|
filter: addressLine.text;
|
||||||
goFunction: goCard;
|
goFunction: goCard;
|
||||||
rpc: root.rpc;
|
http: http;
|
||||||
}
|
}
|
||||||
Feed {
|
Feed {
|
||||||
id: snapshots;
|
id: snapshots;
|
||||||
|
@ -373,12 +346,11 @@ StackView {
|
||||||
cardHeight: 75 + messageHeight + 4;
|
cardHeight: 75 + messageHeight + 4;
|
||||||
messageHeight: 32;
|
messageHeight: 32;
|
||||||
textPadding: 6;
|
textPadding: 6;
|
||||||
metaverseServerUrl: addressBarDialog.metaverseServerUrl;
|
|
||||||
labelText: 'RECENT SNAPS';
|
labelText: 'RECENT SNAPS';
|
||||||
actions: 'snapshot';
|
actions: 'snapshot';
|
||||||
filter: addressLine.text;
|
filter: addressLine.text;
|
||||||
goFunction: goCard;
|
goFunction: goCard;
|
||||||
rpc: root.rpc;
|
http: http;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,18 @@ Item {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeDialog() {
|
||||||
|
if (openMessage != null) {
|
||||||
|
openMessage.destroy();
|
||||||
|
openMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openModal != null) {
|
||||||
|
openModal.destroy();
|
||||||
|
openModal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isUrlLoaded(url) {
|
function isUrlLoaded(url) {
|
||||||
if (currentApp >= 0) {
|
if (currentApp >= 0) {
|
||||||
var currentAppUrl = tabletApps.get(currentApp).appUrl;
|
var currentAppUrl = tabletApps.get(currentApp).appUrl;
|
||||||
|
|
|
@ -468,13 +468,14 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) {
|
||||||
_shouldRender = shouldRenderAvatars;
|
_shouldRender = shouldRenderAvatars;
|
||||||
const render::ScenePointer& scene = qApp->getMain3DScene();
|
const render::ScenePointer& scene = qApp->getMain3DScene();
|
||||||
render::Transaction transaction;
|
render::Transaction transaction;
|
||||||
|
auto avatarHashCopy = getHashCopy();
|
||||||
if (_shouldRender) {
|
if (_shouldRender) {
|
||||||
for (auto avatarData : _avatarHash) {
|
for (auto avatarData : avatarHashCopy) {
|
||||||
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||||
avatar->addToScene(avatar, scene, transaction);
|
avatar->addToScene(avatar, scene, transaction);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (auto avatarData : _avatarHash) {
|
for (auto avatarData : avatarHashCopy) {
|
||||||
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||||
avatar->removeFromScene(avatar, scene, transaction);
|
avatar->removeFromScene(avatar, scene, transaction);
|
||||||
}
|
}
|
||||||
|
@ -514,7 +515,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
||||||
|
|
||||||
glm::vec3 normDirection = glm::normalize(ray.direction);
|
glm::vec3 normDirection = glm::normalize(ray.direction);
|
||||||
|
|
||||||
for (auto avatarData : _avatarHash) {
|
auto avatarHashCopy = getHashCopy();
|
||||||
|
for (auto avatarData : avatarHashCopy) {
|
||||||
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||||
if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) ||
|
if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) ||
|
||||||
(avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) {
|
(avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) {
|
||||||
|
|
|
@ -134,8 +134,14 @@ void Ledger::balance(const QStringList& keys) {
|
||||||
keysQuery("balance", "balanceSuccess", "balanceFailure");
|
keysQuery("balance", "balanceSuccess", "balanceFailure");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ledger::inventory(const QStringList& keys) {
|
void Ledger::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) {
|
||||||
keysQuery("inventory", "inventorySuccess", "inventoryFailure");
|
QJsonObject params;
|
||||||
|
params["edition_filter"] = editionFilter;
|
||||||
|
params["type_filter"] = typeFilter;
|
||||||
|
params["title_filter"] = titleFilter;
|
||||||
|
params["page"] = page;
|
||||||
|
params["per_page"] = perPage;
|
||||||
|
keysQuery("inventory", "inventorySuccess", "inventoryFailure", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString hfcString(const QJsonValue& sentValue, const QJsonValue& receivedValue) {
|
QString hfcString(const QJsonValue& sentValue, const QJsonValue& receivedValue) {
|
||||||
|
@ -260,9 +266,9 @@ void Ledger::historyFailure(QNetworkReply& reply) {
|
||||||
failResponse("history", reply);
|
failResponse("history", reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ledger::history(const QStringList& keys, const int& pageNumber) {
|
void Ledger::history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage) {
|
||||||
QJsonObject params;
|
QJsonObject params;
|
||||||
params["per_page"] = 100;
|
params["per_page"] = itemsPerPage;
|
||||||
params["page"] = pageNumber;
|
params["page"] = pageNumber;
|
||||||
keysQuery("history", "historySuccess", "historyFailure", params);
|
keysQuery("history", "historySuccess", "historyFailure", params);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ public:
|
||||||
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
|
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
|
||||||
bool receiveAt(const QString& hfc_key, const QString& signing_key);
|
bool receiveAt(const QString& hfc_key, const QString& signing_key);
|
||||||
void balance(const QStringList& keys);
|
void balance(const QStringList& keys);
|
||||||
void inventory(const QStringList& keys);
|
void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage);
|
||||||
void history(const QStringList& keys, const int& pageNumber);
|
void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage);
|
||||||
void account();
|
void account();
|
||||||
void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false);
|
void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false);
|
||||||
void certificateInfo(const QString& certificateId);
|
void certificateInfo(const QString& certificateId);
|
||||||
|
|
|
@ -105,21 +105,21 @@ void QmlCommerce::balance() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QmlCommerce::inventory() {
|
void QmlCommerce::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) {
|
||||||
auto ledger = DependencyManager::get<Ledger>();
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
auto wallet = DependencyManager::get<Wallet>();
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
||||||
if (!cachedPublicKeys.isEmpty()) {
|
if (!cachedPublicKeys.isEmpty()) {
|
||||||
ledger->inventory(cachedPublicKeys);
|
ledger->inventory(editionFilter, typeFilter, titleFilter, page, perPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QmlCommerce::history(const int& pageNumber) {
|
void QmlCommerce::history(const int& pageNumber, const int& itemsPerPage) {
|
||||||
auto ledger = DependencyManager::get<Ledger>();
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
auto wallet = DependencyManager::get<Wallet>();
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
||||||
if (!cachedPublicKeys.isEmpty()) {
|
if (!cachedPublicKeys.isEmpty()) {
|
||||||
ledger->history(cachedPublicKeys, pageNumber);
|
ledger->history(cachedPublicKeys, pageNumber, itemsPerPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,10 +227,13 @@ QString QmlCommerce::getInstalledApps() {
|
||||||
QString scriptURL = appFileJsonObject["scriptURL"].toString();
|
QString scriptURL = appFileJsonObject["scriptURL"].toString();
|
||||||
|
|
||||||
// If the script .app.json is on the user's local disk but the associated script isn't running
|
// If the script .app.json is on the user's local disk but the associated script isn't running
|
||||||
// for some reason, start that script again.
|
// for some reason (i.e. the user stopped it from Running Scripts),
|
||||||
|
// delete the .app.json from the user's local disk.
|
||||||
if (!runningScripts.contains(scriptURL)) {
|
if (!runningScripts.contains(scriptURL)) {
|
||||||
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptURL.trimmed())).isNull()) {
|
if (!appFile.remove()) {
|
||||||
qCDebug(commerce) << "Couldn't start script while checking installed apps.";
|
qCWarning(commerce)
|
||||||
|
<< "Couldn't delete local .app.json file (app's script isn't running). App filename is:"
|
||||||
|
<< appFileName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -73,8 +73,8 @@ protected:
|
||||||
|
|
||||||
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
|
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
|
||||||
Q_INVOKABLE void balance();
|
Q_INVOKABLE void balance();
|
||||||
Q_INVOKABLE void inventory();
|
Q_INVOKABLE void inventory(const QString& editionFilter = QString(), const QString& typeFilter = QString(), const QString& titleFilter = QString(), const int& page = 1, const int& perPage = 20);
|
||||||
Q_INVOKABLE void history(const int& pageNumber);
|
Q_INVOKABLE void history(const int& pageNumber, const int& itemsPerPage = 100);
|
||||||
Q_INVOKABLE void generateKeyPair();
|
Q_INVOKABLE void generateKeyPair();
|
||||||
Q_INVOKABLE void account();
|
Q_INVOKABLE void account();
|
||||||
|
|
||||||
|
|
|
@ -93,10 +93,18 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) {
|
||||||
static const QUrl url("dialogs/TabletConnectionFailureDialog.qml");
|
static const QUrl url("dialogs/TabletConnectionFailureDialog.qml");
|
||||||
auto hmd = DependencyManager::get<HMDScriptingInterface>();
|
auto hmd = DependencyManager::get<HMDScriptingInterface>();
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
_dialogCreatedWhileShown = tablet->property("tabletShown").toBool();
|
||||||
tablet->initialScreen(url);
|
tablet->initialScreen(url);
|
||||||
if (!hmd->getShouldShowTablet()) {
|
if (!hmd->getShouldShowTablet()) {
|
||||||
hmd->openTablet();
|
hmd->openTablet();
|
||||||
}
|
}
|
||||||
|
} else if (tablet->isPathLoaded(url)) {
|
||||||
|
tablet->closeDialog();
|
||||||
|
tablet->gotoHomeScreen();
|
||||||
|
if (!_dialogCreatedWhileShown) {
|
||||||
|
hmd->closeTablet();
|
||||||
|
}
|
||||||
|
_dialogCreatedWhileShown = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,7 @@ private:
|
||||||
QPointer<OctreeStatsDialog> _octreeStatsDialog;
|
QPointer<OctreeStatsDialog> _octreeStatsDialog;
|
||||||
QPointer<TestingDialog> _testingDialog;
|
QPointer<TestingDialog> _testingDialog;
|
||||||
QPointer<DomainConnectionDialog> _domainConnectionDialog;
|
QPointer<DomainConnectionDialog> _domainConnectionDialog;
|
||||||
|
bool _dialogCreatedWhileShown { false };
|
||||||
bool _addressBarVisible { false };
|
bool _addressBarVisible { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
#include "InterfaceLogging.h"
|
#include "InterfaceLogging.h"
|
||||||
|
|
||||||
OverlayConductor::OverlayConductor() {
|
OverlayConductor::OverlayConductor() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OverlayConductor::~OverlayConductor() {
|
OverlayConductor::~OverlayConductor() {
|
||||||
|
@ -33,8 +32,8 @@ bool OverlayConductor::headOutsideOverlay() const {
|
||||||
glm::vec3 uiPos = uiTransform.getTranslation();
|
glm::vec3 uiPos = uiTransform.getTranslation();
|
||||||
glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
|
glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
|
||||||
|
|
||||||
const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface.
|
const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface.
|
||||||
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
|
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
|
||||||
if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE ||
|
if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE ||
|
||||||
glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) {
|
glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -43,10 +42,9 @@ bool OverlayConductor::headOutsideOverlay() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverlayConductor::updateAvatarIsAtRest() {
|
bool OverlayConductor::updateAvatarIsAtRest() {
|
||||||
|
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
|
|
||||||
const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
|
const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
|
||||||
const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms
|
const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms
|
||||||
|
|
||||||
const float AT_REST_THRESHOLD = 0.01f;
|
const float AT_REST_THRESHOLD = 0.01f;
|
||||||
|
@ -69,31 +67,6 @@ bool OverlayConductor::updateAvatarIsAtRest() {
|
||||||
return _currentAtRest;
|
return _currentAtRest;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverlayConductor::updateAvatarHasDriveInput() {
|
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
|
||||||
|
|
||||||
const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms
|
|
||||||
const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s
|
|
||||||
|
|
||||||
bool desiredDriving = myAvatar->hasDriveInput();
|
|
||||||
if (desiredDriving != _desiredDriving) {
|
|
||||||
// start timer
|
|
||||||
_desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS);
|
|
||||||
}
|
|
||||||
|
|
||||||
_desiredDriving = desiredDriving;
|
|
||||||
|
|
||||||
if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) {
|
|
||||||
// timer expired
|
|
||||||
// change state!
|
|
||||||
_currentDriving = _desiredDriving;
|
|
||||||
// disable timer
|
|
||||||
_desiredDrivingTimer = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _currentDriving;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OverlayConductor::centerUI() {
|
void OverlayConductor::centerUI() {
|
||||||
// place the overlay at the current hmd position in sensor space
|
// place the overlay at the current hmd position in sensor space
|
||||||
auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose());
|
auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose());
|
||||||
|
@ -115,20 +88,19 @@ void OverlayConductor::update(float dt) {
|
||||||
_hmdMode = false;
|
_hmdMode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool prevDriving = _currentDriving;
|
|
||||||
bool isDriving = updateAvatarHasDriveInput();
|
|
||||||
bool drivingChanged = prevDriving != isDriving;
|
|
||||||
bool isAtRest = updateAvatarIsAtRest();
|
bool isAtRest = updateAvatarIsAtRest();
|
||||||
|
bool isMoving = !isAtRest;
|
||||||
|
|
||||||
bool shouldRecenter = false;
|
bool shouldRecenter = false;
|
||||||
|
|
||||||
if (_flags & SuppressedByDrive) {
|
if (_flags & SuppressedByMove) {
|
||||||
if (!isDriving) {
|
if (!isMoving) {
|
||||||
_flags &= ~SuppressedByDrive;
|
_flags &= ~SuppressedByMove;
|
||||||
shouldRecenter = true;
|
shouldRecenter = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
|
if (myAvatar->getClearOverlayWhenMoving() && isMoving) {
|
||||||
_flags |= SuppressedByDrive;
|
_flags |= SuppressedByMove;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +115,6 @@ void OverlayConductor::update(float dt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
|
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
|
||||||
if (targetVisible != currentVisible) {
|
if (targetVisible != currentVisible) {
|
||||||
offscreenUi->setPinned(!targetVisible);
|
offscreenUi->setPinned(!targetVisible);
|
||||||
|
|
|
@ -23,23 +23,17 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool headOutsideOverlay() const;
|
bool headOutsideOverlay() const;
|
||||||
bool updateAvatarHasDriveInput();
|
|
||||||
bool updateAvatarIsAtRest();
|
bool updateAvatarIsAtRest();
|
||||||
|
|
||||||
enum SupressionFlags {
|
enum SupressionFlags {
|
||||||
SuppressedByDrive = 0x01,
|
SuppressedByMove = 0x01,
|
||||||
SuppressedByHead = 0x02,
|
SuppressedByHead = 0x02,
|
||||||
SuppressMask = 0x03,
|
SuppressMask = 0x03,
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t _flags { SuppressedByDrive };
|
uint8_t _flags { SuppressedByMove };
|
||||||
bool _hmdMode { false };
|
bool _hmdMode { false };
|
||||||
|
|
||||||
// used by updateAvatarHasDriveInput
|
|
||||||
uint64_t _desiredDrivingTimer { 0 };
|
|
||||||
bool _desiredDriving { false };
|
|
||||||
bool _currentDriving { false };
|
|
||||||
|
|
||||||
// used by updateAvatarIsAtRest
|
// used by updateAvatarIsAtRest
|
||||||
uint64_t _desiredAtRestTimer { 0 };
|
uint64_t _desiredAtRestTimer { 0 };
|
||||||
bool _desiredAtRest { true };
|
bool _desiredAtRest { true };
|
||||||
|
|
|
@ -100,24 +100,32 @@ void ModelOverlay::update(float deltatime) {
|
||||||
processMaterials();
|
processMaterials();
|
||||||
emit DependencyManager::get<scriptable::ModelProviderFactory>()->modelAddedToScene(getID(), NestableType::Overlay, _model);
|
emit DependencyManager::get<scriptable::ModelProviderFactory>()->modelAddedToScene(getID(), NestableType::Overlay, _model);
|
||||||
}
|
}
|
||||||
|
bool metaDirty = false;
|
||||||
if (_visibleDirty) {
|
if (_visibleDirty) {
|
||||||
_visibleDirty = false;
|
_visibleDirty = false;
|
||||||
// don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true
|
// don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true
|
||||||
uint8_t modelRenderTagMask = (_isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW);
|
uint8_t modelRenderTagMask = (_isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW);
|
||||||
_model->setTagMask(modelRenderTagMask, scene);
|
_model->setTagMask(modelRenderTagMask, scene);
|
||||||
_model->setVisibleInScene(getVisible(), scene);
|
_model->setVisibleInScene(getVisible(), scene);
|
||||||
|
metaDirty = true;
|
||||||
}
|
}
|
||||||
if (_drawInFrontDirty) {
|
if (_drawInFrontDirty) {
|
||||||
_drawInFrontDirty = false;
|
_drawInFrontDirty = false;
|
||||||
_model->setLayeredInFront(getDrawInFront(), scene);
|
_model->setLayeredInFront(getDrawInFront(), scene);
|
||||||
|
metaDirty = true;
|
||||||
}
|
}
|
||||||
if (_drawInHUDDirty) {
|
if (_drawInHUDDirty) {
|
||||||
_drawInHUDDirty = false;
|
_drawInHUDDirty = false;
|
||||||
_model->setLayeredInHUD(getDrawHUDLayer(), scene);
|
_model->setLayeredInHUD(getDrawHUDLayer(), scene);
|
||||||
|
metaDirty = true;
|
||||||
}
|
}
|
||||||
if (_groupCulledDirty) {
|
if (_groupCulledDirty) {
|
||||||
_groupCulledDirty = false;
|
_groupCulledDirty = false;
|
||||||
_model->setGroupCulled(_isGroupCulled);
|
_model->setGroupCulled(_isGroupCulled, scene);
|
||||||
|
metaDirty = true;
|
||||||
|
}
|
||||||
|
if (metaDirty) {
|
||||||
|
transaction.updateItem<Overlay>(getRenderItemID(), [](Overlay& data) {});
|
||||||
}
|
}
|
||||||
scene->enqueueTransaction(transaction);
|
scene->enqueueTransaction(transaction);
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWe
|
||||||
avatar->setSessionUUID(sessionUUID);
|
avatar->setSessionUUID(sessionUUID);
|
||||||
avatar->setOwningAvatarMixer(mixerWeakPointer);
|
avatar->setOwningAvatarMixer(mixerWeakPointer);
|
||||||
|
|
||||||
|
// addAvatar is only called from newOrExistingAvatar, which already locks _hashLock
|
||||||
_avatarHash.insert(sessionUUID, avatar);
|
_avatarHash.insert(sessionUUID, avatar);
|
||||||
emit avatarAddedEvent(sessionUUID);
|
emit avatarAddedEvent(sessionUUID);
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ class AvatarHashMap : public QObject, public Dependency {
|
||||||
public:
|
public:
|
||||||
AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; }
|
AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; }
|
||||||
const AvatarHash getHashCopy() const { QReadLocker lock(&_hashLock); return _avatarHash; }
|
const AvatarHash getHashCopy() const { QReadLocker lock(&_hashLock); return _avatarHash; }
|
||||||
int size() { return _avatarHash.size(); }
|
int size() { QReadLocker lock(&_hashLock); return _avatarHash.size(); }
|
||||||
|
|
||||||
// Currently, your own avatar will be included as the null avatar id.
|
// Currently, your own avatar will be included as the null avatar id.
|
||||||
|
|
||||||
|
@ -152,8 +152,6 @@ protected:
|
||||||
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
|
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
|
||||||
|
|
||||||
AvatarHash _avatarHash;
|
AvatarHash _avatarHash;
|
||||||
// "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock.
|
|
||||||
// If you read from a different thread, you must read-lock the _hashLock. (Scripted write access is not supported).
|
|
||||||
mutable QReadWriteLock _hashLock;
|
mutable QReadWriteLock _hashLock;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -157,18 +157,19 @@ void TextureBaker::processTexture() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat());
|
|
||||||
if (name == nullptr) {
|
|
||||||
handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// attempt to write the baked texture to the destination file path
|
// attempt to write the baked texture to the destination file path
|
||||||
{
|
if (memKTX->_header.isCompressed()) {
|
||||||
|
const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat());
|
||||||
|
if (name == nullptr) {
|
||||||
|
handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
|
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
|
||||||
const size_t length = memKTX->_storage->size();
|
const size_t length = memKTX->_storage->size();
|
||||||
|
|
||||||
auto fileName = _baseFilename + BAKED_TEXTURE_BCN_SUFFIX;
|
auto fileName = _baseFilename + "_" + name + ".ktx";
|
||||||
auto filePath = _outputDirectory.absoluteFilePath(fileName);
|
auto filePath = _outputDirectory.absoluteFilePath(fileName);
|
||||||
QFile bakedTextureFile { filePath };
|
QFile bakedTextureFile { filePath };
|
||||||
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
|
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
|
||||||
|
@ -177,6 +178,18 @@ void TextureBaker::processTexture() {
|
||||||
}
|
}
|
||||||
_outputFiles.push_back(filePath);
|
_outputFiles.push_back(filePath);
|
||||||
meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName;
|
meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName;
|
||||||
|
} else {
|
||||||
|
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
|
||||||
|
const size_t length = memKTX->_storage->size();
|
||||||
|
|
||||||
|
auto fileName = _baseFilename + ".ktx";
|
||||||
|
auto filePath = _outputDirectory.absoluteFilePath(fileName);
|
||||||
|
QFile ktxTextureFile { filePath };
|
||||||
|
if (!ktxTextureFile.open(QIODevice::WriteOnly) || ktxTextureFile.write(data, length) == -1) {
|
||||||
|
handleError("Could not write ktx texture for " + _textureURL.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_outputFiles.push_back(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,8 @@ layout(std140) uniform particleBuffer {
|
||||||
ParticleUniforms particle;
|
ParticleUniforms particle;
|
||||||
};
|
};
|
||||||
|
|
||||||
in vec3 inPosition;
|
layout(location=0) in vec3 inPosition;
|
||||||
in vec2 inColor; // This is actual Lifetime + Seed
|
layout(location=2) in vec2 inColor; // This is actual Lifetime + Seed
|
||||||
|
|
||||||
out vec4 varColor;
|
out vec4 varColor;
|
||||||
out vec2 varTexcoord;
|
out vec2 varTexcoord;
|
||||||
|
|
|
@ -157,7 +157,11 @@ void AddressManager::storeCurrentAddress() {
|
||||||
// be loaded over http(s)
|
// be loaded over http(s)
|
||||||
// url.scheme() == URL_SCHEME_HTTP ||
|
// url.scheme() == URL_SCHEME_HTTP ||
|
||||||
// url.scheme() == URL_SCHEME_HTTPS ||
|
// url.scheme() == URL_SCHEME_HTTPS ||
|
||||||
currentAddressHandle.set(url);
|
if (isConnected()) {
|
||||||
|
currentAddressHandle.set(url);
|
||||||
|
} else {
|
||||||
|
qCWarning(networking) << "Ignoring attempt to save current address because not connected to domain:" << url;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qCWarning(networking) << "Ignoring attempt to save current address with an invalid url:" << url;
|
qCWarning(networking) << "Ignoring attempt to save current address with an invalid url:" << url;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,11 @@ ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(at
|
||||||
_thread.start();
|
_thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResourceManager::~ResourceManager() {
|
||||||
|
_thread.terminate();
|
||||||
|
_thread.wait();
|
||||||
|
}
|
||||||
|
|
||||||
void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) {
|
void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) {
|
||||||
QMutexLocker locker(&_prefixMapLock);
|
QMutexLocker locker(&_prefixMapLock);
|
||||||
if (replacement.isEmpty()) {
|
if (replacement.isEmpty()) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ class ResourceManager: public QObject, public Dependency {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ResourceManager(bool atpSupportEnabled = true);
|
ResourceManager(bool atpSupportEnabled = true);
|
||||||
|
~ResourceManager();
|
||||||
|
|
||||||
void setUrlPrefixOverride(const QString& prefix, const QString& replacement);
|
void setUrlPrefixOverride(const QString& prefix, const QString& replacement);
|
||||||
QString normalizeURL(const QString& urlString);
|
QString normalizeURL(const QString& urlString);
|
||||||
|
|
|
@ -431,6 +431,19 @@ bool TabletProxy::isMessageDialogOpen() {
|
||||||
return result.toBool();
|
return result.toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TabletProxy::closeDialog() {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "closeDialog");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_qmlTabletRoot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(_qmlTabletRoot, "closeDialog");
|
||||||
|
}
|
||||||
|
|
||||||
void TabletProxy::emitWebEvent(const QVariant& msg) {
|
void TabletProxy::emitWebEvent(const QVariant& msg) {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QMetaObject::invokeMethod(this, "emitWebEvent", Q_ARG(QVariant, msg));
|
QMetaObject::invokeMethod(this, "emitWebEvent", Q_ARG(QVariant, msg));
|
||||||
|
|
|
@ -308,6 +308,12 @@ public:
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE bool isMessageDialogOpen();
|
Q_INVOKABLE bool isMessageDialogOpen();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Close any open dialogs.
|
||||||
|
* @function TabletProxy#closeDialog
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void closeDialog();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Creates a new button, adds it to this and returns it.
|
* Creates a new button, adds it to this and returns it.
|
||||||
* @function TabletProxy#addButton
|
* @function TabletProxy#addButton
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
var DEFAULT_SCRIPTS_COMBINED = [
|
var DEFAULT_SCRIPTS_COMBINED = [
|
||||||
|
"system/request-service.js",
|
||||||
"system/progress.js",
|
"system/progress.js",
|
||||||
"system/away.js",
|
"system/away.js",
|
||||||
"system/audio.js",
|
"system/audio.js",
|
||||||
|
|
|
@ -1119,7 +1119,7 @@ function startRadar() {
|
||||||
|
|
||||||
function endRadar() {
|
function endRadar() {
|
||||||
printd("-- endRadar");
|
printd("-- endRadar");
|
||||||
Camera.mode = "first person";
|
Camera.mode = "third person";
|
||||||
radar = false;
|
radar = false;
|
||||||
|
|
||||||
Controller.setVPadEnabled(true);
|
Controller.setVPadEnabled(true);
|
||||||
|
|
|
@ -511,6 +511,9 @@
|
||||||
case 'wallet_availableUpdatesReceived':
|
case 'wallet_availableUpdatesReceived':
|
||||||
// NOP
|
// NOP
|
||||||
break;
|
break;
|
||||||
|
case 'http.request':
|
||||||
|
// Handled elsewhere, don't log.
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
print('Unrecognized message from QML:', JSON.stringify(message));
|
print('Unrecognized message from QML:', JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,7 +273,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
this.shouldSendStart = false;
|
this.shouldSendStart = false;
|
||||||
this.equipedWithSecondary = false;
|
this.equipedWithSecondary = false;
|
||||||
this.handHasBeenRightsideUp = false;
|
this.handHasBeenRightsideUp = false;
|
||||||
this.mouseEquip = false;
|
|
||||||
|
|
||||||
this.parameters = makeDispatcherModuleParameters(
|
this.parameters = makeDispatcherModuleParameters(
|
||||||
300,
|
300,
|
||||||
|
@ -283,11 +282,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
|
|
||||||
var equipHotspotBuddy = new EquipHotspotBuddy();
|
var equipHotspotBuddy = new EquipHotspotBuddy();
|
||||||
|
|
||||||
this.setMessageGrabData = function(entityProperties, mouseEquip) {
|
this.setMessageGrabData = function(entityProperties) {
|
||||||
if (entityProperties) {
|
if (entityProperties) {
|
||||||
this.messageGrabEntity = true;
|
this.messageGrabEntity = true;
|
||||||
this.grabEntityProps = entityProperties;
|
this.grabEntityProps = entityProperties;
|
||||||
this.mouseEquip = mouseEquip;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -584,7 +582,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
this.targetEntityID = null;
|
this.targetEntityID = null;
|
||||||
this.messageGrabEntity = false;
|
this.messageGrabEntity = false;
|
||||||
this.grabEntityProps = null;
|
this.grabEntityProps = null;
|
||||||
this.mouseEquip = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateInputs = function (controllerData) {
|
this.updateInputs = function (controllerData) {
|
||||||
|
@ -630,14 +627,12 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
|
|
||||||
// if the potentialHotspot is cloneable, clone it and return it
|
// if the potentialHotspot is cloneable, clone it and return it
|
||||||
// if the potentialHotspot os not cloneable and locked return null
|
// if the potentialHotspot os not cloneable and locked return null
|
||||||
|
|
||||||
if (potentialEquipHotspot &&
|
if (potentialEquipHotspot &&
|
||||||
(((this.triggerSmoothedSqueezed() || this.secondarySmoothedSqueezed()) && !this.waitForTriggerRelease) ||
|
(((this.triggerSmoothedSqueezed() || this.secondarySmoothedSqueezed()) && !this.waitForTriggerRelease) ||
|
||||||
this.messageGrabEntity)) {
|
this.messageGrabEntity)) {
|
||||||
this.grabbedHotspot = potentialEquipHotspot;
|
this.grabbedHotspot = potentialEquipHotspot;
|
||||||
this.targetEntityID = this.grabbedHotspot.entityID;
|
this.targetEntityID = this.grabbedHotspot.entityID;
|
||||||
this.startEquipEntity(controllerData);
|
this.startEquipEntity(controllerData);
|
||||||
this.messageGrabEntity = false;
|
|
||||||
this.equipedWithSecondary = this.secondarySmoothedSqueezed();
|
this.equipedWithSecondary = this.secondarySmoothedSqueezed();
|
||||||
return makeRunningValues(true, [potentialEquipHotspot.entityID], []);
|
return makeRunningValues(true, [potentialEquipHotspot.entityID], []);
|
||||||
} else {
|
} else {
|
||||||
|
@ -661,7 +656,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
var timestamp = Date.now();
|
var timestamp = Date.now();
|
||||||
this.updateInputs(controllerData);
|
this.updateInputs(controllerData);
|
||||||
|
|
||||||
if (!this.mouseEquip && !this.isTargetIDValid(controllerData)) {
|
if (!this.messageGrabEntity && !this.isTargetIDValid(controllerData)) {
|
||||||
this.endEquipEntity();
|
this.endEquipEntity();
|
||||||
return makeRunningValues(false, [], []);
|
return makeRunningValues(false, [], []);
|
||||||
}
|
}
|
||||||
|
@ -762,9 +757,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity;
|
var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity;
|
||||||
var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES);
|
var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES);
|
||||||
entityProperties.id = data.entityID;
|
entityProperties.id = data.entityID;
|
||||||
var mouseEquip = false;
|
equipModule.setMessageGrabData(entityProperties);
|
||||||
equipModule.setMessageGrabData(entityProperties, mouseEquip);
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message);
|
print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message);
|
||||||
}
|
}
|
||||||
|
@ -812,15 +805,14 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
var distanceToLeftHand = Vec3.distance(entityProperties.position, leftHandPosition);
|
var distanceToLeftHand = Vec3.distance(entityProperties.position, leftHandPosition);
|
||||||
var leftHandAvailable = leftEquipEntity.targetEntityID === null;
|
var leftHandAvailable = leftEquipEntity.targetEntityID === null;
|
||||||
var rightHandAvailable = rightEquipEntity.targetEntityID === null;
|
var rightHandAvailable = rightEquipEntity.targetEntityID === null;
|
||||||
var mouseEquip = true;
|
|
||||||
if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) {
|
if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) {
|
||||||
// clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags)
|
// clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags)
|
||||||
clearGrabActions(entityID);
|
clearGrabActions(entityID);
|
||||||
rightEquipEntity.setMessageGrabData(entityProperties, mouseEquip);
|
rightEquipEntity.setMessageGrabData(entityProperties);
|
||||||
} else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) {
|
} else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) {
|
||||||
// clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags)
|
// clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags)
|
||||||
clearGrabActions(entityID);
|
clearGrabActions(entityID);
|
||||||
leftEquipEntity.setMessageGrabData(entityProperties, mouseEquip);
|
leftEquipEntity.setMessageGrabData(entityProperties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,7 +282,7 @@ Script.include("/~/system/libraries/Xform.js");
|
||||||
this.previousRoomControllerPosition = roomControllerPosition;
|
this.previousRoomControllerPosition = roomControllerPosition;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.endNearGrabAction = function () {
|
this.endFarGrabAction = function () {
|
||||||
ensureDynamic(this.grabbedThingID);
|
ensureDynamic(this.grabbedThingID);
|
||||||
this.distanceHolding = false;
|
this.distanceHolding = false;
|
||||||
this.distanceRotating = false;
|
this.distanceRotating = false;
|
||||||
|
@ -402,7 +402,7 @@ Script.include("/~/system/libraries/Xform.js");
|
||||||
this.run = function (controllerData) {
|
this.run = function (controllerData) {
|
||||||
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE ||
|
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE ||
|
||||||
this.notPointingAtEntity(controllerData) || this.targetIsNull()) {
|
this.notPointingAtEntity(controllerData) || this.targetIsNull()) {
|
||||||
this.endNearGrabAction();
|
this.endFarGrabAction();
|
||||||
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
|
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
|
||||||
this.highlightedEntity);
|
this.highlightedEntity);
|
||||||
this.highlightedEntity = null;
|
this.highlightedEntity = null;
|
||||||
|
@ -430,11 +430,12 @@ Script.include("/~/system/libraries/Xform.js");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.actionID) {
|
if (this.actionID) {
|
||||||
// if we are doing a distance grab and the object gets close enough to the controller,
|
// if we are doing a distance grab and the object or tablet gets close enough to the controller,
|
||||||
// stop the far-grab so the near-grab or equip can take over.
|
// stop the far-grab so the near-grab or equip can take over.
|
||||||
for (var k = 0; k < nearGrabReadiness.length; k++) {
|
for (var k = 0; k < nearGrabReadiness.length; k++) {
|
||||||
if (nearGrabReadiness[k].active && nearGrabReadiness[k].targets[0] === this.grabbedThingID) {
|
if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.grabbedThingID
|
||||||
this.endNearGrabAction();
|
|| HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) {
|
||||||
|
this.endFarGrabAction();
|
||||||
return makeRunningValues(false, [], []);
|
return makeRunningValues(false, [], []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -445,7 +446,7 @@ Script.include("/~/system/libraries/Xform.js");
|
||||||
// where it could near-grab something, stop searching.
|
// where it could near-grab something, stop searching.
|
||||||
for (var j = 0; j < nearGrabReadiness.length; j++) {
|
for (var j = 0; j < nearGrabReadiness.length; j++) {
|
||||||
if (nearGrabReadiness[j].active) {
|
if (nearGrabReadiness[j].active) {
|
||||||
this.endNearGrabAction();
|
this.endFarGrabAction();
|
||||||
return makeRunningValues(false, [], []);
|
return makeRunningValues(false, [], []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -577,7 +578,7 @@ Script.include("/~/system/libraries/Xform.js");
|
||||||
var disableModule = getEnabledModuleByName(moduleName);
|
var disableModule = getEnabledModuleByName(moduleName);
|
||||||
if (disableModule) {
|
if (disableModule) {
|
||||||
if (disableModule.disableModules) {
|
if (disableModule.disableModules) {
|
||||||
this.endNearGrabAction();
|
this.endFarGrabAction();
|
||||||
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
|
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
|
||||||
this.highlightedEntity);
|
this.highlightedEntity);
|
||||||
this.highlightedEntity = null;
|
this.highlightedEntity = null;
|
||||||
|
|
|
@ -250,7 +250,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buyButtonClicked(id, name, author, price, href, referrer, edition) {
|
function buyButtonClicked(id, name, author, price, href, referrer, edition, type) {
|
||||||
EventBridge.emitWebEvent(JSON.stringify({
|
EventBridge.emitWebEvent(JSON.stringify({
|
||||||
type: "CHECKOUT",
|
type: "CHECKOUT",
|
||||||
itemId: id,
|
itemId: id,
|
||||||
|
@ -259,7 +259,8 @@
|
||||||
itemHref: href,
|
itemHref: href,
|
||||||
referrer: referrer,
|
referrer: referrer,
|
||||||
itemAuthor: author,
|
itemAuthor: author,
|
||||||
itemEdition: edition
|
itemEdition: edition,
|
||||||
|
itemType: type.trim()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,7 +329,8 @@
|
||||||
$(this).closest('.grid-item').find('.item-cost').text(),
|
$(this).closest('.grid-item').find('.item-cost').text(),
|
||||||
$(this).attr('data-href'),
|
$(this).attr('data-href'),
|
||||||
"mainPage",
|
"mainPage",
|
||||||
-1);
|
-1,
|
||||||
|
$(this).closest('.grid-item').find('.item-type').text());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,6 +421,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
var cost = $('.item-cost').text();
|
var cost = $('.item-cost').text();
|
||||||
|
var type = $('.item-type').text();
|
||||||
var isUpdating = window.location.href.indexOf('edition=') > -1;
|
var isUpdating = window.location.href.indexOf('edition=') > -1;
|
||||||
var urlParams = new URLSearchParams(window.location.search);
|
var urlParams = new URLSearchParams(window.location.search);
|
||||||
if (isUpdating) {
|
if (isUpdating) {
|
||||||
|
@ -438,7 +441,8 @@
|
||||||
cost,
|
cost,
|
||||||
href,
|
href,
|
||||||
"itemPage",
|
"itemPage",
|
||||||
urlParams.get('edition'));
|
urlParams.get('edition'),
|
||||||
|
type);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
maybeAddPurchasesButton();
|
maybeAddPurchasesButton();
|
||||||
|
|
|
@ -987,6 +987,11 @@ var selectionDisplay = null; // for gridTool.js to ignore
|
||||||
sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, SEND_ASSET_PARTICLE_TIMER_UPDATE);
|
sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, SEND_ASSET_PARTICLE_TIMER_UPDATE);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'http.request':
|
||||||
|
// Handled elsewhere, don't log.
|
||||||
|
break;
|
||||||
|
case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about?
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));
|
print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
|
|
@ -317,6 +317,8 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case 'http.request':
|
||||||
|
break; // Handled by request-service.
|
||||||
default:
|
default:
|
||||||
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
|
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
|
48
scripts/system/request-service.js
Normal file
48
scripts/system/request-service.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
"use strict";
|
||||||
|
//
|
||||||
|
// request-service.js
|
||||||
|
//
|
||||||
|
// Created by Howard Stearns on May 22, 2018
|
||||||
|
// Copyright 2018 High Fidelity, Inc
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
(function() { // BEGIN LOCAL_SCOPE
|
||||||
|
|
||||||
|
// QML has its own XMLHttpRequest, but:
|
||||||
|
// - npm request is easier to use.
|
||||||
|
// - It is not easy to hack QML's XMLHttpRequest to use our MetaverseServer, and to supply the user's auth when contacting it.
|
||||||
|
// a. Our custom XMLHttpRequestClass object only works with QScriptEngine, not QML's javascript.
|
||||||
|
// b. We have hacked profiles that intercept requests to our MetavserseServer (providing the correct auth), but those
|
||||||
|
// only work in QML WebEngineView. Setting up communication between ordinary QML and a hiddent WebEngineView is
|
||||||
|
// tantamount to the following anyway, and would still have to duplicate the code from request.js.
|
||||||
|
|
||||||
|
// So, this script does two things:
|
||||||
|
// 1. Allows any root .qml to signal sendToScript({id: aString, method: 'http.request', params: byNameOptions})
|
||||||
|
// We will then asynchonously call fromScript({id: theSameString, method: 'http.response', error: errorOrFalsey, response: body})
|
||||||
|
// on that root object.
|
||||||
|
// RootHttpRequest.qml does this.
|
||||||
|
// 2. If the uri used (computed from byNameOptions, see request.js) is to our metaverse, we will use the appropriate auth.
|
||||||
|
|
||||||
|
var request = Script.require('request').request;
|
||||||
|
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||||
|
function fromQml(message) { // messages are {id, method, params}, like json-rpc. See also sendToQml.
|
||||||
|
switch (message.method) {
|
||||||
|
case 'http.request':
|
||||||
|
request(message.params, function (error, response) {
|
||||||
|
tablet.sendToQml({
|
||||||
|
id: message.id,
|
||||||
|
method: 'http.response',
|
||||||
|
error: error, // Alas, this isn't always a JSON-RPC conforming error object.
|
||||||
|
response: response,
|
||||||
|
jsonrpc: '2.0'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tablet.fromQml.connect(fromQml);
|
||||||
|
Script.scriptEnding.connect(function () { tablet.fromQml.disconnect(fromQml); });
|
||||||
|
}()); // END LOCAL_SCOPE
|
|
@ -41,24 +41,6 @@
|
||||||
sortOrder: 8
|
sortOrder: 8
|
||||||
});
|
});
|
||||||
|
|
||||||
function fromQml(message) {
|
|
||||||
console.debug('tablet-goto::fromQml: message = ', JSON.stringify(message));
|
|
||||||
|
|
||||||
var response = {id: message.id, jsonrpc: "2.0"};
|
|
||||||
switch (message.method) {
|
|
||||||
case 'request':
|
|
||||||
request(message.params, function (error, data) {
|
|
||||||
debug('rpc', request, 'error:', error, 'data:', data);
|
|
||||||
response.error = error;
|
|
||||||
response.result = data;
|
|
||||||
tablet.sendToQml(response);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
response.error = {message: 'Unrecognized message', data: message};
|
|
||||||
}
|
|
||||||
tablet.sendToQml(response);
|
|
||||||
}
|
|
||||||
function messagesWaiting(isWaiting) {
|
function messagesWaiting(isWaiting) {
|
||||||
button.editProperties({
|
button.editProperties({
|
||||||
icon: isWaiting ? WAITING_ICON : NORMAL_ICON
|
icon: isWaiting ? WAITING_ICON : NORMAL_ICON
|
||||||
|
@ -66,21 +48,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasEventBridge = false;
|
|
||||||
function wireEventBridge(on) {
|
|
||||||
if (on) {
|
|
||||||
if (!hasEventBridge) {
|
|
||||||
tablet.fromQml.connect(fromQml);
|
|
||||||
hasEventBridge = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (hasEventBridge) {
|
|
||||||
tablet.fromQml.disconnect(fromQml);
|
|
||||||
hasEventBridge = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClicked() {
|
function onClicked() {
|
||||||
if (onGotoScreen) {
|
if (onGotoScreen) {
|
||||||
// for toolbar-mode: go back to home screen, this will close the window.
|
// for toolbar-mode: go back to home screen, this will close the window.
|
||||||
|
@ -98,15 +65,11 @@
|
||||||
onGotoScreen = true;
|
onGotoScreen = true;
|
||||||
shouldActivateButton = true;
|
shouldActivateButton = true;
|
||||||
button.editProperties({isActive: shouldActivateButton});
|
button.editProperties({isActive: shouldActivateButton});
|
||||||
wireEventBridge(true);
|
|
||||||
messagesWaiting(false);
|
messagesWaiting(false);
|
||||||
tablet.sendToQml({ method: 'refreshFeeds', protocolSignature: Window.protocolSignature() })
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
shouldActivateButton = false;
|
shouldActivateButton = false;
|
||||||
onGotoScreen = false;
|
onGotoScreen = false;
|
||||||
button.editProperties({isActive: shouldActivateButton});
|
button.editProperties({isActive: shouldActivateButton});
|
||||||
wireEventBridge(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button.clicked.connect(onClicked);
|
button.clicked.connect(onClicked);
|
||||||
|
|
|
@ -75,10 +75,14 @@ function getBuildInfo() {
|
||||||
}
|
}
|
||||||
const buildInfo = getBuildInfo();
|
const buildInfo = getBuildInfo();
|
||||||
|
|
||||||
function getRootHifiDataDirectory() {
|
function getRootHifiDataDirectory(local) {
|
||||||
var organization = buildInfo.organization;
|
var organization = buildInfo.organization;
|
||||||
if (osType == 'Windows_NT') {
|
if (osType == 'Windows_NT') {
|
||||||
return path.resolve(osHomeDir(), 'AppData/Roaming', organization);
|
if (local) {
|
||||||
|
return path.resolve(osHomeDir(), 'AppData/Local', organization);
|
||||||
|
} else {
|
||||||
|
return path.resolve(osHomeDir(), 'AppData/Roaming', organization);
|
||||||
|
}
|
||||||
} else if (osType == 'Darwin') {
|
} else if (osType == 'Darwin') {
|
||||||
return path.resolve(osHomeDir(), 'Library/Application Support', organization);
|
return path.resolve(osHomeDir(), 'Library/Application Support', organization);
|
||||||
} else {
|
} else {
|
||||||
|
@ -94,8 +98,8 @@ function getAssignmentClientResourcesDirectory() {
|
||||||
return path.join(getRootHifiDataDirectory(), '/assignment-client');
|
return path.join(getRootHifiDataDirectory(), '/assignment-client');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getApplicationDataDirectory() {
|
function getApplicationDataDirectory(local) {
|
||||||
return path.join(getRootHifiDataDirectory(), '/Server Console');
|
return path.join(getRootHifiDataDirectory(local), '/Server Console');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update lock filepath
|
// Update lock filepath
|
||||||
|
@ -104,7 +108,7 @@ const UPDATER_LOCK_FULL_PATH = getRootHifiDataDirectory() + "/" + UPDATER_LOCK_F
|
||||||
|
|
||||||
// Configure log
|
// Configure log
|
||||||
global.log = require('electron-log');
|
global.log = require('electron-log');
|
||||||
const logFile = getApplicationDataDirectory() + '/log.txt';
|
const logFile = getApplicationDataDirectory(true) + '/log.txt';
|
||||||
fs.ensureFileSync(logFile); // Ensure file exists
|
fs.ensureFileSync(logFile); // Ensure file exists
|
||||||
log.transports.file.maxSize = 5 * 1024 * 1024;
|
log.transports.file.maxSize = 5 * 1024 * 1024;
|
||||||
log.transports.file.file = logFile;
|
log.transports.file.file = logFile;
|
||||||
|
@ -221,7 +225,19 @@ function deleteOldFiles(directoryPath, maxAgeInSeconds, filenameRegex) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var logPath = path.join(getApplicationDataDirectory(), '/logs');
|
var oldLogPath = path.join(getApplicationDataDirectory(), '/logs');
|
||||||
|
var logPath = path.join(getApplicationDataDirectory(true), '/logs');
|
||||||
|
|
||||||
|
if (oldLogPath != logPath) {
|
||||||
|
console.log("Migrating old logs from " + oldLogPath + " to " + logPath);
|
||||||
|
fs.copy(oldLogPath, logPath, err => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
} else {
|
||||||
|
console.log('success!');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("Log directory:", logPath);
|
log.debug("Log directory:", logPath);
|
||||||
log.debug("Data directory:", getRootHifiDataDirectory());
|
log.debug("Data directory:", getRootHifiDataDirectory());
|
||||||
|
|
1
tools/010-templates/README.md
Normal file
1
tools/010-templates/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This directory contains [010 editor](https://www.sweetscape.com/010editor/) templates for parsing and inspecting different file types.
|
102
tools/010-templates/fbx.bt
Normal file
102
tools/010-templates/fbx.bt
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
//
|
||||||
|
// fbx.bt
|
||||||
|
// tools/010-templates
|
||||||
|
//
|
||||||
|
// Created by Ryan Huffman
|
||||||
|
// Copyright 2018 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// FBX file template
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
local char use64BitAddresses = 1;
|
||||||
|
|
||||||
|
struct Header {
|
||||||
|
char prefix[23];
|
||||||
|
int32 version;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Property {
|
||||||
|
char type;
|
||||||
|
if (type == 'Y') {
|
||||||
|
int16 value;
|
||||||
|
} else if (type == 'C') {
|
||||||
|
char value;
|
||||||
|
} else if (type == 'I') {
|
||||||
|
int32 value;
|
||||||
|
} else if (type == 'F') {
|
||||||
|
float value;
|
||||||
|
} else if (type == 'D') {
|
||||||
|
double value;
|
||||||
|
} else if (type == 'L') {
|
||||||
|
int64 value;
|
||||||
|
} else if (type == 'S' || type == 'R') {
|
||||||
|
uint32 size;
|
||||||
|
char value[size];
|
||||||
|
} else {
|
||||||
|
uint32 length;
|
||||||
|
uint32 encoding;
|
||||||
|
uint32 compressedLength;
|
||||||
|
if (encoding == 1) {
|
||||||
|
char compressedData[compressedLength];
|
||||||
|
} else if (type == 'f') {
|
||||||
|
float values[this.length];
|
||||||
|
} else if (type == 'd') {
|
||||||
|
double values[this.length];
|
||||||
|
} else if (type == 'l') {
|
||||||
|
int64 values[this.length];
|
||||||
|
} else if (type == 'i') {
|
||||||
|
int32 values[this.length];
|
||||||
|
} else if (type == 'b') {
|
||||||
|
char values[this.length];
|
||||||
|
} else {
|
||||||
|
Printf("%c", type);
|
||||||
|
Assert(false, "Error, unknown property type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Node;
|
||||||
|
|
||||||
|
string nodeName(Node& node) {
|
||||||
|
if (!exists(node.name)) {
|
||||||
|
return "Node ----- ";
|
||||||
|
}
|
||||||
|
local string s;
|
||||||
|
SPrintf(s, "Node (%s) ", node.name);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
if (use64BitAddresses) {
|
||||||
|
int64 endOffset;
|
||||||
|
uint64 propertyCount;
|
||||||
|
uint64 propertyListLength;
|
||||||
|
} else {
|
||||||
|
int32 endOffset;
|
||||||
|
uint32 propertyCount;
|
||||||
|
uint32 propertyListLength;
|
||||||
|
}
|
||||||
|
uchar nameLength;
|
||||||
|
char name[this.nameLength];
|
||||||
|
Property properties[this.propertyCount]<optimize=false>;
|
||||||
|
while (FTell() < endOffset) {
|
||||||
|
Node children<optimize=false, name=nodeName>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct File {
|
||||||
|
Header header;
|
||||||
|
use64BitAddresses = header.version >= 7500;
|
||||||
|
local int i = 0;
|
||||||
|
Node node<name=nodeName>;
|
||||||
|
local string name = node.name;
|
||||||
|
while (name != "") {
|
||||||
|
Node node<name=nodeName>;
|
||||||
|
i++;
|
||||||
|
name = exists(node[i].name) ? node[i].name : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
} file;
|
52
tools/010-templates/ktx.bt
Normal file
52
tools/010-templates/ktx.bt
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// ktx.bt
|
||||||
|
// tools/010-templates
|
||||||
|
//
|
||||||
|
// Created by Ryan Huffman
|
||||||
|
// Copyright 2018 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// KTX file template
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
struct Header {
|
||||||
|
char identifier[12];
|
||||||
|
uint32 endianness<format=hex>;
|
||||||
|
uint32 glType;
|
||||||
|
uint32 glTypeSize;
|
||||||
|
uint32 glFormat;
|
||||||
|
uint32 glInternalFormat;
|
||||||
|
uint32 glBaseInternalFormat;
|
||||||
|
uint32 pixelWidth;
|
||||||
|
uint32 pixelHeight;
|
||||||
|
uint32 pixelDepth;
|
||||||
|
uint32 numberOfArrayElements;
|
||||||
|
uint32 numberOfFaces;
|
||||||
|
uint32 numberOfMipmapLevels;
|
||||||
|
uint32 bytesOfKeyValueData;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KV {
|
||||||
|
uint32 byteSize;
|
||||||
|
local uint32 keyLength = ReadStringLength(FTell());
|
||||||
|
char key[keyLength];
|
||||||
|
char value[byteSize - keyLength] <format=hex>;
|
||||||
|
char padding[3 - ((byteSize + 3) % 4)];
|
||||||
|
};
|
||||||
|
|
||||||
|
string kvName(KV& kv) {
|
||||||
|
local string s;
|
||||||
|
SPrintf(s, "KeyValue (%s) ", kv.key);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct File {
|
||||||
|
Header header;
|
||||||
|
local uint32 endOfKV = FTell() + header.bytesOfKeyValueData;
|
||||||
|
while (FTell() < endOfKV) {
|
||||||
|
KV keyValue <optimize=false, name=kvName>;
|
||||||
|
}
|
||||||
|
char imageData[FileSize() - FTell()];
|
||||||
|
} file;
|
|
@ -11,7 +11,7 @@ if (WIN32)
|
||||||
elseif (UNIX AND NOT APPLE)
|
elseif (UNIX AND NOT APPLE)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
if(THREADS_HAVE_PTHREAD_ARG)
|
if(THREADS_HAVE_PTHREAD_ARG)
|
||||||
target_compile_options(PUBLIC oven "-pthread")
|
target_compile_options(oven PUBLIC "-pthread")
|
||||||
endif()
|
endif()
|
||||||
elseif (APPLE)
|
elseif (APPLE)
|
||||||
# Fix up the rpath so macdeployqt works
|
# Fix up the rpath so macdeployqt works
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "OvenCLIApplication.h"
|
#include "OvenCLIApplication.h"
|
||||||
#include "ModelBakingLoggingCategory.h"
|
#include "ModelBakingLoggingCategory.h"
|
||||||
#include "FBXBaker.h"
|
#include "FBXBaker.h"
|
||||||
|
@ -38,17 +40,15 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString&
|
||||||
static const QString MODEL_EXTENSION { "fbx" };
|
static const QString MODEL_EXTENSION { "fbx" };
|
||||||
static const QString SCRIPT_EXTENSION { "js" };
|
static const QString SCRIPT_EXTENSION { "js" };
|
||||||
|
|
||||||
QString extension = type;
|
|
||||||
|
|
||||||
if (extension.isNull()) {
|
|
||||||
auto url = inputUrl.toDisplayString();
|
|
||||||
extension = url.mid(url.lastIndexOf('.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// check what kind of baker we should be creating
|
// check what kind of baker we should be creating
|
||||||
bool isFBX = extension == MODEL_EXTENSION;
|
bool isFBX = type == MODEL_EXTENSION;
|
||||||
bool isScript = extension == SCRIPT_EXTENSION;
|
bool isScript = type == SCRIPT_EXTENSION;
|
||||||
|
|
||||||
|
// If the type doesn't match the above, we assume we have a texture, and the type specified is the
|
||||||
|
// texture usage type (albedo, cubemap, normals, etc.)
|
||||||
|
auto url = inputUrl.toDisplayString();
|
||||||
|
auto idx = url.lastIndexOf('.');
|
||||||
|
auto extension = idx >= 0 ? url.mid(idx + 1).toLower() : "";
|
||||||
bool isSupportedImage = QImageReader::supportedImageFormats().contains(extension.toLatin1());
|
bool isSupportedImage = QImageReader::supportedImageFormats().contains(extension.toLatin1());
|
||||||
|
|
||||||
_outputPath = outputPath;
|
_outputPath = outputPath;
|
||||||
|
@ -65,7 +65,29 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString&
|
||||||
_baker = std::unique_ptr<Baker> { new JSBaker(inputUrl, outputPath) };
|
_baker = std::unique_ptr<Baker> { new JSBaker(inputUrl, outputPath) };
|
||||||
_baker->moveToThread(Oven::instance().getNextWorkerThread());
|
_baker->moveToThread(Oven::instance().getNextWorkerThread());
|
||||||
} else if (isSupportedImage) {
|
} else if (isSupportedImage) {
|
||||||
_baker = std::unique_ptr<Baker> { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) };
|
static const std::unordered_map<QString, image::TextureUsage::Type> STRING_TO_TEXTURE_USAGE_TYPE_MAP {
|
||||||
|
{ "default", image::TextureUsage::DEFAULT_TEXTURE },
|
||||||
|
{ "strict", image::TextureUsage::STRICT_TEXTURE },
|
||||||
|
{ "albedo", image::TextureUsage::ALBEDO_TEXTURE },
|
||||||
|
{ "normal", image::TextureUsage::NORMAL_TEXTURE },
|
||||||
|
{ "bump", image::TextureUsage::BUMP_TEXTURE },
|
||||||
|
{ "specular", image::TextureUsage::SPECULAR_TEXTURE },
|
||||||
|
{ "metallic", image::TextureUsage::METALLIC_TEXTURE },
|
||||||
|
{ "roughness", image::TextureUsage::ROUGHNESS_TEXTURE },
|
||||||
|
{ "gloss", image::TextureUsage::GLOSS_TEXTURE },
|
||||||
|
{ "emissive", image::TextureUsage::EMISSIVE_TEXTURE },
|
||||||
|
{ "cube", image::TextureUsage::CUBE_TEXTURE },
|
||||||
|
{ "occlusion", image::TextureUsage::OCCLUSION_TEXTURE },
|
||||||
|
{ "scattering", image::TextureUsage::SCATTERING_TEXTURE },
|
||||||
|
{ "lightmap", image::TextureUsage::LIGHTMAP_TEXTURE },
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = STRING_TO_TEXTURE_USAGE_TYPE_MAP.find(type);
|
||||||
|
if (it == STRING_TO_TEXTURE_USAGE_TYPE_MAP.end()) {
|
||||||
|
qCDebug(model_baking) << "Unknown texture usage type:" << type;
|
||||||
|
QCoreApplication::exit(OVEN_STATUS_CODE_FAIL);
|
||||||
|
}
|
||||||
|
_baker = std::unique_ptr<Baker> { new TextureBaker(inputUrl, it->second, outputPath) };
|
||||||
_baker->moveToThread(Oven::instance().getNextWorkerThread());
|
_baker->moveToThread(Oven::instance().getNextWorkerThread());
|
||||||
} else {
|
} else {
|
||||||
qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl;
|
qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl;
|
||||||
|
|
|
@ -14,11 +14,14 @@
|
||||||
#include <QtCore/QCommandLineParser>
|
#include <QtCore/QCommandLineParser>
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
|
|
||||||
|
#include <image/Image.h>
|
||||||
|
|
||||||
#include "BakerCLI.h"
|
#include "BakerCLI.h"
|
||||||
|
|
||||||
static const QString CLI_INPUT_PARAMETER = "i";
|
static const QString CLI_INPUT_PARAMETER = "i";
|
||||||
static const QString CLI_OUTPUT_PARAMETER = "o";
|
static const QString CLI_OUTPUT_PARAMETER = "o";
|
||||||
static const QString CLI_TYPE_PARAMETER = "t";
|
static const QString CLI_TYPE_PARAMETER = "t";
|
||||||
|
static const QString CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER = "disable-texture-compression";
|
||||||
|
|
||||||
OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) :
|
OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) :
|
||||||
QCoreApplication(argc, argv)
|
QCoreApplication(argc, argv)
|
||||||
|
@ -29,7 +32,8 @@ OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) :
|
||||||
parser.addOptions({
|
parser.addOptions({
|
||||||
{ CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" },
|
{ CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" },
|
||||||
{ CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" },
|
{ CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" },
|
||||||
{ CLI_TYPE_PARAMETER, "Type of asset.", "type" }
|
{ CLI_TYPE_PARAMETER, "Type of asset.", "type" },
|
||||||
|
{ CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER, "Disable texture compression." }
|
||||||
});
|
});
|
||||||
|
|
||||||
parser.addHelpOption();
|
parser.addHelpOption();
|
||||||
|
@ -40,6 +44,15 @@ OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) :
|
||||||
QUrl inputUrl(QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER)));
|
QUrl inputUrl(QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER)));
|
||||||
QUrl outputUrl(QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER)));
|
QUrl outputUrl(QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER)));
|
||||||
QString type = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString::null;
|
QString type = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString::null;
|
||||||
|
|
||||||
|
if (parser.isSet(CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER)) {
|
||||||
|
qDebug() << "Disabling texture compression";
|
||||||
|
image::setColorTexturesCompressionEnabled(false);
|
||||||
|
image::setGrayscaleTexturesCompressionEnabled(false);
|
||||||
|
image::setNormalTexturesCompressionEnabled(false);
|
||||||
|
image::setCubeTexturesCompressionEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
QMetaObject::invokeMethod(cli, "bakeFile", Qt::QueuedConnection, Q_ARG(QUrl, inputUrl),
|
QMetaObject::invokeMethod(cli, "bakeFile", Qt::QueuedConnection, Q_ARG(QUrl, inputUrl),
|
||||||
Q_ARG(QString, outputUrl.toString()), Q_ARG(QString, type));
|
Q_ARG(QString, outputUrl.toString()), Q_ARG(QString, type));
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue