mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 11:53:28 +02:00
Integrate marketplace upload API
This commit is contained in:
parent
2269447741
commit
ad471387f7
12 changed files with 386 additions and 52 deletions
|
@ -28,6 +28,10 @@ Original.Button {
|
|||
width: hifi.dimensions.buttonWidth
|
||||
height: hifi.dimensions.controlLineHeight
|
||||
|
||||
property size implicitPadding: Qt.size(20, 16)
|
||||
property int implicitWidth: content.implicitWidth + implicitPadding.width
|
||||
property int implicitHeight: content.implicitHeight + implicitPadding.height
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
onHoveredChanged: {
|
||||
|
@ -89,6 +93,9 @@ Original.Button {
|
|||
}
|
||||
|
||||
contentItem: Item {
|
||||
id: content
|
||||
implicitWidth: (buttonGlyph.visible ? buttonGlyph.implicitWidth : 0) + buttonText.implicitWidth
|
||||
implicitHeight: buttonText.implicitHeight
|
||||
HiFiGlyphs {
|
||||
id: buttonGlyph;
|
||||
visible: control.buttonGlyph !== "";
|
||||
|
|
|
@ -47,7 +47,7 @@ Windows.ScrollingWindow {
|
|||
anchors.top: parent.top
|
||||
anchors.topMargin: 25
|
||||
anchors.bottomMargin: 25
|
||||
text: 'Avatar Packager'
|
||||
text: 'Avatar Packager ' + parent.width + " " + parent.height
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
|
@ -72,6 +72,7 @@ Windows.ScrollingWindow {
|
|||
height: 30
|
||||
onClicked: function() {
|
||||
var avatarProjectsPath = fileDialogHelper.standardPath(/*fileDialogHelper.StandardLocation.DocumentsLocation*/ 1) + "/High Fidelity/Avatar Projects";
|
||||
var avatarProjectsPath = "C:/Users/ryanh/Documents/High Fidelity Avatars";
|
||||
console.log("path = " + avatarProjectsPath);
|
||||
|
||||
// TODO: make the dialog modal
|
||||
|
|
|
@ -3,6 +3,8 @@ import QtQuick.Controls 2.2
|
|||
import QtQuick.Layouts 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import QtQuick.Controls 2.2 as Original
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
|
@ -12,6 +14,7 @@ Item {
|
|||
HifiConstants { id: hifi }
|
||||
|
||||
property int colorScheme;
|
||||
property var uploader: undefined;
|
||||
|
||||
visible: true
|
||||
|
||||
|
@ -58,13 +61,118 @@ Item {
|
|||
}
|
||||
HifiControls.Button {
|
||||
id: uploadButton
|
||||
|
||||
width: parent.width
|
||||
height: 30
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
text: qsTr("Upload")
|
||||
color: hifi.buttons.blue
|
||||
colorScheme: root.colorScheme
|
||||
height: 30
|
||||
onClicked: function() {
|
||||
console.log("Uploading");
|
||||
parent.uploader = AvatarPackagerCore.currentAvatarProject.upload();
|
||||
console.log("uploader: "+ parent.uploader);
|
||||
parent.uploader.uploadProgress.connect(function(uploaded, total) {
|
||||
console.log("Uploader progress: " + uploaded + " / " + total);
|
||||
});
|
||||
parent.uploader.completed.connect(function() {
|
||||
try {
|
||||
var response = JSON.parse(parent.uploader.responseData);
|
||||
console.log("Uploader complete! " + response);
|
||||
uploadStatus.text = response.status;
|
||||
} catch (e) {
|
||||
console.log("Error parsing JSON: " + parent.uploader.reponseData);
|
||||
}
|
||||
});
|
||||
parent.uploader.send();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: uploadingScreen
|
||||
|
||||
visible: !!root.uploader
|
||||
anchors.fill: parent
|
||||
|
||||
color: "black"
|
||||
|
||||
Item {
|
||||
visible: !!root.uploader && !root.uploader.complete
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
AnimatedImage {
|
||||
id: uploadSpinner
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
source: "../../../icons/loader-snake-64-w.gif"
|
||||
playing: true
|
||||
z: 10000
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: !!root.uploader && root.uploader.complete
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
HiFiGlyphs {
|
||||
id: successIcon
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
size: 128
|
||||
text: "\ue01a"
|
||||
color: "#1FC6A6"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Congratulations!"
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: successIcon.bottom
|
||||
|
||||
color: "white"
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
|
||||
text: "View in Inventory"
|
||||
|
||||
color: hifi.buttons.blue
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: function() {
|
||||
console.log("Opening in inventory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Text {
|
||||
id: uploadStatus
|
||||
|
||||
text: "Uploading"
|
||||
color: "white"
|
||||
|
||||
}
|
||||
Text {
|
||||
text: "State: " + (!!root.uploader ? root.uploader.state : " NONE")
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -460,7 +460,7 @@ public:
|
|||
// Don't actually crash in debug builds, in case this apparent deadlock is simply from
|
||||
// the developer actively debugging code
|
||||
#ifdef NDEBUG
|
||||
deadlockDetectionCrash();
|
||||
//deadlockDetectionCrash();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -536,6 +536,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
|
|||
// my avatar. (Other user machines will make a similar analysis and inject sound for their collisions.)
|
||||
if (collision.idA.isNull() || collision.idB.isNull()) {
|
||||
auto myAvatar = getMyAvatar();
|
||||
myAvatar->collisionWithEntity(collision);
|
||||
auto collisionSound = myAvatar->getCollisionSound();
|
||||
if (collisionSound) {
|
||||
const auto characterController = myAvatar->getCharacterController();
|
||||
|
@ -571,9 +572,8 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
|
|||
auto injector = AudioInjector::playSoundAndDelete(collisionSound, options);
|
||||
_collisionInjectors.emplace_back(injector);
|
||||
}
|
||||
myAvatar->collisionWithEntity(collision);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ std::once_flag setupQMLTypesFlag;
|
|||
AvatarPackager::AvatarPackager() {
|
||||
std::call_once(setupQMLTypesFlag, []() {
|
||||
qmlRegisterType<FST>();
|
||||
qmlRegisterType<MarketplaceItemUploader>();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -47,8 +48,3 @@ QObject* AvatarPackager::openAvatarProject(QString avatarProjectFSTPath) {
|
|||
emit avatarProjectChanged();
|
||||
return _currentAvatarProject;
|
||||
}
|
||||
|
||||
QObject* AvatarPackager::uploadItem() {
|
||||
std::vector<QString> filePaths;
|
||||
return new MarketplaceItemUploader(QUuid(), filePaths);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ signals:
|
|||
private:
|
||||
Q_INVOKABLE AvatarProject* getAvatarProject() const { return _currentAvatarProject; };
|
||||
//Q_INVOKABLE QObject* openAvatarProject();
|
||||
Q_INVOKABLE QObject* uploadItem();
|
||||
|
||||
AvatarProject* _currentAvatarProject{ nullptr };
|
||||
};
|
||||
|
|
|
@ -41,6 +41,9 @@ AvatarProject* AvatarProject::openAvatarProject(const QString& path) {
|
|||
AvatarProject::AvatarProject(const QString& fstPath, const QByteArray& data) :
|
||||
_fstPath(fstPath), _fst(fstPath, FSTReader::readMapping(data)) {
|
||||
|
||||
_fstFilename = QFileInfo(_fstPath).fileName();
|
||||
qDebug() << "Pointers: " << this << &_fst;
|
||||
|
||||
_directory = QFileInfo(_fstPath).absoluteDir();
|
||||
|
||||
//_projectFiles = _directory.entryList();
|
||||
|
@ -51,13 +54,12 @@ AvatarProject::AvatarProject(const QString& fstPath, const QByteArray& data) :
|
|||
}
|
||||
|
||||
void AvatarProject::appendDirectory(QString prefix, QDir dir) {
|
||||
qDebug() << "Inside of " << prefix << dir.absolutePath();
|
||||
auto flags = QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden;
|
||||
for (auto& entry : dir.entryInfoList({}, flags)) {
|
||||
if (entry.isFile()) {
|
||||
_projectFiles.append(prefix + "/" + entry.fileName());
|
||||
//_projectFiles.append(prefix + "/" + entry.fileName());
|
||||
_projectFiles.append(entry.absoluteFilePath());
|
||||
} else if (entry.isDir()) {
|
||||
qDebug() << "Found dir " << entry.absoluteFilePath() << " in " << dir.absolutePath();
|
||||
appendDirectory(prefix + dir.dirName() + "/", entry.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
|
@ -67,3 +69,7 @@ void AvatarProject::refreshProjectFiles() {
|
|||
_projectFiles.clear();
|
||||
appendDirectory("", _directory);
|
||||
}
|
||||
|
||||
Q_INVOKABLE MarketplaceItemUploader* AvatarProject::upload() {
|
||||
return new MarketplaceItemUploader("test_avatar", "blank description", _fstFilename, QUuid(), _projectFiles);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#ifndef hifi_AvatarProject_h
|
||||
#define hifi_AvatarProject_h
|
||||
|
||||
#include "MarketplaceItemUploader.h"
|
||||
#include "FST.h"
|
||||
|
||||
#include <QDir>
|
||||
|
@ -38,10 +39,7 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
Q_INVOKABLE QObject* upload() {
|
||||
// TODO: create new AvatarProjectUploader here, launch it and return it for status tracking in QML
|
||||
return nullptr;
|
||||
}
|
||||
Q_INVOKABLE MarketplaceItemUploader* upload();
|
||||
|
||||
/**
|
||||
* returns the AvatarProject or a nullptr on failure.
|
||||
|
@ -70,6 +68,7 @@ private:
|
|||
QStringList _projectFiles{};
|
||||
QString _projectPath;
|
||||
QString _fstPath;
|
||||
QString _fstFilename;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarProject_h
|
||||
|
|
|
@ -14,44 +14,221 @@
|
|||
#include <AccountManager.h>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include <qtimer.h>
|
||||
|
||||
MarketplaceItemUploader::MarketplaceItemUploader(QUuid marketplaceID, std::vector<QString> filePaths)
|
||||
: _filePaths(filePaths), _marketplaceID(marketplaceID) {
|
||||
#include <QBuffer>
|
||||
#include <quazip5\quazip.h>
|
||||
#include <quazip5\quazipfile.h>
|
||||
|
||||
#include <qtimer.h>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
|
||||
MarketplaceItemUploader::MarketplaceItemUploader(QString title,
|
||||
QString description,
|
||||
QString rootFilename,
|
||||
QUuid marketplaceID,
|
||||
QStringList filePaths) :
|
||||
_title(title),
|
||||
_description(description), _rootFilename(rootFilename), _filePaths(filePaths), _marketplaceID(marketplaceID) {
|
||||
qWarning() << "File paths: " << _filePaths.join(", ");
|
||||
//_marketplaceID = QUuid::fromString(QLatin1String("{50dbd62f-cb6b-4be4-afb8-1ef8bd2dffa8}"));
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::setState(State newState) {
|
||||
qDebug() << "Setting uploader state to: " << newState;
|
||||
|
||||
_state = newState;
|
||||
emit stateChanged(newState);
|
||||
if (newState == State::Complete) {
|
||||
emit completed();
|
||||
}
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::send() {
|
||||
doGetCategories();
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::doGetCategories() {
|
||||
setState(State::GettingCategories);
|
||||
|
||||
static const QString path = "/api/v1/marketplace/categories";
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto request = accountManager->createRequest("/marketplace/item", AccountManagerAuth::Required);
|
||||
auto request = accountManager->createRequest(path, AccountManagerAuth::None);
|
||||
|
||||
qWarning() << "Request url is: " << request.url();
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QByteArray data;
|
||||
|
||||
/*
|
||||
auto reply = networkAccessManager.post(request, data);
|
||||
|
||||
connect(reply, &QNetworkReply::uploadProgress, this, &MarketplaceItemUploader::uploadProgress);
|
||||
QNetworkReply* reply = networkAccessManager.get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
auto doc = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
auto error = reply->error();
|
||||
if (error == QNetworkReply::NoError) {
|
||||
} else {
|
||||
}
|
||||
emit complete();
|
||||
});
|
||||
*/
|
||||
auto extractCategoryID = [&doc]() -> std::pair<bool, int> {
|
||||
auto items = doc.object()["data"].toObject()["items"];
|
||||
if (!items.isArray()) {
|
||||
qWarning() << "Categories parse error: data.items is not an array";
|
||||
return { false, 0 };
|
||||
}
|
||||
|
||||
QTimer* timer = new QTimer();
|
||||
timer->setInterval(1000);
|
||||
connect(timer, &QTimer::timeout, this, [this, timer]() {
|
||||
if (progress <= 1.0f) {
|
||||
progress += 0.1;
|
||||
emit uploadProgress(progress * 100.0f, 100.0f);
|
||||
auto itemsArray = items.toArray();
|
||||
for (const auto item : itemsArray) {
|
||||
if (!item.isObject()) {
|
||||
qWarning() << "Categories parse error: item is not an object";
|
||||
return { false, 0 };
|
||||
}
|
||||
|
||||
auto itemObject = item.toObject();
|
||||
if (itemObject["name"].toString() == "Avatars") {
|
||||
auto idValue = itemObject["id"];
|
||||
if (!idValue.isDouble()) {
|
||||
qWarning() << "Categories parse error: id is not a number";
|
||||
return { false, 0 };
|
||||
}
|
||||
return { true, (int)idValue.toDouble() };
|
||||
}
|
||||
}
|
||||
|
||||
qWarning() << "Categories parse error: could not find a category for 'Avatar'";
|
||||
return { false, 0 };
|
||||
};
|
||||
|
||||
bool success;
|
||||
int id;
|
||||
std::tie(success, id) = extractCategoryID();
|
||||
qDebug() << "Done " << success << id;
|
||||
if (!success) {
|
||||
qWarning() << "Failed to find marketplace category id";
|
||||
_error = Error::Unknown;
|
||||
setState(State::Complete);
|
||||
} else {
|
||||
doUploadAvatar();
|
||||
}
|
||||
} else {
|
||||
emit complete();
|
||||
timer->stop();
|
||||
_error = Error::Unknown;
|
||||
setState(State::Complete);
|
||||
}
|
||||
});
|
||||
timer->start();
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::doUploadAvatar() {
|
||||
QBuffer buffer{ &_fileData };
|
||||
//buffer.open(QIODevice::WriteOnly);
|
||||
QuaZip zip{ &buffer };
|
||||
if (!zip.open(QuaZip::Mode::mdAdd)) {
|
||||
qWarning() << "Failed to open zip!!";
|
||||
}
|
||||
|
||||
for (auto& filePath : _filePaths) {
|
||||
qWarning() << "Zipping: " << filePath;
|
||||
QFileInfo fileInfo{ filePath };
|
||||
|
||||
QuaZipFile zipFile{ &zip };
|
||||
if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileInfo.fileName()))) {
|
||||
qWarning() << "Could not open zip file:" << zipFile.getZipError();
|
||||
_error = Error::Unknown;
|
||||
setState(State::Complete);
|
||||
return;
|
||||
}
|
||||
QFile file{ filePath };
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
zipFile.write(file.readAll());
|
||||
} else {
|
||||
qWarning() << "Failed to open: " << filePath;
|
||||
}
|
||||
file.close();
|
||||
zipFile.close();
|
||||
if (zipFile.getZipError() != UNZ_OK) {
|
||||
qWarning() << "Could not close zip file: " << zipFile.getZipError();
|
||||
setState(State::Complete);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
zip.close();
|
||||
|
||||
qDebug() << "Finished zipping, size: " << (buffer.size() / (1000.0f)) << "KB";
|
||||
|
||||
QString path = "/api/v1/marketplace/items";
|
||||
bool creating = true;
|
||||
if (!_marketplaceID.isNull()) {
|
||||
creating = false;
|
||||
auto idWithBraces = _marketplaceID.toString();
|
||||
auto idWithoutBraces = idWithBraces.mid(1, idWithBraces.length() - 2);
|
||||
path += "/" + idWithoutBraces;
|
||||
}
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto request = accountManager->createRequest(path, AccountManagerAuth::Required);
|
||||
qWarning() << "Request url is: " << request.url();
|
||||
|
||||
QJsonObject root{ { "marketplace_item",
|
||||
QJsonObject{ { "title", _title },
|
||||
{ "description", _description },
|
||||
{ "root_file_key", _rootFilename },
|
||||
{ "category_ids", QJsonArray({ 5 }) },
|
||||
//{ "attributions", QJsonArray({ QJsonObject{ { "name", "" }, { "link", "" } } }) },
|
||||
{ "license", 0 },
|
||||
{ "files", QString::fromLatin1(_fileData.toBase64()) } } } };
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
QJsonDocument doc{ root };
|
||||
|
||||
qWarning() << "data: " << doc.toJson();
|
||||
|
||||
_fileData.toBase64();
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QNetworkReply* reply{ nullptr };
|
||||
if (creating) {
|
||||
reply = networkAccessManager.post(request, doc.toJson());
|
||||
} else {
|
||||
reply = networkAccessManager.put(request, doc.toJson());
|
||||
}
|
||||
|
||||
connect(reply, &QNetworkReply::uploadProgress, this, &MarketplaceItemUploader::uploadProgress);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
_responseData = reply->readAll();
|
||||
qWarning() << "Finished request " << _responseData;
|
||||
auto error = reply->error();
|
||||
if (error == QNetworkReply::NoError) {
|
||||
doWaitForInventory();
|
||||
} else {
|
||||
_error = Error::Unknown;
|
||||
setState(State::Complete);
|
||||
}
|
||||
});
|
||||
|
||||
setState(State::UploadingAvatar);
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::doWaitForInventory() {
|
||||
static const QString path = "/api/v1/commerce/inventory";
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto request = accountManager->createRequest(path, AccountManagerAuth::Required);
|
||||
|
||||
qWarning() << "Request url is: " << request.url();
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QNetworkReply* reply = networkAccessManager.post(request, "");
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
auto data = reply->readAll();
|
||||
qWarning() << "Finished inventory request " << data;
|
||||
|
||||
auto error = reply->error();
|
||||
if (error == QNetworkReply::NoError) {
|
||||
} else {
|
||||
_error = Error::Unknown;
|
||||
}
|
||||
setState(State::Complete);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,36 +20,68 @@ class QNetworkReply;
|
|||
|
||||
class MarketplaceItemUploader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool complete READ getComplete NOTIFY stateChanged)
|
||||
Q_PROPERTY(State state READ getState NOTIFY stateChanged)
|
||||
Q_PROPERTY(Error error READ getError)
|
||||
Q_PROPERTY(QString responseData READ getResponseData)
|
||||
public:
|
||||
enum class Error
|
||||
{
|
||||
None,
|
||||
ItemNotUpdateable,
|
||||
ItemDoesNotExist,
|
||||
RequestTimedOut,
|
||||
Unknown
|
||||
};
|
||||
Q_ENUM(Error);
|
||||
|
||||
enum class State
|
||||
{
|
||||
Ready,
|
||||
Sent
|
||||
Idle,
|
||||
GettingCategories,
|
||||
UploadingAvatar,
|
||||
WaitingForInventory,
|
||||
Complete
|
||||
};
|
||||
Q_ENUM(State);
|
||||
|
||||
MarketplaceItemUploader(QUuid markertplaceID, std::vector<QString> filePaths);
|
||||
|
||||
float progress{ 0.0f };
|
||||
MarketplaceItemUploader(QString title,
|
||||
QString description,
|
||||
QString rootFilename,
|
||||
QUuid marketplaceID,
|
||||
QStringList filePaths);
|
||||
|
||||
Q_INVOKABLE void send();
|
||||
|
||||
QString getResponseData() const { return _responseData; }
|
||||
void setState(State newState);
|
||||
State getState() const { return _state; }
|
||||
bool getComplete() const { return _state == State::Complete; }
|
||||
|
||||
Error getError() const { return _error; }
|
||||
|
||||
signals:
|
||||
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
|
||||
void complete();
|
||||
void completed();
|
||||
void stateChanged(State newState);
|
||||
|
||||
private:
|
||||
void doGetCategories();
|
||||
void doUploadAvatar();
|
||||
void doWaitForInventory();
|
||||
|
||||
QNetworkReply* _reply;
|
||||
|
||||
State _state{ State::Idle };
|
||||
Error _error{ Error::None };
|
||||
|
||||
QString _title;
|
||||
QString _description;
|
||||
QString _rootFilename;
|
||||
QUuid _marketplaceID;
|
||||
std::vector<QString> _filePaths;
|
||||
|
||||
QString _responseData;
|
||||
|
||||
QStringList _filePaths;
|
||||
QByteArray _fileData;
|
||||
};
|
||||
|
||||
#endif // hifi_MarketplaceItemUploader_h
|
||||
|
|
|
@ -112,6 +112,15 @@ const GROUPS = [
|
|||
type: "color",
|
||||
propertyID: "color",
|
||||
},
|
||||
{
|
||||
label: "Alpha",
|
||||
type: "",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.001,
|
||||
propertyID: "alpha",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue