#pragma once #include "Reflection/Type.hpp" #include "CEvent.hpp" #include "Script/LuaSourceContainer.hpp" #include "Utility/ContentId.hpp" #include "Utility/HttpAsync.hpp" #include "DataModel/ContentProvider.hpp" #include #include #include #include #include #include // https://stackoverflow.com/questions/22597948/using-boostfuture-with-then-continuations #define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION #define BOOST_THREAD_PROVIDES_FUTURE #include #include #include #include class QLabel; class QPushButton; class QStandardItem; class QStandardItemModel; enum EntityCategory { ENTITY_CATEGORY_Places, // the node that places are inserted under ENTITY_CATEGORY_DeveloperProducts, // The node that dev products are inserted under ENTITY_CATEGORY_Badges, ENTITY_CATEGORY_NamedAssets, ENTITY_CATEGORY_MAX }; enum AliasType { ALIAS_TYPE_Asset = 1, ALIAS_TYPE_DeveloperProduct = 2, ALIAS_TYPE_AssetVersion = 3, }; enum AssetTypeId { ASSET_TYPE_ID_Image = 1, ASSET_TYPE_ID_Script = 5, ASSET_TYPE_ID_Animation = 24 }; // Caller is responsible for thread safety when using setter methods class EntityProperties { public: EntityProperties(); void setFromJsonFuture(const boost::shared_future& jsonFuture); void setFromValueTable(const boost::shared_ptr& valueTable); void setFromEntitySettings(EntityProperties& other); std::string asJson(); void waitUntilLoaded(); bool isLoadedAndParsed() const; template boost::optional get(const std::string& fieldName) { boost::optional var = getVariant(fieldName); if (var.is_initialized() && var->isType()) { return var->cast(); } return boost::optional(); } boost::optional getVariant(const std::string& fieldName); // Caller is responsible for ensuring the thread safety of this call template void set(const std::string& fieldName, const T& value) { waitUntilLoaded(); data[fieldName] = value; } typedef std::vector EntityCollection; void addChild(const EntityProperties& child) { children.push_back(child); } EntityCollection& getChildren() { return children; } private: bool hasPendingFuture; boost::shared_future jsonFuture; bool ableToGetFuture; bool ableToParseJson; Aya::Reflection::ValueTable data; EntityCollection children; }; class UniverseSettings { public: void setFromJsonFuture(const boost::shared_future& jsonFuture); void waitUntilLoaded(); bool isLoadedAndParsed() const; bool hasRootPlace(); int rootPlaceId(); std::string getName(); void setName(const std::string& newName); bool currentUserHasAccess(); std::string asJson(); private: EntityProperties properties; }; struct CategoryWebSettings { std::string jsonItemGroupFieldName; std::string jsonItemIdFieldName; std::string perItemIdFieldName; std::string fetchPageUrlFormatString; std::string createFormatString; std::string updateFormatString; std::string removeFormatString; std::string getContentFormatString; std::string writeContentFormatString; boost::function propertiesFixer; }; class EntityPropertiesForCategory : public boost::enable_shared_from_this { public: typedef boost::shared_ptr ref; static ref loadFromWeb(const CategoryWebSettings& webSettings, int universeId, boost::function doneLoadingCallback); bool isLoadedAndParsed(); template void publishTo(ref target, const std::vector& ignoredFields, boost::unordered_map>* idRemaps); template void transform(boost::function transform, std::vector* out) { waitUntilLoaded(); for (EntityCollection::iterator itr = data.begin(); itr != data.end(); ++itr) { out->push_back(transform(&(*itr))); } } private: EntityPropertiesForCategory(const CategoryWebSettings& webSettings, const int universeId); const CategoryWebSettings& webSettings; const int universeId; Aya::CEvent doneEvent; boost::function doneLoadingCallback; typedef std::vector EntityCollection; EntityCollection data; void requestPage(int page); void handlePage(int page, std::string* json, std::exception* error); void waitUntilLoaded(); }; class AddImageDialog : public QObject { Q_OBJECT public: void runModal(QWidget* parent, bool* created, QString* newName); private: QDialog* dialog; QLineEdit* nameEdit; QLabel* nameErrorMessage; QPushButton* fileNameEdit; QLabel* fileNameLabel; QLabel* fileNameErrorMessage; QLabel* generalErrorMessage; int currentGameId; boost::optional currentGameGroupId; bool* created; QString* newName; std::vector usedNames; void createImageAndNameThread(); private Q_SLOTS: bool validateName(); bool validateImageFile(); void openFileSelector(); void createButtonClicked(); }; class AbortableLineEdit : public QLineEdit { Q_OBJECT public: AbortableLineEdit(boost::function getLastValue) : QLineEdit("") , getLastValue(getLastValue) { } protected: virtual void keyReleaseEvent(QKeyEvent* keyEvent) { if (keyEvent->key() == Qt::Key_Escape) { setText(QString::fromStdString(getLastValue())); clearFocus(); } if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { clearFocus(); } QLineEdit::keyReleaseEvent(keyEvent); } private: boost::function getLastValue; }; class RobloxGameExplorer : public QTreeView , public Aya::AssetFetchMediator { Q_OBJECT public: RobloxGameExplorer(QWidget* parent); // Asset management functions virtual boost::optional findCachedAssetOrEmpty(const Aya::ContentId& contentId, int universeId); void updateAsset(const Aya::ContentId& contentId, const std::string& newContents); void notifyIfUnpublishedChanges(int universeId); void publishNamedAssetsToCurrentSlot(); static Aya::HttpFuture publishScriptAsset( const std::string& scriptText, boost::optional optionalAssetId, boost::optional optionalGroupId); // These methods must be run from main thread for concurrency protection: void getListOfImages(std::vector* out); void getListOfScripts(std::vector* out); void getListOfAnimations(std::vector* out); int getCurrentGameId() const { return currentGameId; } boost::optional getCurrentGameGroupId() { return currentGameGroupId; } QString getAnimationsDataJson(); Q_SIGNALS: void namedAssetsLoaded(int gameId); void namedAssetModified(int gameId, const QString& assetName); public Q_SLOTS: void nonGameLoaded(); void openGameFromGameId(int gameId); void openGameFromPlaceId(int placeId); void onCloseIdeDoc(); void publishGameToNewSlot(); void publishGameToNewGroupSlot(int groupId); void publishGameToExistingSlot(int gameId); void reloadDataFromWeb(); private: struct CategoryUiSettings { enum NameMode { FlatNames, NameAsFolderPath }; QString groupDisplayName; QIcon icon; boost::function displayNameGetter; boost::function shouldBuildExplorerItemCallback; boost::function doubleClickCallback; // The QPoint provided will be global (ready to use for showing context menu) boost::function contextMenuCallback; boost::function groupContextMenuCallback; boost::function nameChangedCallback; NameMode nameMode; }; struct NamedAssetCopyInfo { std::string assetName; int aliasType; bool needsTargetIdUpdate; EntityProperties newAssetIdProperties; boost::function newTargetIdGetter; }; int currentGameId; int currentPlaceId; // This widget is stateful, and does a lot of async requests for data. Use // a session identifier to throw out async requests that are stale (e.g. // request places for a universe, then the widget is pointed to a different // universe before the request returns). int currentSessionId; boost::optional currentGameGroupId; bool enoughDataLoadedToBeAbleToOpenStartPlace; bool startPlaceOpenRequested; boost::unordered_map uiSettings; boost::unordered_map entitySettings; UniverseSettings gameSettings; QLabel* nameLabel; AbortableLineEdit* gameNameEdit; QLabel* idLabel; QPushButton* reloadButton; static void handleUniverseForPlaceResponse( QPointer explorer, int originatingSessionId, int placeId, std::string* data, std::exception* error); static void doneLoadingDataForCategory(QPointer explorer, int originatingSessionId, EntityCategory category); static QStandardItem* buildItem( const CategoryUiSettings& uiSettings, const CategoryWebSettings& webSettings, QStandardItem* root, EntityProperties* settings); QStandardItemModel* getModel(); QStandardItem* findGroup(EntityCategory type); QStandardItem* makeGroup(EntityCategory type); void openAndFocusConfigureDoc(const std::string& url); void setGroupLoadingStatus(EntityCategory category); void placeDoubleClickCallback(EntityProperties* placeInfo); void namedAssetsDoubleClickCallback(EntityProperties* assetInfo); void openPlace(int placeId); void updateItemForOpenedPlaceState(QStandardItem* placeItem); void refreshOpenedPlaceIndicator(); void openStartPlaceIfRequestedAndNoPlaceOpenedAndDataReady(); void publishInternal(boost::function newUniverseFuture); std::pair> buildPlaceContentPair(EntityProperties* properties); NamedAssetCopyInfo buildNamedContentInfo(EntityPropertiesForCategory::ref targetProperties, int sourceUnviverseId, boost::optional targetUniverseGroupId, boost::unordered_map>* devProductIdRemap, EntityProperties* properties); void publishGameThread(boost::function newUniverseFuture, bool* publishSucceeded, int* targetGameId); void addNewPlace(); void placeContextMenuHandler(const QPoint& point, EntityProperties* properties); void developerProductContextMenuHandler(const QPoint& point, EntityProperties* properties); void namedAssetsContextMenuHandler(const QPoint& point, EntityProperties* properties); void badgesContextMenuHandler(const QPoint& point, EntityProperties* properties); void badgesPlaceContextMenuHandler(const QPoint& point, EntityProperties* properties); void placeGroupContextMenuHandler(const QPoint& point); void developerProductGroupContextMenuHandler(const QPoint& point); void namedAssetsGroupContextMenuHandler(const QPoint& point); void handleRename(const QPoint& globalPoint); void handleRemoveAssetName(const std::string& name, bool* needReload); void bulkAddNewImageNames(); void insertNamedImage(EntityProperties* imageInfo); void insertNamedScript(EntityProperties* scriptInfo, shared_ptr container); void checkRowForNameUpdate(EntityCategory category, QStandardItem* entityRow); void afterNamedAssetsFinishedRecursive(QStandardItem* root, QStringList& imagesToReload); bool namedAssetHasUnpublishedChanges(const std::string& name); boost::shared_future publishIfThereAreLocalModifications(EntityProperties* settings); private Q_SLOTS: void doOpenGameFromGameId(int gameId); void failedToLoadSettings(int originatingSessionId); void populateWithLoadedData(int originatingSessionId, int category); void afterPlacesLoadedFinished(int originatingSessionId); void afterNamedAssetsFinished(int originatingSessionId); void refreshNamedScriptIcons(int originatingSessionId); void afterBadgesFinished(int originatingSessionId); void thumbnailLoadedForImage(int originatingSessionId, QModelIndex item, QVariant future); void doubleClickHandler(const QModelIndex& modelIndex); // The QPoint provided will be relative to the viewport void contextMenuHandler(const QPoint& clickPoint); void itemChangedHandler(QStandardItem* updatedItem); void handleNameUpdates(); void doneEditingUniverseName(); };