diff --git a/SpeckleConnector/Connector.vcxproj b/SpeckleConnector/Connector.vcxproj index fb75216..b00b5d6 100644 --- a/SpeckleConnector/Connector.vcxproj +++ b/SpeckleConnector/Connector.vcxproj @@ -605,7 +605,7 @@ CALL "$(ProjectDir)..\SpeckleLib\Make.win\install.bat" true false - $(HEADER_PATH_5)\Lib\ACAP_STATD.lib;$(HEADER_PATH_5)\Modules\DGGraphix\Win\DGGraphixImp.LIB;$(HEADER_PATH_5)\Modules\DGLib\Win\DGImp.lib;$(HEADER_PATH_5)\Modules\Geometry\Win\GeometryImp.LIB;$(HEADER_PATH_5)\Modules\Graphix\Win\GraphixImp.LIB;$(HEADER_PATH_5)\Modules\GSModeler\Win\GSModelerImp.LIB;$(HEADER_PATH_5)\Modules\GSRoot\Win\GSRootImp.lib;$(HEADER_PATH_5)\Modules\GXImage\Win\GXImageImp.lib;$(HEADER_PATH_5)\Modules\GXImageBase\Win\GXImageBaseImp.lib;$(HEADER_PATH_5)\Modules\GX\Win\GXImp.LIB;$(HEADER_PATH_5)\Modules\InputOutput\Win\InputOutputImp.lib;$(HEADER_PATH_5)\Modules\RS\Win\RSImp.LIB;$(HEADER_PATH_5)\Modules\TextEngine\Win\TextEngineImp.LIB;$(HEADER_PATH_5)\Modules\UCLib\Win\UCImp.lib;$(HEADER_PATH_5)\Modules\UDLib\Win\UDImp.lib;$(HEADER_PATH_5)\Modules\VBElemDialogs\Win\VBElemDialogsImp.LIB;$(HEADER_PATH_5)\Modules\VectorImage\Win\VectorImageImp.LIB;$(HEADER_PATH_5)\Modules\JavascriptEngine\Win\JavascriptEngineImp.LIB;%(AdditionalDependencies) + $(HEADER_PATH_5)\Lib\ACAP_STATD.lib;$(HEADER_PATH_5)\Modules\DGGraphix\Win\DGGraphixImp.LIB;$(HEADER_PATH_5)\Modules\DGLib\Win\DGImp.lib;$(HEADER_PATH_5)\Modules\Geometry\Win\GeometryImp.LIB;$(HEADER_PATH_5)\Modules\Graphix\Win\GraphixImp.LIB;$(HEADER_PATH_5)\Modules\GSModeler\Win\GSModelerImp.LIB;$(HEADER_PATH_5)\Modules\GSRoot\Win\GSRootImp.lib;$(HEADER_PATH_5)\Modules\GXImage\Win\GXImageImp.lib;$(HEADER_PATH_5)\Modules\GXImageBase\Win\GXImageBaseImp.lib;$(HEADER_PATH_5)\Modules\GX\Win\GXImp.LIB;$(HEADER_PATH_5)\Modules\InputOutput\Win\InputOutputImp.lib;$(HEADER_PATH_5)\Modules\RS\Win\RSImp.LIB;$(HEADER_PATH_5)\Modules\TextEngine\Win\TextEngineImp.LIB;$(HEADER_PATH_5)\Modules\UCLib\Win\UCImp.lib;$(HEADER_PATH_5)\Modules\UDLib\Win\UDImp.lib;$(HEADER_PATH_5)\Modules\VBElemDialogs\Win\VBElemDialogsImp.LIB;$(HEADER_PATH_5)\Modules\VectorImage\Win\VectorImageImp.LIB;$(HEADER_PATH_5)\Modules\JavascriptEngine\Win\JavascriptEngineImp.LIB;$(HEADER_PATH_5)\Modules\JSON\Win\JSONImp.LIB;%(AdditionalDependencies) msvcrt.lib diff --git a/SpeckleConnector/Connector/Interface/Browser/Bridge/Config/Arg/ConnectorConfig.cpp b/SpeckleConnector/Connector/Interface/Browser/Bridge/Config/Arg/ConnectorConfig.cpp index f8ac881..f605e19 100644 --- a/SpeckleConnector/Connector/Interface/Browser/Bridge/Config/Arg/ConnectorConfig.cpp +++ b/SpeckleConnector/Connector/Interface/Browser/Bridge/Config/Arg/ConnectorConfig.cpp @@ -14,13 +14,11 @@ namespace { enum FieldIndex { arg0, darkTheme, - darkThemeAlt, ///>This recent addition might not be permanent - watch for changes }; ///Serialisation field IDs static std::array fieldID = { Identity{"0"}, - Identity{"DarkTheme"}, Identity{"darkTheme"}, }; @@ -41,7 +39,6 @@ bool ConnectorConfig::fillInventory(Inventory& inventory) const { inventory.merge(Inventory{ { { fieldID[darkTheme], darkTheme, element }, - { fieldID[darkThemeAlt], darkThemeAlt, element }, }, }.withType(&typeid(ConnectorConfig))); return true; @@ -63,7 +60,7 @@ Cargo::Unique ConnectorConfig::getCargo(const Inventory::Item& item) const { case arg0: //This structure is the first argument return std::make_unique(*this); - case darkTheme: case darkThemeAlt: + case darkTheme: return std::make_unique>(isDarkTheme); default: return nullptr; //Requested an unknown index diff --git a/SpeckleConnector/Connector/Interface/Browser/Bridge/Config/GetConfig.cpp b/SpeckleConnector/Connector/Interface/Browser/Bridge/Config/GetConfig.cpp index dc5cfde..6b16936 100644 --- a/SpeckleConnector/Connector/Interface/Browser/Bridge/Config/GetConfig.cpp +++ b/SpeckleConnector/Connector/Interface/Browser/Bridge/Config/GetConfig.cpp @@ -30,6 +30,6 @@ GetConfig::GetConfig() : JSBridgeMethod{"GetConfig", [&]() { --------------------------------------------------------------------*/ std::unique_ptr GetConfig::run() const { ConnectorConfig config; - ///TODO: Get the accounts here - returning an empty array for testing only + ///TODO: Get the accounts here - returning an empty array for testing only return std::make_unique(config); } //GetConfig::run diff --git a/SpeckleLib/Speckle/Interface/Browser/Bridge/Functions/GetCallResult.cpp b/SpeckleLib/Speckle/Interface/Browser/Bridge/Functions/GetCallResult.cpp index 75f04cc..158c271 100644 --- a/SpeckleLib/Speckle/Interface/Browser/Bridge/Functions/GetCallResult.cpp +++ b/SpeckleLib/Speckle/Interface/Browser/Bridge/Functions/GetCallResult.cpp @@ -38,7 +38,7 @@ GetCallResult::GetCallResult(BrowserBridge& bridge) : m_bridge{bridge}, return: The requested result (nullptr on failure) --------------------------------------------------------------------*/ std::unique_ptr GetCallResult::getResult(WrappedResultArg& argument) const { - //Confirm argument type + //Retrieve the requested result auto result = m_bridge.releaseResult(argument); auto item = dynamic_cast(result.get()); if (!item) diff --git a/SpeckleLib/Speckle/Interface/Browser/JSPortal.h b/SpeckleLib/Speckle/Interface/Browser/JSPortal.h index c706a61..b9b0c75 100644 --- a/SpeckleLib/Speckle/Interface/Browser/JSPortal.h +++ b/SpeckleLib/Speckle/Interface/Browser/JSPortal.h @@ -70,6 +70,9 @@ namespace speckle::interfac::browser { #ifdef ARCHICAD try { auto engine = getJSEngine(); + + OutputDebugString((LPCTSTR)speckle::utility::String{ "\nExecuted:\n" + code}.operator std::u16string().data()); + auto result = engine ? engine->ExecuteJS(code) : false; return result; } catch(...) { @@ -94,10 +97,13 @@ namespace speckle::interfac::browser { auto engine = getJSEngine(); if (!engine) return false; + //Define the JS object JS::Object* acObject = new JS::Object(object->getName()); + //Add all the functions supported by this object for (auto& function : *object) { acObject->AddItem(new JS::Function(function->getName(), [&](GS::Ref args) { try { + //NB: All JS functions enter at this point return function->execute(args); } catch(...) { ///TODO: Need to discuss the best course of action to notify of a failure @@ -105,6 +111,7 @@ namespace speckle::interfac::browser { return GS::Ref{}; })); } + //And finally register the object if (engine->RegisterAsynchJSObject(acObject)) { base::push_back(object); object->setPortal(*this); diff --git a/SpeckleLib/Speckle/Serialise/JSBase/JSBaseTransport.cpp b/SpeckleLib/Speckle/Serialise/JSBase/JSBaseTransport.cpp index 70c20d5..34b76d3 100644 --- a/SpeckleLib/Speckle/Serialise/JSBase/JSBaseTransport.cpp +++ b/SpeckleLib/Speckle/Serialise/JSBase/JSBaseTransport.cpp @@ -1,6 +1,8 @@ #include "Speckle/Serialise/JSBase/JSBaseTransport.h" #include "Active/Serialise/Item/Item.h" +#include "Active/Serialise/Item/Wrapper/AnyValueWrap.h" +#include "Active/Serialise/Null.h" #include "Active/Setting/Values/BoolValue.h" #include "Active/Setting/Values/DoubleValue.h" #include "Active/Setting/Values/Int32Value.h" @@ -10,6 +12,7 @@ #include "Active/Serialise/Package/PackageWrap.h" #include "Active/Serialise/XML/Item/XMLDateTime.h" +#include #include using namespace active::serialise; @@ -21,11 +24,11 @@ using namespace speckle::utility; using enum JSBaseTransport::Status; namespace { - - ///Category for JSBase processing errors + + ///Category for JSBase processing errors class JSBaseCategory : public std::error_category { public: - ///Category name + ///Category name const char* name() const noexcept override { return "speckle::serialise::jsbase::category"; } @@ -62,22 +65,22 @@ namespace { } }; - - ///JSBase processing category error instance + + ///JSBase processing category error instance static JSBaseCategory instance; - - ///Make an error code for JSBase processing + + ///Make an error code for JSBase processing inline std::error_code makeJSBaseError(JSBaseTransport::Status code) { return std::error_code(static_cast(code), instance); } - - - ///Identification type for JSBase elements + + + ///Identification type for JSBase elements struct JSBaseIdentity : Identity { - + // MARK: Types - + ///Enumeration of JSBase element tag types enum class Type { undefined, ///(&identity); jsonIdentity != nullptr) { type = jsonIdentity->type; stage = jsonIdentity->stage; - } else + } + else type = tagType; } /*! @@ -115,16 +119,16 @@ namespace { @param tagType The tag type */ JSBaseIdentity(Type tagType) : Identity() { type = tagType; } - + // MARK: Public variables - + ///The element type Type type = Type::undefined; - ///The stage at which the identity is found + ///The stage at which the identity is found Stage stage = Stage::root; - + // MARK: Functions (mutating) - + /*! Set the identity tag as the hierarchy root @return A reference to this @@ -133,7 +137,7 @@ namespace { stage = newStage; return *this; } - + /*! Set the identity tag type @param tagType The tag type @@ -144,41 +148,42 @@ namespace { return *this; } }; - + using JSElements = std::vector>; - + using enum JSBaseIdentity::Type; using enum JSBaseIdentity::Stage; - + /*-------------------------------------------------------------------- Add an item to a JSBase object - + item: The item to write destination: The JSBase destination --------------------------------------------------------------------*/ void addJSBase(GS::Ref& item, const String& tag, GS::Ref& destination) { //Attempt to add to object - if (auto object = dynamic_cast(destination.operator JS::Base*()); object != nullptr) + if (auto object = dynamic_cast(destination.operator JS::Base * ()); object != nullptr) object->AddItem(tag, item); //Attempt to add to array - else if (auto array = dynamic_cast(destination.operator JS::Base*()); array != nullptr) + else if (auto array = dynamic_cast(destination.operator JS::Base * ()); array != nullptr) array->AddItem(item); else throw std::system_error(makeJSBaseError(badDestination)); //The destination isn't a container return; } //addJSBase - - + + /*-------------------------------------------------------------------- Write an item to a JSBase object - + item: The item to write + tag: The item tag destination: The JSBase destination --------------------------------------------------------------------*/ void writeValue(const Item& item, const String& tag, GS::Ref& destination) { GS::Ref newValue; - switch(item.type().value_or(Item::text)) { + switch (item.type().value_or(Item::text)) { case Item::boolean: { BoolValue value; if (!item.write(value)) @@ -197,8 +202,8 @@ namespace { String value; if (!item.write(value)) throw std::system_error(makeJSBaseError(badValue)); - newValue = new JS::Value(value); - break; + newValue = new JS::Value(value); + break; } } if (destination) @@ -207,10 +212,25 @@ namespace { destination = newValue; } //writeValue - + + /*-------------------------------------------------------------------- + Write a null value to a JSBase object + + tag: The item tag + destination: The JSBase destination + --------------------------------------------------------------------*/ + void writeNull(const String& tag, GS::Ref& destination) { + GS::Ref newValue = new JS::Value{}; + if (destination) + addJSBase(newValue, tag, destination); + else + destination = newValue; + } //writeNull + + /*-------------------------------------------------------------------- Decompose a JSBase into constitient items, paired with a name where possible - + source: The source JSBase return: The items in the JSBase @@ -220,17 +240,19 @@ namespace { if (auto object = dynamic_cast(&source); object != nullptr) { //Decompose an object for (auto& item : object->GetItemTable()) - result.push_back({item.value->operator JS::Base*(), String{*item.key}}); - } else if (auto array = dynamic_cast(&source); array != nullptr) { + result.push_back({ item.value->operator JS::Base * (), String{*item.key} }); + } + else if (auto array = dynamic_cast(&source); array != nullptr) { //Decompose an array for (auto& item : array->GetItemArray()) - result.push_back({item, std::nullopt}); - } else + result.push_back({ item, std::nullopt }); + } + else throw std::system_error(makeJSBaseError(badSource)); //The source isn't a container return result; } //decomposeJSBase - - + + /*-------------------------------------------------------------------- Import a cargo item from a JSBase element @@ -264,18 +286,18 @@ namespace { break; } } //doJSBaseItemImport - - + + /*-------------------------------------------------------------------- Import the contents of the specified cargo from JSBase - + container: The cargo container to receive the imported data containerIdentity: The container identity source: The JSBase source --------------------------------------------------------------------*/ void doJSBaseImport(Cargo& container, const JSBaseIdentity& containerIdentity, JS::Base& source) { if (dynamic_cast(&container) != nullptr) { - //If we've got a single-value item at the root, import the source value and end + //If we've got a single-value item at the root, import the source value and end readValue(container, source); return; } @@ -288,7 +310,7 @@ namespace { if (elements.empty()) return; bool isArray = !elements[0].second; - Identity parentIdentity{containerIdentity}; + Identity parentIdentity{ containerIdentity }; //Anonymous arrays need an identity if (isArray && parentIdentity.name.empty()) { for (auto& entry : inventory) @@ -300,7 +322,7 @@ namespace { for (auto& element : elements) { Cargo::Unique cargo; Inventory::iterator incomingItem = inventory.end(); - Identity identity{element.second.value_or(parentIdentity.name)}; + Identity identity{ element.second.value_or(parentIdentity.name) }; if (incomingItem = inventory.registerIncoming(identity); incomingItem != inventory.end()) { //Seek the incoming element in the inventory if (!incomingItem->bumpAvailable()) throw std::system_error(makeJSBaseError(inventoryBoundsExceeded)); @@ -312,18 +334,18 @@ namespace { doJSBaseImport(*cargo, identity, *element.first); if (incomingItem->isRepeating()) { if (auto package = dynamic_cast(&container); - (package != nullptr) && !package->insert(std::move(cargo), *incomingItem)) + (package != nullptr) && !package->insert(std::move(cargo), *incomingItem)) throw std::system_error(makeJSBaseError(invalidObject)); } } if (!container.validate()) throw std::system_error(makeJSBaseError(invalidObject)); //The incoming data was rejected as invalid } //doJSBaseImport - - + + /*-------------------------------------------------------------------- Export cargo to JSBase - + cargo: The cargo to export identity: The cargo identity destination: The JSBase destination @@ -333,8 +355,12 @@ namespace { Inventory inventory; //Single-value items won't specify an inventory (no point) if (!cargo.fillInventory(inventory) || (inventory.empty())) { - if (item == nullptr) - throw std::system_error(makeJSBaseError(badValue)); //Non-items must be named + if (item == nullptr) { + if (dynamic_cast(&cargo) == nullptr) + throw std::system_error(makeJSBaseError(badValue)); //Non-items must be named + writeNull(identity.name, destination); + return; + } writeValue(*item, identity.name, destination); return; } @@ -350,7 +376,7 @@ namespace { auto container = destination; if (isWrapperTag) { auto containerType = cargo.entryType().value_or((inventory.size() == 1) && !(inventory.begin()->maximum() == 1) ? - Entry::Type::array : Entry::Type::element); + Entry::Type::array : Entry::Type::element); if (containerType == Entry::Type::array) container = new JS::Array(); else @@ -369,29 +395,82 @@ namespace { for (item.available = 0; item.available < limit; ++item.available) { if (auto content = cargo.getCargo(item); content) { doJSBaseExport(*content, item.identity(), container); - } else + } + else break; //Discontinue an inventory item when the supply runs out } } } //doJSBaseExport - + + + /*-------------------------------------------------------------------- + Convert a JS::Base object to JSON + + jsBase: The object to convert + --------------------------------------------------------------------*/ + JSON::ValueRef convertToJSONValue(const GS::Ref& jsBase) { + JS::Object* objectJS = dynamic_cast ((JS::Base*)jsBase); + if (objectJS != nullptr) { + JSON::ObjectValueRef objectJSON = new JSON::ObjectValue(); + for (const auto& member : objectJS->GetItemTable()) + objectJSON->AddValue(*member.key, convertToJSONValue(*member.value)); + return objectJSON; + } + JS::Array* arrayJs = dynamic_cast ((JS::Base*)jsBase); + if (arrayJs != nullptr) { + JSON::ArrayValueRef arrayJSON = new JSON::ArrayValue(); + for (const auto& item : arrayJs->GetItemArray()) + arrayJSON->AddValue(convertToJSONValue(item)); + return arrayJSON; + } + JS::Value* valueJs = dynamic_cast ((JS::Base*)jsBase); + if (valueJs != nullptr) { + JSON::ValueRef primitiveJSON; + switch (valueJs->GetType()) { + case JS::Value::DEFAULT: + primitiveJSON = new JSON::NullValue(); + break; + case JS::Value::BOOL: + primitiveJSON = new JSON::BoolValue(valueJs->GetBool()); + break; + case JS::Value::INTEGER: + primitiveJSON = new JSON::NumberValue(valueJs->GetInteger()); + break; + case JS::Value::UINTEGER: + primitiveJSON = new JSON::NumberValue(valueJs->GetUInteger()); + break; + case JS::Value::DOUBLE: + primitiveJSON = new JSON::NumberValue(valueJs->GetDouble()); + break; + case JS::Value::STRING: + primitiveJSON = new JSON::StringValue(valueJs->GetString()); + break; + default: + DBBREAK(); + } + return primitiveJSON; + } + return nullptr; + } //convertToJSONValue + } /*-------------------------------------------------------------------- Send cargo as JSBase to a specified destination - + cargo: The cargo to be sent as JS::Base identity: The cargo identity (name, optional namespace) destination: A reference to a JS::Base object (will be populated by this function) --------------------------------------------------------------------*/ void JSBaseTransport::send(Cargo&& cargo, const Identity& identity, GS::Ref& destination) const { doJSBaseExport(cargo, JSBaseIdentity(identity).atStage(root), destination); + OutputDebugString((WCHAR*)String {"\nSent:\n" + convertToJSON(destination)}.operator std::u16string().data()); } //JSBaseTransport::send /*-------------------------------------------------------------------- Receive cargo from a specified JSBase source - + cargo: The cargo to receive the JS::Base data identity: The cargo identity (name, optional namespace) source: A reference to a JS::Base object @@ -399,5 +478,28 @@ void JSBaseTransport::send(Cargo&& cargo, const Identity& identity, GS::Ref source) const { if (!source) throw std::system_error(makeJSBaseError(badSource)); + OutputDebugString((WCHAR*) String{"\nReceived:\n" + convertToJSON(source)}.operator std::u16string().data()); doJSBaseImport(cargo, JSBaseIdentity(identity).atStage(root), *source); } //JSBaseTransport::receive + + +/*-------------------------------------------------------------------- + Convert a JS::Base object to JSON + + jsBase: The object to convert + --------------------------------------------------------------------*/ +String JSBaseTransport::convertToJSON(const GS::Ref& jsBase) { + GS::UniString resultString; + try { + //JDOMStringWriter can't cope with single values + if (auto jsValue = dynamic_cast(jsBase.operator JS::Base * ()); jsValue != nullptr) { + AnyValueWrap value; + readValue(value, *jsBase); + String json; + value.write(json); + return json; + } else if (auto jsonRef = convertToJSONValue(jsBase); jsonRef != nullptr) + JSON::JDOMStringWriter writer(*jsonRef, resultString); + } catch (...) {} + return resultString; +} //JSBaseTransport::convertToJSON diff --git a/SpeckleLib/Speckle/Serialise/JSBase/JSBaseTransport.h b/SpeckleLib/Speckle/Serialise/JSBase/JSBaseTransport.h index 35a4f45..1c020bf 100644 --- a/SpeckleLib/Speckle/Serialise/JSBase/JSBaseTransport.h +++ b/SpeckleLib/Speckle/Serialise/JSBase/JSBaseTransport.h @@ -23,6 +23,12 @@ namespace speckle::serialise::jsbase { class JSBaseTransport { public: + /*! + Convert a JS::Base object to JSON + @param jsBase The object to convert + */ + static speckle::utility::String convertToJSON(const GS::Ref& jsBase); + // MARK: - Types ///Status of of the XML transport